changeset 2602:2bbb7d47fd55 draft

(svn r3139) -NewGRF, Feature: support for articulated rail vehicles. This is used, for example, by coal tenders.
author peter1138 <peter1138@openttd.org>
date Sat, 05 Nov 2005 16:07:26 +0000
parents d995ef36dbf5
children ef269df2933f
files engine.c engine.h train_cmd.c train_gui.c vehicle.c vehicle.h
diffstat 6 files changed, 232 insertions(+), 66 deletions(-) [+]
line wrap: on
line diff
--- a/engine.c
+++ b/engine.c
@@ -375,6 +375,8 @@
 			if (dsg->variable == 0x0C) {
 				/* Callback ID */
 				value = callback_info & 0xFF;
+			} else if (dsg->variable == 0x10) {
+				value = (callback_info >> 8) & 0xFF;
 			} else if ((dsg->variable >> 6) == 0) {
 				/* General property */
 				value = GetDeterministicSpriteValue(dsg->variable);
--- a/engine.h
+++ b/engine.h
@@ -153,6 +153,8 @@
 	// Refit capacity, the passed vehicle needs to have its ->cargo_type set to
 	// the cargo we are refitting to, returns the new cargo capacity
 	CBID_REFIT_CAP = 0x15,
+
+	CBID_ARTIC_ENGINE = 0x16,
 };
 
 // bit positions for rvi->callbackmask, indicates which callbacks are used by an engine
@@ -161,6 +163,7 @@
 	CBM_WAGON_POWER = 0,
 	CBM_VEH_LENGTH = 1,
 	CBM_REFIT_CAP = 3,
+	CBM_ARTIC_ENGINE = 4,
 };
 
 enum {
--- a/train_cmd.c
+++ b/train_cmd.c
@@ -46,12 +46,17 @@
 		const RailVehicleInfo *rvi = RailVehInfo(u->engine_type);
 		uint16 vweight = 0;
 
-		// vehicle weight is the sum of the weight of the vehicle and the weight of its cargo
-		vweight += rvi->weight;
 		vweight += (_cargoc.weights[u->cargo_type] * u->cargo_count) / 16;
-		// powered wagons have extra weight added
-		if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON))
-			vweight += RailVehInfo(v->engine_type)->pow_wag_weight;
+
+		// Vehicle weight is not added for articulated parts.
+		if (u->subtype != TS_Artic_Part) {
+			// vehicle weight is the sum of the weight of the vehicle and the weight of its cargo
+			vweight += rvi->weight;
+
+			// powered wagons have extra weight added
+			if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON))
+				vweight += RailVehInfo(v->engine_type)->pow_wag_weight;
+		}
 
 		// consist weight is the sum of the weight of all vehicles in the consist
 		weight += vweight;
@@ -92,14 +97,11 @@
 		// update the 'first engine'
 		u->u.rail.first_engine = (v == u) ? INVALID_VEHICLE : first_engine;
 
-		// power is the sum of the powers of all engines and powered wagons in the consist
-		power += rvi_u->power;
-
 		if (rvi_u->visual_effect != 0) {
 			u->u.rail.cached_vis_effect = rvi_u->visual_effect;
 		} else {
-			if (rvi_u->flags & RVI_WAGON) {
-				// Wagons have no effect by default
+			if (rvi_u->flags & RVI_WAGON || u->subtype == TS_Artic_Part) {
+				// Wagons and articulated parts have no effect by default
 				u->u.rail.cached_vis_effect = 0x40;
 			} else if (rvi_u->engclass == 0) {
 				// Steam is offset by -4 units
@@ -110,28 +112,33 @@
 			}
 		}
 
-		// check if its a powered wagon
-		CLRBIT(u->u.rail.flags, VRF_POWEREDWAGON);
-		if ((rvi_v->pow_wag_power != 0) && (rvi_u->flags & RVI_WAGON) && UsesWagonOverride(u)) {
-			if (HASBIT(rvi_u->callbackmask, CBM_WAGON_POWER)) {
-				uint16 callback = GetCallBackResult(CBID_WAGON_POWER,  u->engine_type, u);
-
-				if (callback != CALLBACK_FAILED)
-					u->u.rail.cached_vis_effect = callback;
+		if (u->subtype != TS_Artic_Part) {
+			// power is the sum of the powers of all engines and powered wagons in the consist
+			power += rvi_u->power;
+
+			// check if its a powered wagon
+			CLRBIT(u->u.rail.flags, VRF_POWEREDWAGON);
+			if ((rvi_v->pow_wag_power != 0) && (rvi_u->flags & RVI_WAGON) && UsesWagonOverride(u)) {
+				if (HASBIT(rvi_u->callbackmask, CBM_WAGON_POWER)) {
+					uint16 callback = GetCallBackResult(CBID_WAGON_POWER,  u->engine_type, u);
+
+					if (callback != CALLBACK_FAILED)
+						u->u.rail.cached_vis_effect = callback;
+				}
+
+				if (u->u.rail.cached_vis_effect < 0x40) {
+					/* wagon is powered */
+					SETBIT(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status
+					power += rvi_v->pow_wag_power;
+				}
 			}
 
-			if (u->u.rail.cached_vis_effect < 0x40) {
-				/* wagon is powered */
-				SETBIT(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status
-				power += rvi_v->pow_wag_power;
-			}
+			// max speed is the minimum of the speed limits of all vehicles in the consist
+			if (!(rvi_u->flags & RVI_WAGON) || _patches.wagon_speed_limits)
+				if (rvi_u->max_speed != 0 && !UsesWagonOverride(u))
+					max_speed = min(rvi_u->max_speed, max_speed);
 		}
 
-		// max speed is the minimum of the speed limits of all vehicles in the consist
-		if (!(rvi_u->flags & RVI_WAGON) || _patches.wagon_speed_limits)
-			if (rvi_u->max_speed != 0 && !UsesWagonOverride(u))
-				max_speed = min(rvi_u->max_speed, max_speed);
-
 		// check the vehicle length (callback)
 		veh_len = CALLBACK_FAILED;
 		if (HASBIT(rvi_u->callbackmask, CBM_VEH_LENGTH))
@@ -418,6 +425,78 @@
 	DrawSprite(image | image_ormod, x, y);
 }
 
+static uint CountArticulatedParts(const RailVehicleInfo *rvi, EngineID engine_type)
+{
+	uint16 callback;
+	uint i;
+
+	if (!HASBIT(rvi->callbackmask, CBM_ARTIC_ENGINE))
+		return 0;
+
+	for (i = 1; i < 10; i++) {
+		callback = GetCallBackResult(CBID_ARTIC_ENGINE + (i << 8), engine_type, NULL);
+		if (callback == CALLBACK_FAILED || callback == 0xFF)
+			break;
+	}
+
+	return i;
+}
+
+static void AddArticulatedParts(const RailVehicleInfo *rvi, Vehicle **vl)
+{
+	const RailVehicleInfo *rvi_artic;
+	EngineID engine_type;
+	Vehicle *v = vl[0];
+	Vehicle *u = v;
+	uint16 callback;
+	bool flip_image;
+	uint i;
+
+	if (!HASBIT(rvi->callbackmask, CBM_ARTIC_ENGINE))
+		return;
+
+	for (i = 1; i < 10; i++) {
+		callback = GetCallBackResult(CBID_ARTIC_ENGINE + (i << 8), v->engine_type, NULL);
+		if (callback == CALLBACK_FAILED || callback == 0xFF)
+			return;
+
+		u->next = vl[i];
+		u = u->next;
+
+		engine_type = GB(callback, 0, 6);
+		flip_image = HASBIT(callback, 7);
+		rvi_artic = RailVehInfo(engine_type);
+
+		// get common values from first engine
+		u->direction = v->direction;
+		u->owner = v->owner;
+		u->tile = v->tile;
+		u->x_pos = v->x_pos;
+		u->y_pos = v->y_pos;
+		u->z_pos = v->z_pos;
+		u->z_height = v->z_height;
+		u->u.rail.track = v->u.rail.track;
+		u->u.rail.railtype = v->u.rail.railtype;
+		u->build_year = v->build_year;
+		u->vehstatus = v->vehstatus & ~VS_STOPPED;
+		u->u.rail.first_engine = v->engine_type;
+
+		// get more settings from rail vehicle info
+		u->spritenum = rvi_artic->image_index;
+		if (flip_image) u->spritenum++;
+		u->cargo_type = rvi_artic->cargo_type;
+		u->cargo_cap = rvi_artic->capacity;
+		u->max_speed = 0;
+		u->max_age = 0;
+		u->engine_type = engine_type;
+		u->value = 0;
+		u->type = VEH_Train;
+		u->subtype = TS_Artic_Part;
+		u->cur_image = 0xAC2;
+
+		VehiclePositionChanged(u);
+	}
+}
 
 static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags)
 {
@@ -426,24 +505,27 @@
 	const RailVehicleInfo *rvi;
 	const Engine *e;
 	int x,y;
+	uint num_vehicles;
 
 	SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
 
 	rvi = RailVehInfo(engine);
 	value = (rvi->base_cost * _price.build_railwagon) >> 8;
 
+	num_vehicles = 1 + CountArticulatedParts(rvi, engine);
+
 	if (!(flags & DC_QUERY_COST)) {
-		_error_message = STR_00E1_TOO_MANY_VEHICLES_IN_GAME;
-
-		v = AllocateVehicle();
-		if (v == NULL)
-			return CMD_ERROR;
+		Vehicle *vl[num_vehicles];
+
+		if (!AllocateVehicles(vl, num_vehicles))
+			return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
 
 		if (flags & DC_EXEC) {
 			byte img = rvi->image_index;
 			Vehicle *u, *w;
 			uint dir;
 
+			v = vl[0];
 			v->spritenum = img;
 
 			u = NULL;
@@ -492,6 +574,8 @@
 			v->type = VEH_Train;
 			v->cur_image = 0xAC2;
 
+			AddArticulatedParts(rvi, vl);
+
 			_new_wagon_id = v->index;
 			_new_vehicle_id = v->index;
 
@@ -581,10 +665,11 @@
 {
 	const RailVehicleInfo *rvi;
 	int value;
-	Vehicle *v, *u;
+	Vehicle *v;
 	UnitID unit_num;
 	Engine *e;
 	TileIndex tile = TileVirtXY(x, y);
+	uint num_vehicles;
 
 	/* Check if the engine-type is valid (for the player) */
 	if (!IsEngineBuildable(p1, VEH_Train)) return CMD_ERROR;
@@ -612,12 +697,16 @@
 	if (rvi->flags&RVI_MULTIHEAD && HASBIT(p2,0))
 		value /= 2;
 
+	num_vehicles = (rvi->flags & RVI_MULTIHEAD && HASBIT(p2, 0)) ? 2 : 1;
+	num_vehicles += CountArticulatedParts(rvi, p1);
 
 	if (!(flags & DC_QUERY_COST)) {
-		v = AllocateVehicle();
-		if (v == NULL || IsOrderPoolFull())
+		Vehicle *vl[num_vehicles];
+		if (!AllocateVehicles(vl, num_vehicles) || IsOrderPoolFull())
 			return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
 
+		v = vl[0];
+
 		unit_num = GetFreeUnitNumber(VEH_Train);
 		if (unit_num > _patches.max_trains)
 			return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
@@ -665,8 +754,10 @@
 
 			VehiclePositionChanged(v);
 
-			if (rvi->flags&RVI_MULTIHEAD && (u = AllocateVehicle()) != NULL && !HASBIT(p2,0)) {
-					AddRearEngineToMultiheadedTrain(v, u, true);
+			if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) {
+				AddRearEngineToMultiheadedTrain(vl[0], vl[1], true);
+			} else {
+				AddArticulatedParts(rvi, vl);
 			}
 
 			TrainConsistChanged(v);
@@ -716,23 +807,27 @@
 	return count;
 }
 
-// unlink a rail wagon from the linked list.
-// returns the new value of first
+/**
+ * Unlink a rail wagon from the consist.
+ * @param v Vehicle to remove.
+ * @param first The first vehicle of the consist.
+ * @return The first vehicle of the consist.
+ */
 static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first)
 {
 	Vehicle *u;
 
 	// unlinking the first vehicle of the chain?
 	if (v == first) {
-		v = v->next;
+		v = GetNextVehicle(v);
 		if (v == NULL) return NULL;
 
 		v->subtype = TS_Free_Car;
 		return v;
 	}
 
-	for (u = first; u->next != v; u = u->next) {}
-	u->next = v->next;
+	for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {}
+	GetLastEnginePart(u)->next = GetNextVehicle(v);
 	return first;
 }
 
@@ -788,6 +883,12 @@
 		dst = GetVehicle(d);
 	}
 
+	// if an articulated part is being handled, deal with its parent vehicle
+	while (src->subtype == TS_Artic_Part) src = GetPrevVehicleInChain(src);
+	if (dst != NULL) {
+		while (dst->subtype == TS_Artic_Part) dst = GetPrevVehicleInChain(dst);
+	}
+
 	// don't move the same vehicle..
 	if (src == dst) return 0;
 
@@ -798,7 +899,11 @@
 	/* locate the head of the two chains */
 	src_head = GetFirstVehicleInChain(src);
 	dst_head = NULL;
-	if (dst != NULL) dst_head = GetFirstVehicleInChain(dst);
+	if (dst != NULL) {
+		dst_head = GetFirstVehicleInChain(dst);
+		// Now deal with articulated part of destination wagon
+		dst = GetLastEnginePart(dst);
+	}
 
 	/* clear the ->first cache */
 	{
@@ -852,8 +957,8 @@
 			// unlink ALL wagons
 			if (src != src_head) {
 				Vehicle *v = src_head;
-				while (v->next != src) v=v->next;
-				v->next = NULL;
+				while (GetNextVehicle(v) != src) v = GetNextVehicle(v);
+				GetLastEnginePart(v)->next = NULL;
 			} else {
 				src_head = NULL;
 			}
@@ -863,7 +968,7 @@
 				dst_head = NULL;
 			// unlink single wagon from linked list
 			src_head = UnlinkWagon(src, src_head);
-			src->next = NULL;
+			GetLastEnginePart(src)->next = NULL;
 		}
 
 		if (dst == NULL) {
@@ -893,8 +998,8 @@
 			{
 				Vehicle *v;
 
-				for (v = src; v->next != NULL; v = v->next) {};
-				v->next = dst->next;
+				for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v));
+				GetLastEnginePart(v)->next = dst->next;
 			}
 			dst->next = src;
 		}
@@ -999,6 +1104,7 @@
 
 	SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
 
+	while (v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v);
 	first = GetFirstVehicleInChain(v);
 
 	// make sure the vehicle is stopped in the depot
@@ -1032,7 +1138,7 @@
 			/* 2. We are selling the first engine, some special action might be required
 				* here, so take attention */
 			if ((flags & DC_EXEC) && v == first) {
-				Vehicle *new_f = first->next;
+				Vehicle *new_f = GetNextVehicle(first);
 
 				/* 2.1 If the first wagon is sold, update the first-> pointers to NULL */
 				for (tmp = first; tmp != NULL; tmp = tmp->next) tmp->first = NULL;
@@ -1082,7 +1188,7 @@
 				* engines to its train anyways */
 				if (p2 == 2 && ori_subtype == TS_Front_Engine) {
 					for (v = first; v != NULL; v = tmp) {
-						tmp = v->next;
+						tmp = GetNextVehicle(v);
 						DoCommandByTile(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
 					}
 				}
@@ -1093,7 +1199,7 @@
 			* to be able to deduce which ones go with which ones */
 			int enf_count = 0;
 			int enr_count = 0;
-			for (tmp = first; tmp != NULL; tmp = tmp->next) {
+			for (tmp = first; tmp != NULL; tmp = GetNextVehicle(tmp)) {
 				if (RailVehInfo(tmp->engine_type)->flags & RVI_MULTIHEAD)
 					(IS_FIRSTHEAD_SPRITE(tmp->spritenum)) ? enf_count++ : enr_count++;
 			}
@@ -1102,7 +1208,7 @@
 			* If we encounter a matching rear-engine to a front-engine
 			* earlier in the chain (before deletion), leave it alone */
 			for (; v != NULL; v = tmp) {
-				tmp = v->next;
+				tmp = GetNextVehicle(v);
 
 				if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) {
 					if (IS_FIRSTHEAD_SPRITE(v->spritenum)) {
--- a/train_gui.c
+++ b/train_gui.c
@@ -530,6 +530,9 @@
 		if (v == NULL) break;
 	}
 
+	// if an articulated part was selected, find its parent
+	while (v != NULL && v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v);
+
 	d->wagon = v;
 
 	return 0;
@@ -1136,7 +1139,7 @@
 			tot_cargo[u->cargo_type][0] += u->cargo_count;
 			tot_cargo[u->cargo_type][1] += u->cargo_cap;
 		}
-	} while ( (u = u->next) != NULL);
+	} while ((u = GetNextVehicle(u)) != NULL);
 
 	/*	set scroll-amount seperately from counting, as to not
 			compute num double for more carriages of the same type
@@ -1197,11 +1200,17 @@
 	if (det_tab != 3) {
 		for(;;) {
 			if (--sel < 0 && sel >= -6) {
-				DrawTrainImage(v, x, y, 1, 0, INVALID_VEHICLE);
-				_train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + 30, y + 2);
+				int dx = 0;
+				u = v;
+				do {
+					DrawTrainImage(u, x + WagonLengthToPixels(dx), y, 1, 0, INVALID_VEHICLE);
+					dx += u->u.rail.cached_veh_length;
+					u = u->next;
+				} while (u != NULL && u->subtype == TS_Artic_Part);
+				_train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + WagonLengthToPixels(dx) + 2, y + 2);
 				y += 14;
 			}
-			if ( (v=v->next) == NULL)
+			if ((v = GetNextVehicle(v)) == NULL)
 				return;
 		}
 	}
--- a/vehicle.c
+++ b/vehicle.c
@@ -519,20 +519,28 @@
 
 void DeleteVehicle(Vehicle *v)
 {
-	DeleteName(v->string_id);
-	v->type = 0;
-	UpdateVehiclePosHash(v, INVALID_COORD, 0);
-	v->next_hash = INVALID_VEHICLE;
+	Vehicle *u;
+	bool has_artic_part = false;
 
-	if (v->orders != NULL)
-		DeleteVehicleOrders(v);
+	do {
+		u = v->next;
+		has_artic_part = EngineHasArticPart(v);
+		DeleteName(v->string_id);
+		v->type = 0;
+		UpdateVehiclePosHash(v, INVALID_COORD, 0);
+		v->next_hash = INVALID_VEHICLE;
+
+		if (v->orders != NULL)
+			DeleteVehicleOrders(v);
+		v = u;
+	} while (v != NULL && has_artic_part);
 }
 
 void DeleteVehicleChain(Vehicle *v)
 {
 	do {
 		Vehicle *u = v;
-		v = v->next;
+		v = GetNextVehicle(v);
 		DeleteVehicle(u);
 	} while (v != NULL);
 }
@@ -1486,6 +1494,8 @@
 			const RailVehicleInfo *rvi = RailVehInfo(v->engine_type);
 			if (rvi->flags & RVI_MULTIHEAD)
 				return GetRearEngine(v, v->engine_type);
+			if (v->next != NULL && v->next->subtype == TS_Artic_Part)
+				return v->next;
 		}
 			break;
 		case VEH_Aircraft:
@@ -1579,7 +1589,7 @@
 			}
 			w_rear = w;	// trains needs to know the last car in the train, so they can add more in next loop
 		}
-	} while (v->type == VEH_Train && (v=v->next) != NULL);
+	} while (v->type == VEH_Train && (v = GetNextVehicle(v)) != NULL);
 
 	if (flags & DC_EXEC) {
 		v = v_front;
@@ -1755,7 +1765,7 @@
 				break;
 
 			cost += temp_cost;
-		} while (w->type == VEH_Train && (w=w->next) != NULL);
+		} while (w->type == VEH_Train && (w = GetNextVehicle(w)) != NULL);
 
 		if (!(flags & DC_EXEC) && (CmdFailed(temp_cost) || p->money64 < (int32)(cost + p->engine_renew_money) || cost == 0)) {
 			if (p->money64 < (int32)(cost + p->engine_renew_money) && ( _local_player == v->owner ) && cost != 0) {
--- a/vehicle.h
+++ b/vehicle.h
@@ -30,6 +30,7 @@
 // 1 and 3 do not appear to be used
 typedef enum TrainSubtypes {
 	TS_Front_Engine = 0, // Leading engine of a train
+	TS_Artic_Part = 1,   // Articulated part of an engine
 	TS_Not_First = 2,    // Wagon or additional engine
 	TS_Free_Car = 4,     // First in a wagon chain (in depot)
 } TrainSubtype;
@@ -414,6 +415,41 @@
 	return index < GetVehiclePoolSize();
 }
 
+/**
+ * Get the next real (non-articulated part) vehicle in the consist.
+ * @param v Vehicle.
+ * @return Next vehicle in the consist.
+ */
+static inline Vehicle *GetNextVehicle(const Vehicle *v)
+{
+	Vehicle *u = v->next;
+	while (u != NULL && u->subtype == TS_Artic_Part) {
+		u = u->next;
+	}
+	return u;
+}
+
+/**
+ * Check if an engine has an articulated part.
+ * @param v Vehicle.
+ * @return True if the engine has an articulated part.
+ */
+static inline bool EngineHasArticPart(const Vehicle *v)
+{
+	return (v->next != NULL && v->next->subtype == TS_Artic_Part);
+}
+
+/**
+ * Get the last part of a multi-part engine.
+ * @param v Vehicle.
+ * @return Last part of the engine.
+ */
+static inline Vehicle *GetLastEnginePart(Vehicle *v)
+{
+	while (EngineHasArticPart(v)) v = v->next;
+	return v;
+}
+
 /* Returns order 'index' of a vehicle or NULL when it doesn't exists */
 static inline Order *GetVehicleOrder(const Vehicle *v, int index)
 {