From 22a46798378768271af6b90cd41327155a909dd2 Mon Sep 17 00:00:00 2001 From: bjarni Date: Fri, 18 Nov 2005 23:41:03 +0000 Subject: [PATCH] (svn r3218) -Feature: Multiheaded train engines will now stay in the same train This means that any user attempt to remove a rear engine will tell the user to move the front engine instead This fixes the assert when moving multiheaded engines (introduced in r3144) Note: to make old savegames use this feature, some engines might be turned around in order to link engines in pairs -Codechange: train subtype is now a bitmask This allows fast access to info like if it is a wagon or engine and if it is in front and so on Note: savegame version bump --- economy.c | 9 +- lang/english.txt | 1 + main_gui.c | 3 +- network_server.c | 3 +- order_gui.c | 5 +- player_gui.c | 3 +- saveload.c | 2 +- station_cmd.c | 3 +- train.h | 208 +++++++++++++++++++++++++++ train_cmd.c | 351 ++++++++++++++++++++++++++++----------------- train_gui.c | 33 ++--- tunnelbridge_cmd.c | 5 +- vehicle.c | 168 +++++++++++++++++----- vehicle.h | 47 +----- vehicle_gui.c | 11 +- viewport.c | 3 +- water_cmd.c | 3 +- 17 files changed, 615 insertions(+), 243 deletions(-) create mode 100644 train.h diff --git a/economy.c b/economy.c index 04aa5ad05e..d595d13f49 100644 --- a/economy.c +++ b/economy.c @@ -26,6 +26,7 @@ #include "variables.h" #include "vehicle_gui.h" #include "ai/ai.h" +#include "train.h" // Score info const ScoreInfo _score_info[] = { @@ -129,7 +130,7 @@ int UpdateCompanyRatingAndValue(Player *p, bool update) FOR_ALL_VEHICLES(v) { if (v->owner != owner) continue; - if ((v->type == VEH_Train && v->subtype == TS_Front_Engine) || + if ((v->type == VEH_Train && IsFrontEngine(v)) || v->type == VEH_Road || (v->type == VEH_Aircraft && v->subtype<=2) || v->type == VEH_Ship) { @@ -313,7 +314,7 @@ void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player) if (v->owner == new_player) { switch (v->type) { case VEH_Train: - if (v->subtype == TS_Front_Engine) num_train++; + if (IsFrontEngine(v)) num_train++; break; case VEH_Road: num_road++; @@ -338,7 +339,7 @@ void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player) DeleteVehicle(v); } else { v->owner = new_player; - if (v->type == VEH_Train && v->subtype == TS_Front_Engine) + if (v->type == VEH_Train && IsFrontEngine(v)) v->unitnumber = ++num_train; else if (v->type == VEH_Road) v->unitnumber = ++num_road; @@ -1289,7 +1290,7 @@ static bool LoadWait(const Vehicle *v, const Vehicle *u) { } FOR_ALL_VEHICLES(x) { - if ((x->type != VEH_Train || x->subtype == TS_Front_Engine) && // for all locs + if ((x->type != VEH_Train || IsFrontEngine(x)) && // for all locs u->last_station_visited == x->last_station_visited && // at the same station !(x->vehstatus & VS_STOPPED) && // not stopped x->current_order.type == OT_LOADING && // loading diff --git a/lang/english.txt b/lang/english.txt index 98c6b17062..4a9dee00ba 100644 --- a/lang/english.txt +++ b/lang/english.txt @@ -2405,6 +2405,7 @@ STR_8833_CAN_T_INSERT_NEW_ORDER :{WHITE}Can't in STR_8834_CAN_T_DELETE_THIS_ORDER :{WHITE}Can't delete this order... STR_8835_CAN_T_MODIFY_THIS_ORDER :{WHITE}Can't modify this order... STR_8837_CAN_T_MOVE_VEHICLE :{WHITE}Can't move vehicle... +STR_REAR_ENGINE_FOLLOW_FRONT_ERROR :{WHITE}The rear engine will always follow its front counterpart STR_8838_N_A :N/A{SKIP} STR_8839_CAN_T_SELL_RAILROAD_VEHICLE :{WHITE}Can't sell railway vehicle... STR_883A_UNABLE_TO_FIND_ROUTE_TO :{WHITE}Unable to find route to local depot diff --git a/main_gui.c b/main_gui.c index c3c8277ad5..6272db3c88 100644 --- a/main_gui.c +++ b/main_gui.c @@ -26,6 +26,7 @@ #include "signs.h" #include "waypoint.h" #include "variables.h" +#include "train.h" #include "network_data.h" #include "network_client.h" @@ -799,7 +800,7 @@ static void ToolbarTrainClick(Window *w) int dis = -1; FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Front_Engine) CLRBIT(dis, v->owner); + if (v->type == VEH_Train && IsFrontEngine(v)) CLRBIT(dis, v->owner); } PopupMainPlayerToolbMenu(w, 310, 13, dis); } diff --git a/network_server.c b/network_server.c index cbfa0c19e5..2dd20ffc52 100644 --- a/network_server.c +++ b/network_server.c @@ -5,6 +5,7 @@ #include "string.h" #include "strings.h" #include "network_data.h" +#include "train.h" #ifdef ENABLE_NETWORK @@ -1271,7 +1272,7 @@ void NetworkPopulateCompanyInfo(void) if (v->owner < MAX_PLAYERS) switch (v->type) { case VEH_Train: - if (v->subtype == TS_Front_Engine) + if (IsFrontEngine(v)) _network_player_info[v->owner].num_vehicle[0]++; break; case VEH_Road: diff --git a/order_gui.c b/order_gui.c index adf4ab8bc8..7fd02aeebb 100644 --- a/order_gui.c +++ b/order_gui.c @@ -17,6 +17,7 @@ #include "viewport.h" #include "depot.h" #include "waypoint.h" +#include "train.h" static int OrderGetSel(const Window* w) { @@ -282,9 +283,9 @@ static bool HandleOrderVehClick(const Vehicle* v, const Vehicle* u, Window* w) { if (u->type != v->type) return false; - if (u->type == VEH_Train && u->subtype != TS_Front_Engine) { + if (u->type == VEH_Train && !IsFrontEngine(u)) { u = GetFirstVehicleInChain(u); - if (u->subtype != TS_Front_Engine) return false; + if (!IsFrontEngine(u)) return false; } // v is vehicle getting orders. Only copy/clone orders if vehicle doesn't have any orders yet diff --git a/player_gui.c b/player_gui.c index 3d97a2612e..67fa40041b 100644 --- a/player_gui.c +++ b/player_gui.c @@ -15,6 +15,7 @@ #include "economy.h" #include "network.h" #include "variables.h" +#include "train.h" #ifdef ENABLE_NETWORK #include "network_data.h" @@ -432,7 +433,7 @@ static void DrawPlayerVehiclesAmount(PlayerID player) FOR_ALL_VEHICLES(v) { if (v->owner == player) { switch (v->type) { - case VEH_Train: if (v->subtype == TS_Front_Engine) train++; break; + case VEH_Train: if (IsFrontEngine(v)) train++; break; case VEH_Road: road++; break; case VEH_Aircraft: if (v->subtype <= 2) air++; break; case VEH_Ship: ship++; break; diff --git a/saveload.c b/saveload.c index 7b3a00b80d..dfd0d6709f 100644 --- a/saveload.c +++ b/saveload.c @@ -30,7 +30,7 @@ enum { SAVEGAME_MAJOR_VERSION = 17, - SAVEGAME_MINOR_VERSION = 0, + SAVEGAME_MINOR_VERSION = 1, SAVEGAME_LOADABLE_VERSION = (SAVEGAME_MAJOR_VERSION << 8) + SAVEGAME_MINOR_VERSION }; diff --git a/station_cmd.c b/station_cmd.c index 66e8c24154..8b3fe0e215 100644 --- a/station_cmd.c +++ b/station_cmd.c @@ -26,6 +26,7 @@ #include "sprite.h" #include "depot.h" #include "pbs.h" +#include "train.h" enum { /* Max stations: 64000 (64 * 1000) */ @@ -2255,7 +2256,7 @@ static uint32 VehicleEnter_Station(Vehicle *v, TileIndex tile, int x, int y) byte dir; if (v->type == VEH_Train) { - if (IS_BYTE_INSIDE(_m[tile].m5, 0, 8) && v->subtype == TS_Front_Engine && + if (IS_BYTE_INSIDE(_m[tile].m5, 0, 8) && IsFrontEngine(v) && !IsCompatibleTrainStationTile(tile + TileOffsByDir(v->direction >> 1), tile)) { station_id = _m[tile].m2; diff --git a/train.h b/train.h new file mode 100644 index 0000000000..9cb3a78d21 --- /dev/null +++ b/train.h @@ -0,0 +1,208 @@ +/* $Id$ */ + +#ifndef TRAIN_H +#define TRAIN_H + +#include "stdafx.h" +#include "vehicle.h" + + +/* + * enum to handle train subtypes + * Do not access it directly unless you have to. Use the access functions below + * This is an enum to tell what bit to access as it is a bitmask + */ + +typedef enum TrainSubtypes { + Train_Front = 0, // Leading engine of a train + Train_Articulated_Part = 1, // Articulated part of an engine + Train_Wagon = 2, // Wagon + Train_Engine = 3, // Engine, that can be front engines, but might be placed behind another engine + Train_Free_Wagon = 4, // First in a wagon chain (in depot) + Train_Multiheaded = 5, // Engine is a multiheaded +} TrainSubtype; + + +/** Check if a vehicle is front engine + * @param v vehicle to check + * @return Returns true if vehicle is a front engine + */ +static inline bool IsFrontEngine(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Front); +} + +/** Set front engine state + * @param v vehicle to change + */ +static inline void SetFrontEngine(Vehicle *v) +{ + SETBIT(v->subtype, Train_Front); +} + +/** Remove the front engine state + * @param v vehicle to change + */ +static inline void ClearFrontEngine(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Front); +} + +/** Check if a vehicle is an articulated part of an engine + * @param v vehicle to check + * @return Returns true if vehicle is an articulated part + */ +static inline bool IsArticulatedPart(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Articulated_Part); +} + +/** Set a vehicle to be an articulated part + * @param v vehicle to change + */ +static inline void SetArticulatedPart(Vehicle *v) +{ + SETBIT(v->subtype, Train_Articulated_Part); +} + +/** Clear a vehicle from being an articulated part + * @param v vehicle to change + */ +static inline void ClearArticulatedPart(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Articulated_Part); +} + +/** Check if a vehicle is a wagon + * @param v vehicle to check + * @return Returns true if vehicle is a wagon + */ +static inline bool IsTrainWagon(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Wagon); +} + +/** Set a vehicle to be a wagon + * @param v vehicle to change + */ +static inline void SetTrainWagon(Vehicle *v) +{ + SETBIT(v->subtype, Train_Wagon); +} + +/** Clear wagon property + * @param v vehicle to change + */ +static inline void ClearTrainWagon(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Wagon); +} + +/** Check if a vehicle is an engine (can be first in a train) + * @param v vehicle to check + * @return Returns true if vehicle is an engine + */ +static inline bool IsTrainEngine(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Engine); +} + +/** Set engine status + * @param v vehicle to change + */ +static inline void SetTrainEngine(Vehicle *v) +{ + SETBIT(v->subtype, Train_Engine); +} + +/** Clear engine status + * @param v vehicle to change + */ +static inline void ClearTrainEngine(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Engine); +} + +/** Check if a vehicle is a free wagon (got no engine in front of it) + * @param v vehicle to check + * @return Returns true if vehicle is a free wagon + */ +static inline bool IsFreeWagon(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Free_Wagon); +} + +/** Set if a vehicle is a free wagon + * @param v vehicle to change + */ +static inline void SetFreeWagon(Vehicle *v) +{ + SETBIT(v->subtype, Train_Free_Wagon); +} + +/** Clear a vehicle from being a free wagon + * @param v vehicle to change + */ +static inline void ClearFreeWagon(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Free_Wagon); +} + +/** Check if a vehicle is a multiheaded engine + * @param v vehicle to check + * @return Returns true if vehicle is a multiheaded engine + */ +static inline bool IsMultiheaded(const Vehicle *v) +{ + return HASBIT(v->subtype, Train_Multiheaded); +} + +/** Set if a vehicle is a multiheaded engine + * @param v vehicle to change + */ +static inline void SetMultiheaded(Vehicle *v) +{ + SETBIT(v->subtype, Train_Multiheaded); +} + +/** Clear multiheaded engine property + * @param v vehicle to change + */ +static inline void ClearMultiheaded(Vehicle *v) +{ + CLRBIT(v->subtype, Train_Multiheaded); +} + +/** 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 && IsArticulatedPart(u)) { + 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 && IsArticulatedPart(v->next)); +} + +/** 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; +} + +#endif /* TRAIN_H */ diff --git a/train_cmd.c b/train_cmd.c index f933984e73..116600dc4d 100644 --- a/train_cmd.c +++ b/train_cmd.c @@ -22,9 +22,7 @@ #include "debug.h" #include "waypoint.h" #include "vehicle_gui.h" - -#define IS_FIRSTHEAD_SPRITE(spritenum) \ - (is_custom_sprite(spritenum) ? IS_CUSTOM_FIRSTHEAD_SPRITE(spritenum) : _engine_sprite_add[spritenum] == 0) +#include "train.h" static bool TrainCheckIfLineEnds(Vehicle *v); static void TrainController(Vehicle *v); @@ -50,7 +48,7 @@ void TrainCargoChanged(Vehicle* v) vweight += (_cargoc.weights[u->cargo_type] * u->cargo_count) / 16; // Vehicle weight is not added for articulated parts. - if (u->subtype != TS_Artic_Part) { + if (!IsArticulatedPart(u)) { // vehicle weight is the sum of the weight of the vehicle and the weight of its cargo vweight += rvi->weight; @@ -86,10 +84,10 @@ void TrainConsistChanged(Vehicle* v) assert(v->type == VEH_Train); - assert(v->subtype == TS_Front_Engine || v->subtype == TS_Free_Car); + assert(IsFrontEngine(v) || IsFreeWagon(v)); rvi_v = RailVehInfo(v->engine_type); - first_engine = (v->subtype == TS_Front_Engine) ? v->engine_type : INVALID_VEHICLE; + first_engine = IsFrontEngine(v) ? v->engine_type : INVALID_VEHICLE; v->u.rail.cached_total_length = 0; for (u = v; u != NULL; u = u->next) { @@ -102,7 +100,7 @@ void TrainConsistChanged(Vehicle* v) if (rvi_u->visual_effect != 0) { u->u.rail.cached_vis_effect = rvi_u->visual_effect; } else { - if (rvi_u->flags & RVI_WAGON || u->subtype == TS_Artic_Part) { + if (IsTrainWagon(u) || IsArticulatedPart(u)) { // Wagons and articulated parts have no effect by default u->u.rail.cached_vis_effect = 0x40; } else if (rvi_u->engclass == 0) { @@ -114,7 +112,7 @@ void TrainConsistChanged(Vehicle* v) } } - if (u->subtype != TS_Artic_Part) { + if (!IsArticulatedPart(u)) { // power is the sum of the powers of all engines and powered wagons in the consist power += rvi_u->power; @@ -277,7 +275,7 @@ static int GetTrainAcceleration(Vehicle *v, bool mode) max_speed += (max_speed / 2) * v->u.rail.railtype; - if (IsTileType(v->tile, MP_STATION) && v->subtype == TS_Front_Engine) { + if (IsTileType(v->tile, MP_STATION) && IsFrontEngine(v)) { if (TrainShouldStop(v, v->tile)) { int station_length = 0; TileIndex tile = v->tile; @@ -361,7 +359,7 @@ void UpdateTrainAcceleration(Vehicle *v) uint power = 0; uint weight = 0; - assert(v->subtype == TS_Front_Engine); + assert(IsFrontEngine(v)); weight = v->u.rail.cached_weight; power = v->u.rail.cached_power; @@ -491,7 +489,8 @@ static void AddArticulatedParts(const RailVehicleInfo *rvi, Vehicle **vl) u->engine_type = engine_type; u->value = 0; u->type = VEH_Train; - u->subtype = TS_Artic_Part; + u->subtype = 0; + SetArticulatedPart(u); u->cur_image = 0xAC2; VehiclePositionChanged(u); @@ -531,7 +530,7 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) FOR_ALL_VEHICLES(w) { if (w->type == VEH_Train && w->tile == tile && - w->subtype == TS_Free_Car && w->engine_type == engine) { + IsFreeWagon(w) && w->engine_type == engine) { u = GetLastVehicleInChain(w); break; } @@ -555,10 +554,12 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) v->u.rail.track = 0x80; v->vehstatus = VS_HIDDEN | VS_DEFPAL; - v->subtype = TS_Free_Car; + v->subtype = 0; + SetTrainWagon(v); if (u != NULL) { u->next = v; - v->subtype = TS_Not_First; + } else { + SetFreeWagon(v); } v->cargo_type = rvi->cargo_type; @@ -593,11 +594,11 @@ static void NormalizeTrainVehInDepot(const Vehicle* u) const Vehicle* v; FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Free_Car && + if (v->type == VEH_Train && IsFreeWagon(v) && v->tile == u->tile && v->u.rail.track == 0x80) { - if (DoCommandByTile(0, v->index | (u->index << 16), 1, DC_EXEC, - CMD_MOVE_RAIL_VEHICLE) == CMD_ERROR) + if (CmdFailed(DoCommandByTile(0, v->index | (u->index << 16), 1, DC_EXEC, + CMD_MOVE_RAIL_VEHICLE))) break; } } @@ -638,7 +639,8 @@ void AddRearEngineToMultiheadedTrain(Vehicle *v, Vehicle *u, bool building) u->z_height = 6; u->u.rail.track = 0x80; u->vehstatus = v->vehstatus & ~VS_STOPPED; - u->subtype = TS_Not_First; + u->subtype = 0; + SetMultiheaded(u); u->spritenum = v->spritenum + 1; u->cargo_type = v->cargo_type; u->cargo_cap = v->cargo_cap; @@ -750,13 +752,24 @@ int32 CmdBuildRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) v->type = VEH_Train; v->cur_image = 0xAC2; + v->subtype = 0; + SetFrontEngine(v); + SetTrainEngine(v); + v->u.rail.shortest_platform[0] = 255; v->u.rail.shortest_platform[1] = 0; VehiclePositionChanged(v); if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) { + SetMultiheaded(v); AddRearEngineToMultiheadedTrain(vl[0], vl[1], true); + /* Now we need to link the front and rear engines together + * other_multiheaded_part is the pointer that links to the other half of the engine + * vl[0] is the front and vl[1] is the rear + */ + vl[0]->u.rail.other_multiheaded_part = vl[1]; + vl[1]->u.rail.other_multiheaded_part = vl[0]; } else { AddArticulatedParts(rvi, vl); } @@ -800,7 +813,7 @@ int CheckTrainStoppedInDepot(const Vehicle *v) for (; v != NULL; v = v->next) { count++; if (v->u.rail.track != 0x80 || v->tile != tile || - (v->subtype == TS_Front_Engine && !(v->vehstatus & VS_STOPPED))) { + (IsFrontEngine(v) && !(v->vehstatus & VS_STOPPED))) { _error_message = STR_881A_TRAINS_CAN_ONLY_BE_ALTERED; return -1; } @@ -824,7 +837,8 @@ static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first) v = GetNextVehicle(v); if (v == NULL) return NULL; - v->subtype = TS_Free_Car; + if (IsTrainWagon(v)) SetFreeWagon(v); + return v; } @@ -840,7 +854,7 @@ static Vehicle *FindGoodVehiclePos(const Vehicle *src) TileIndex tile = src->tile; FOR_ALL_VEHICLES(dst) { - if (dst->type == VEH_Train && dst->subtype == TS_Free_Car && + if (dst->type == VEH_Train && IsFreeWagon(dst) && dst->tile == tile) { // check so all vehicles in the line have the same engine. Vehicle *v = dst; @@ -855,6 +869,45 @@ static Vehicle *FindGoodVehiclePos(const Vehicle *src) return NULL; } +/* + * add a vehicle v behind vehicle dest + * use this function since it sets flags as needed + */ +static void AddWagonToConsist(Vehicle *v, Vehicle *dest) +{ + UnlinkWagon(v, GetFirstVehicleInChain(v)); + if (dest == NULL) return; + + v->next = dest->next; + dest->next = v; + ClearFreeWagon(v); + ClearFrontEngine(v); +} + +/* + * move around on the train so rear engines are placed correctly according to the other engines + * always call with the front engine + */ +static void NormaliseTrainConsist(Vehicle *v) +{ + Vehicle *u; + + if (IsFreeWagon(v)) return; + + assert(IsFrontEngine(v)); + + for(; v != NULL; v = GetNextVehicle(v)) { + if (!IsMultiheaded(v) || !IsTrainEngine(v)) continue; + + /* make sure that there are no free cars before next engine */ + for(u = v; u->next != NULL && !IsTrainEngine(u->next); u = u->next); + + if (u == v->u.rail.other_multiheaded_part) continue; + AddWagonToConsist(v->u.rail.other_multiheaded_part, u); + + } +} + /** Move a rail vehicle around inside the depot. * @param x,y unused * @param p1 various bitstuffed elements @@ -867,7 +920,6 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) VehicleID s = GB(p1, 0, 16); VehicleID d = GB(p1, 16, 16); Vehicle *src, *dst, *src_head, *dst_head; - bool is_loco; if (!IsVehicleIndex(s)) return CMD_ERROR; @@ -875,20 +927,18 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) if (src->type != VEH_Train) return CMD_ERROR; - is_loco = !(RailVehInfo(src->engine_type)->flags & RVI_WAGON) && IS_FIRSTHEAD_SPRITE(src->spritenum); - // if nothing is selected as destination, try and find a matching vehicle to drag to. if (d == INVALID_VEHICLE) { dst = NULL; - if (!is_loco) dst = FindGoodVehiclePos(src); + if (!IsTrainEngine(src)) dst = FindGoodVehiclePos(src); } else { dst = GetVehicle(d); } // if an articulated part is being handled, deal with its parent vehicle - while (src->subtype == TS_Artic_Part) src = GetPrevVehicleInChain(src); + while (IsArticulatedPart(src)) src = GetPrevVehicleInChain(src); if (dst != NULL) { - while (dst->subtype == TS_Artic_Part) dst = GetPrevVehicleInChain(dst); + while (IsArticulatedPart(dst)) dst = GetPrevVehicleInChain(dst); } // don't move the same vehicle.. @@ -907,14 +957,42 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) dst = GetLastEnginePart(dst); } - /* clear the ->first cache */ - { - Vehicle *u; - - for (u = src_head; u != NULL; u = u->next) u->first = NULL; - for (u = dst_head; u != NULL; u = u->next) u->first = NULL; + if (dst != NULL && IsMultiheaded(dst) && !IsTrainEngine(dst) && IsTrainWagon(src)) { + /* We are moving a wagon to the rear part of a multiheaded engine */ + if (dst->next == NULL) { + /* It's the last one, so we will add the wagon just before the rear engine */ + dst = GetPrevVehicleInChain(dst); + // if dst is NULL, it means that dst got a rear multiheaded engine as first engine. We can't use that + if (dst == NULL) return CMD_ERROR; + } else { + /* there are more units on this train, so we will add the wagon after the next one*/ + dst = dst->next; + } } + if (IsTrainEngine(src) && dst_head != NULL) { + /* we need to make sure that we didn't place it between a pair of multiheaded engines */ + Vehicle *u, *engine = NULL; + + for(u = dst_head; u != NULL; u = u->next) { + if (IsTrainEngine(u) && IsMultiheaded(u) && u->u.rail.other_multiheaded_part != NULL) { + engine = u; + } + if (engine != NULL && engine->u.rail.other_multiheaded_part == u) { + engine = NULL; + } + if (u == dst) { + if (engine != NULL) { + dst = engine->u.rail.other_multiheaded_part; + } + break; + } + + } + } + + if (IsMultiheaded(src) && !IsTrainEngine(src)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); + /* check if all vehicles in the source train are stopped inside a depot */ if (CheckTrainStoppedInDepot(src_head) < 0) return CMD_ERROR; @@ -924,18 +1002,9 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) int num = CheckTrainStoppedInDepot(dst_head); if (num < 0) return CMD_ERROR; - if (num > (_patches.mammoth_trains ? 100 : 9) && dst_head->subtype == TS_Front_Engine ) + if (num > (_patches.mammoth_trains ? 100 : 9) && IsFrontEngine(dst_head)) return_cmd_error(STR_8819_TRAIN_TOO_LONG); - // if it's a multiheaded vehicle we're dragging to, drag to the vehicle before.. - while (IS_CUSTOM_SECONDHEAD_SPRITE(dst->spritenum) || ( - !is_custom_sprite(dst->spritenum) && _engine_sprite_add[dst->spritenum] != 0) - ) { - Vehicle *v = GetPrevVehicleInChain(dst); - if (v == NULL || src == v) break; - dst = v; - } - assert(dst_head->tile == src_head->tile); } @@ -943,7 +1012,7 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) if (HASBIT(p2, 0) && src_head == dst_head) return 0; // moving a loco to a new line?, then we need to assign a unitnumber. - if (dst == NULL && src->subtype != TS_Front_Engine && is_loco) { + if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) { UnitID unit_num = GetFreeUnitNumber(VEH_Train); if (unit_num > _patches.max_trains) return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); @@ -955,8 +1024,13 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* do it? */ if (flags & DC_EXEC) { - Vehicle *new_front = GetNextVehicle(src); //used if next in line should make a train on it's own - bool make_new_front = src->subtype == TS_Front_Engine; + /* clear the ->first cache */ + { + Vehicle *u; + + for (u = src_head; u != NULL; u = u->next) u->first = NULL; + for (u = dst_head; u != NULL; u = u->next) u->first = NULL; + } if (HASBIT(p2, 0)) { // unlink ALL wagons @@ -977,26 +1051,27 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) } if (dst == NULL) { - // move the train to an empty line. for locomotives, we set the type to 0. for wagons, 4. - if (is_loco) { - if (src->subtype != TS_Front_Engine) { + // move the train to an empty line. for locomotives, we set the type to TS_Front. for wagons, 4. + if (IsTrainEngine(src)) { + if (!IsFrontEngine(src)) { // setting the type to 0 also involves setting up the orders field. - src->subtype = TS_Front_Engine; + SetFrontEngine(src); assert(src->orders == NULL); src->num_orders = 0; } } else { - src->subtype = TS_Free_Car; + SetFreeWagon(src); } dst_head = src; } else { - if (src->subtype == TS_Front_Engine) { + if (IsFrontEngine(src)) { // the vehicle was previously a loco. need to free the order list and delete vehicle windows etc. DeleteWindowById(WC_VEHICLE_VIEW, src->index); DeleteVehicleOrders(src); } - src->subtype = TS_Not_First; + ClearFrontEngine(src); + ClearFreeWagon(src); src->unitnumber = 0; // doesn't occupy a unitnumber anymore. // link in the wagon(s) in the chain. @@ -1008,19 +1083,45 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) } dst->next = src; } + if (src->u.rail.other_multiheaded_part != NULL) { + if (src->u.rail.other_multiheaded_part == src_head) { + src_head = src_head->next; + } + AddWagonToConsist(src->u.rail.other_multiheaded_part, src); + } - /* If there is an engine behind first_engine we moved away, it should become new first_engine - * To do this, CmdMoveRailVehicle must be called once more - * since we set p2 to a condition that makes the statement false, we can't loop forever with this one */ - if (make_new_front && new_front != NULL && !(HASBIT(p2, 0))) { - if (!(RailVehInfo(new_front->engine_type)->flags & RVI_WAGON)) { - CmdMoveRailVehicle(x, y, flags, new_front->index | (INVALID_VEHICLE << 16), 1); + if (HASBIT(p2, 0) && src_head != NULL && src_head != src) { + /* if we stole a rear multiheaded engine, we better give it back to the front end */ + Vehicle *engine = NULL, *u; + for (u = src_head; u != NULL; u = u->next) { + if (IsMultiheaded(u)) { + if (IsTrainEngine(u)) { + engine = u; + continue; + } + /* we got the rear engine to match with the front one */ + engine = NULL; + } + } + if (engine != NULL && engine->u.rail.other_multiheaded_part != NULL) { + AddWagonToConsist(engine->u.rail.other_multiheaded_part, engine); + // previous line set the front engine to the old front. We need to clear that + engine->u.rail.other_multiheaded_part->first = NULL; } } + /* If there is an engine behind first_engine we moved away, it should become new first_engine + * To do this, CmdMoveRailVehicle must be called once more + * we can't loop forever here because next time we reach this line we will have a front engine */ + if (src_head != NULL && !IsFrontEngine(src_head) && IsTrainEngine(src_head)) { + CmdMoveRailVehicle(x, y, flags, src_head->index | (INVALID_VEHICLE << 16), 1); + src_head = NULL; // don't do anything more to this train since the new call will do it + } + if (src_head) { + NormaliseTrainConsist(src_head); TrainConsistChanged(src_head); - if (src_head->subtype == TS_Front_Engine) { + if (IsFrontEngine(src_head)) { UpdateTrainAcceleration(src_head); InvalidateWindow(WC_VEHICLE_DETAILS, src_head->index); /* Update the refit button and window */ @@ -1032,8 +1133,9 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) }; if (dst_head) { + NormaliseTrainConsist(dst_head); TrainConsistChanged(dst_head); - if (dst_head->subtype == TS_Front_Engine) { + if (IsFrontEngine(dst_head)) { UpdateTrainAcceleration(dst_head); InvalidateWindow(WC_VEHICLE_DETAILS, dst_head->index); /* Update the refit button and window */ @@ -1074,27 +1176,6 @@ int32 CmdStartStopTrain(int x, int y, uint32 flags, uint32 p1, uint32 p2) return 0; } -/** - * Search for a matching rear-engine of a dual-headed train. - * Do this as if you would find matching parentheses. If a new - * engine is 'started', first 'close' that before 'closing' our - * searched engine - */ -Vehicle* GetRearEngine(const Vehicle* v) -{ - Vehicle *u; - int en_count = 1; - - for (u = v->next; u != NULL; u = u->next) { - if (u->engine_type == v->engine_type) { // find matching engine - en_count += (IS_FIRSTHEAD_SPRITE(u->spritenum)) ? +1 : -1; - - if (en_count == 0) return (Vehicle *)u; - } - } - return NULL; -} - /** Sell a (single) train wagon/engine. * @param x,y unused * @param p1 the wagon/engine index @@ -1108,6 +1189,7 @@ Vehicle* GetRearEngine(const Vehicle* v) int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) { Vehicle *v, *tmp, *first; + Vehicle *new_f = NULL; int32 cost = 0; if (!IsVehicleIndex(p1) || p2 > 2) return CMD_ERROR; @@ -1118,14 +1200,16 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); - while (v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v); + while (IsArticulatedPart(v)) v = GetPrevVehicleInChain(v); first = GetFirstVehicleInChain(v); // make sure the vehicle is stopped in the depot if (CheckTrainStoppedInDepot(first) < 0) return CMD_ERROR; + if (IsMultiheaded(v) && !IsTrainEngine(v)) return_cmd_error(STR_REAR_ENGINE_FOLLOW_FRONT_ERROR); + if (flags & DC_EXEC) { - if (v == first && first->subtype == TS_Front_Engine) { + if (v == first && IsFrontEngine(first)) { DeleteWindowById(WC_VEHICLE_VIEW, first->index); } if (IsLocalPlayer() && (p1 == 1 || !(RailVehInfo(v->engine_type)->flags & RVI_WAGON))) { @@ -1141,20 +1225,22 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) byte ori_subtype = v->subtype; // backup subtype of deleted wagon in case DeleteVehicle() changes /* 1. Delete the engine, if it is dualheaded also delete the matching - * rear engine of the loco (from the point of deletion onwards) */ - Vehicle* rear = (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) ? GetRearEngine(v) : NULL; + * rear engine of the loco (from the point of deletion onwards) */ + Vehicle *rear = (IsMultiheaded(v) && + IsTrainEngine(v)) ? v->u.rail.other_multiheaded_part : NULL; + if (rear != NULL) { - cost -= v->value; + cost -= rear->value; if (flags & DC_EXEC) { - v = UnlinkWagon(rear, v); + UnlinkWagon(rear, first); DeleteVehicle(rear); } } /* 2. We are selling the first engine, some special action might be required - * here, so take attention */ + * here, so take attention */ if ((flags & DC_EXEC) && v == first) { - Vehicle *new_f = GetNextVehicle(first); + 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; @@ -1163,7 +1249,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) * if the second wagon (which will be first) is an engine. If it is one, * promote it as a new train, retaining the unitnumber, orders */ if (new_f != NULL) { - if (!(RailVehInfo(new_f->engine_type)->flags & RVI_WAGON) && IS_FIRSTHEAD_SPRITE(new_f->spritenum)) { + if (IsTrainEngine(new_f)) { switch_engine = true; /* Copy important data from the front engine */ new_f->unitnumber = first->unitnumber; @@ -1185,12 +1271,13 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* 4 If the second wagon was an engine, update it to front_engine * which UnlinkWagon() has changed to TS_Free_Car */ - if (switch_engine) first->subtype = TS_Front_Engine; + if (switch_engine) SetFrontEngine(first); /* 5. If the train still exists, update its acceleration, window, etc. */ if (first != NULL) { + NormaliseTrainConsist(first); TrainConsistChanged(first); - if (first->subtype == TS_Front_Engine) { + if (IsFrontEngine(first)) { InvalidateWindow(WC_VEHICLE_DETAILS, first->index); InvalidateWindow(WC_VEHICLE_REFIT, first->index); UpdateTrainAcceleration(first); @@ -1199,10 +1286,10 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* (6.) Borked AI. If it sells an engine it expects all wagons lined - * up on a new line to be added to the newly built loco. Replace it is. - * Totally braindead cause building a new engine adds all loco-less - * engines to its train anyways */ - if (p2 == 2 && ori_subtype == TS_Front_Engine) { + * up on a new line to be added to the newly built loco. Replace it is. + * Totally braindead cause building a new engine adds all loco-less + * engines to its train anyways */ + if (p2 == 2 && HASBIT(ori_subtype, Train_Front)) { for (v = first; v != NULL; v = tmp) { tmp = GetNextVehicle(v); DoCommandByTile(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); @@ -1211,31 +1298,26 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) } } break; case 1: { /* Delete wagon and all wagons after it given certain criteria */ - /* 1. Count the number for first and rear engines for dualheads - * 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 = GetNextVehicle(tmp)) { - if (RailVehInfo(tmp->engine_type)->flags & RVI_MULTIHEAD) - (IS_FIRSTHEAD_SPRITE(tmp->spritenum)) ? enf_count++ : enr_count++; - } - - /* 2. Start deleting every vehicle after the selected one - * If we encounter a matching rear-engine to a front-engine - * earlier in the chain (before deletion), leave it alone */ + /* Start deleting every vehicle after the selected one + * 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 = GetNextVehicle(v); - if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) { - if (IS_FIRSTHEAD_SPRITE(v->spritenum)) { - /* Always delete newly encountered front-engines */ - enf_count--; - } else if (enr_count > enf_count) { - /* More rear engines than front engines means this rear-engine does - * not belong to any front-engine; delete */ - enr_count--; - } else { - /* Otherwise leave it alone */ + if (IsMultiheaded(v)) { + if (IsTrainEngine(v)) { + /* We got a front engine of a multiheaded set. Now we will sell the rear end too */ + Vehicle *rear = v->u.rail.other_multiheaded_part; + + if (rear != NULL) { + cost -= rear->value; + if (flags & DC_EXEC) { + first = UnlinkWagon(rear, first); + DeleteVehicle(rear); + } + } + } else if (v->u.rail.other_multiheaded_part != NULL) { + /* The front to this engine is earlier in this train. Do nothing */ continue; } } @@ -1249,9 +1331,12 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* 3. If it is still a valid train after selling, update its acceleration and cached values */ if ((flags & DC_EXEC) && first != NULL) { + NormaliseTrainConsist(first); TrainConsistChanged(first); - if (first->subtype == TS_Front_Engine) + if (IsFrontEngine(first)) UpdateTrainAcceleration(first); + InvalidateWindow(WC_VEHICLE_DETAILS, first->index); + InvalidateWindow(WC_VEHICLE_REFIT, first->index); } } break; } @@ -2620,7 +2705,7 @@ static bool CheckCompatibleRail(const Vehicle *v, TileIndex tile) // tracks over roads, do owner check of tracks return IsTileOwner(tile, v->owner) && ( - v->subtype != TS_Front_Engine || + !IsFrontEngine(v) || IsCompatibleRail(v->u.rail.railtype, GB(_m[tile].m4, 0, 4)) ); @@ -2630,7 +2715,7 @@ static bool CheckCompatibleRail(const Vehicle *v, TileIndex tile) return IsTileOwner(tile, v->owner) && ( - v->subtype != TS_Front_Engine || + !IsFrontEngine(v) || IsCompatibleRail(v->u.rail.railtype, GetRailType(tile)) ); } @@ -2741,7 +2826,7 @@ static uint CountPassengersInTrain(const Vehicle* v) /* * Checks whether the specified train has a collision with another vehicle. If - * so, destroys this vehicle, and the other vehicle if its subtype is 0 (TS_Front_Engine). + * so, destroys this vehicle, and the other vehicle if its subtype has TS_Front. * Reports the incident in a flashy news item, modifies station ratings and * plays a sound. */ @@ -2778,7 +2863,7 @@ static void CheckTrainCollision(Vehicle *v) num += 2 + CountPassengersInTrain(coll); SetVehicleCrashed(v); - if (coll->subtype == TS_Front_Engine) SetVehicleCrashed(coll); + if (IsFrontEngine(coll)) SetVehicleCrashed(coll); SetDParam(0, num); AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, @@ -2800,7 +2885,7 @@ static void *CheckVehicleAtSignal(Vehicle *v, void *data) { const VehicleAtSignalData* vasd = data; - if (v->type == VEH_Train && v->subtype == TS_Front_Engine && + if (v->type == VEH_Train && IsFrontEngine(v) && v->tile == vasd->tile) { byte diff = (v->direction - vasd->direction + 2) & 7; @@ -2970,7 +3055,7 @@ green_light: goto invalid_rail; } - if (v->subtype == TS_Front_Engine) v->load_unload_time_rem = 0; + if (IsFrontEngine(v)) v->load_unload_time_rem = 0; if (!(r&0x4)) { v->tile = gp.new_tile; @@ -2978,7 +3063,7 @@ green_light: assert(v->u.rail.track); } - if (v->subtype == TS_Front_Engine) + if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir); /* Signals can only change when the first @@ -3433,13 +3518,13 @@ void Train_Tick(Vehicle *v) v->tick_counter++; - if (v->subtype == TS_Front_Engine) { + if (IsFrontEngine(v)) { TrainLocoHandler(v, false); // make sure vehicle wasn't deleted. - if (v->type == VEH_Train && v->subtype == TS_Front_Engine) + if (v->type == VEH_Train && IsFrontEngine(v)) TrainLocoHandler(v, true); - } else if (v->subtype == TS_Free_Car && HASBITS(v->vehstatus, VS_CRASHED)) { + } else if (IsFreeWagon(v) && HASBITS(v->vehstatus, VS_CRASHED)) { // Delete flooded standalone wagon if (++v->u.rail.crash_anim_pos >= 4400) DeleteVehicle(v); @@ -3460,7 +3545,7 @@ void TrainEnterDepot(Vehicle *v, TileIndex tile) { SetSignalsOnBothDir(tile, _depot_track_ind[GB(_m[tile].m5, 0, 2)]); - if (v->subtype != TS_Front_Engine) v = GetFirstVehicleInChain(v); + if (!IsFrontEngine(v)) v = GetFirstVehicleInChain(v); VehicleServiceInDepot(v); @@ -3565,7 +3650,7 @@ void OnNewDay_Train(Vehicle *v) if ((++v->day_counter & 7) == 0) DecreaseVehicleValue(v); - if (v->subtype == TS_Front_Engine) { + if (IsFrontEngine(v)) { CheckVehicleBreakdown(v); AgeVehicle(v); @@ -3610,7 +3695,7 @@ void TrainsYearlyLoop(void) Vehicle *v; FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Front_Engine) { + if (v->type == VEH_Train && IsFrontEngine(v)) { // show warning if train is not generating enough income last 2 years (corresponds to a red icon in the vehicle list) if (_patches.train_income_warn && v->owner == _local_player && v->age >= 730 && v->profit_this_year < 0) { diff --git a/train_gui.c b/train_gui.c index 35b66b9563..6980d40915 100644 --- a/train_gui.c +++ b/train_gui.c @@ -18,6 +18,7 @@ #include "engine.h" #include "vehicle_gui.h" #include "depot.h" +#include "train.h" int _traininfo_vehicle_pitch = 0; @@ -128,7 +129,7 @@ void CcBuildWagon(bool success, TileIndex tile, uint32 p1, uint32 p2) // find a locomotive in the depot. found = NULL; FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Front_Engine && + if (v->type == VEH_Train && IsFrontEngine(v) && v->tile == tile && v->u.rail.track == 0x80) { if (found != NULL) // must be exactly one. @@ -392,12 +393,12 @@ static void DrawTrainDepotWindow(Window *w) hnum = 8; FOR_ALL_VEHICLES(v) { if (v->type == VEH_Train && - (v->subtype == TS_Front_Engine || v->subtype == TS_Free_Car) && + (IsFrontEngine(v) || IsFreeWagon(v)) && v->tile == tile && v->u.rail.track == 0x80) { num++; // determine number of items in the X direction. - if (v->subtype == TS_Front_Engine) { + if (IsFrontEngine(v)) { hnum = max(hnum, v->u.rail.cached_total_length); } } @@ -422,7 +423,7 @@ static void DrawTrainDepotWindow(Window *w) // draw all trains FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Front_Engine && + if (v->type == VEH_Train && IsFrontEngine(v) && v->tile == tile && v->u.rail.track == 0x80 && --num < 0 && num >= -w->vscroll.cap) { DrawTrainImage(v, x+21, y, w->hscroll.cap, w->hscroll.pos, WP(w,traindepot_d).sel); @@ -443,7 +444,7 @@ static void DrawTrainDepotWindow(Window *w) // draw all remaining vehicles FOR_ALL_VEHICLES(v) { - if (v->type == VEH_Train && v->subtype == TS_Free_Car && + if (v->type == VEH_Train && IsFreeWagon(v) && v->tile == tile && v->u.rail.track == 0x80 && --num < 0 && num >= -w->vscroll.cap) { DrawTrainImage(v, x+50, y, w->hscroll.cap - 1, 0, WP(w,traindepot_d).sel); @@ -482,7 +483,7 @@ static int GetVehicleFromTrainDepotWndPt(const Window *w, int x, int y, GetDepot /* go through all the locomotives */ FOR_ALL_VEHICLES(v) { if (v->type == VEH_Train && - v->subtype == TS_Front_Engine && + IsFrontEngine(v) && v->tile == w->window_number && v->u.rail.track == 0x80 && --row < 0) { @@ -496,7 +497,7 @@ static int GetVehicleFromTrainDepotWndPt(const Window *w, int x, int y, GetDepot /* and then the list of free wagons */ FOR_ALL_VEHICLES(v) { if (v->type == VEH_Train && - v->subtype == TS_Free_Car && + IsFreeWagon(v) && v->tile == w->window_number && v->u.rail.track == 0x80 && --row < 0) @@ -513,7 +514,7 @@ found_it: d->head = d->wagon = v; /* either pressed the flag or the number, but only when it's a loco */ - if (x < 0 && v->subtype == TS_Front_Engine) + if (x < 0 && IsFrontEngine(v)) return (x >= -10) ? -2 : -1; // skip vehicles that are scrolled off the left side @@ -526,7 +527,7 @@ found_it: } // if an articulated part was selected, find its parent - while (v != NULL && v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v); + while (v != NULL && IsArticulatedPart(v)) v = GetPrevVehicleInChain(v); d->wagon = v; @@ -539,7 +540,7 @@ static void TrainDepotMoveVehicle(Vehicle *wagon, VehicleID sel, Vehicle *head) v = GetVehicle(sel); - if (/*v->subtype == TS_Front_Engine ||*/ v == wagon) + if (v == wagon) return; if (wagon == NULL) { @@ -605,10 +606,10 @@ static void HandleCloneVehClick(const Vehicle* v, const Window* w) if (v == NULL || v->type != VEH_Train) return; // for train vehicles: subtype 0 for locs and not zero for others - if (v->subtype != TS_Front_Engine) { + if (!IsFrontEngine(v)) { v = GetFirstVehicleInChain(v); // Do nothing when clicking on a train in depot with no loc attached - if (v->subtype != TS_Front_Engine) return; + if (!IsFrontEngine(v)) return; } DoCommandP(w->window_number, v->index, _ctrl_pressed ? 1 : 0, CcCloneTrain, @@ -707,7 +708,7 @@ static void TrainDepotWndProc(Window *w, WindowEvent *e) sell_cmd = (e->click.widget == 5 || _ctrl_pressed) ? 1 : 0; - if (v->subtype != TS_Front_Engine) { + if (!IsFrontEngine(v)) { DoCommandP(v->tile, v->index, sell_cmd, NULL, CMD_SELL_RAIL_WAGON | CMD_MSG(STR_8839_CAN_T_SELL_RAILROAD_VEHICLE)); } else { _backup_orders_tile = v->tile; @@ -728,7 +729,7 @@ static void TrainDepotWndProc(Window *w, WindowEvent *e) sel != INVALID_VEHICLE) { if (gdvp.wagon == NULL || gdvp.wagon->index != sel) { TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head); - } else if (gdvp.head != NULL && gdvp.head->subtype == TS_Front_Engine) { + } else if (gdvp.head != NULL && IsFrontEngine(gdvp.head)) { ShowTrainViewWindow(gdvp.head); } } @@ -1201,7 +1202,7 @@ static void DrawTrainDetailsWindow(Window *w) 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); + } while (u != NULL && IsArticulatedPart(u)); _train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + WagonLengthToPixels(dx) + 2, y + 2); y += 14; } @@ -1464,7 +1465,7 @@ static void PlayerTrainsWndProc(Window *w, WindowEvent *e) v = GetVehicle(vl->sort_list[id_v].index); - assert(v->type == VEH_Train && v->subtype == TS_Front_Engine && v->owner == owner); + assert(v->type == VEH_Train && IsFrontEngine(v) && v->owner == owner); ShowTrainViewWindow(v); } diff --git a/tunnelbridge_cmd.c b/tunnelbridge_cmd.c index 817c742cdb..7d4882fe52 100644 --- a/tunnelbridge_cmd.c +++ b/tunnelbridge_cmd.c @@ -22,6 +22,7 @@ #include "debug.h" #include "variables.h" #include "bridge.h" +#include "train.h" #include "table/bridge_land.h" @@ -1479,7 +1480,7 @@ static uint32 VehicleEnter_TunnelBridge(Vehicle *v, TileIndex tile, int x, int y vdir = v->direction >> 1; if (v->u.rail.track != 0x40 && dir == vdir) { - if (v->subtype == TS_Front_Engine && fc == _tunnel_fractcoord_1[dir]) { + if (IsFrontEngine(v) && fc == _tunnel_fractcoord_1[dir]) { if (v->spritenum < 4) SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v); return 0; @@ -1534,7 +1535,7 @@ static uint32 VehicleEnter_TunnelBridge(Vehicle *v, TileIndex tile, int x, int y } } } else if (_m[tile].m5 & 0x80) { - if (v->type == VEH_Road || (v->type == VEH_Train && v->subtype == TS_Front_Engine)) { + if (v->type == VEH_Road || (v->type == VEH_Train && IsFrontEngine(v))) { uint h; if (GetTileSlope(tile, &h) != 0) diff --git a/vehicle.c b/vehicle.c index 8fafc28e47..46a64994e9 100644 --- a/vehicle.c +++ b/vehicle.c @@ -23,6 +23,7 @@ #include "station.h" #include "gui.h" #include "rail.h" +#include "train.h" #define INVALID_COORD (-0x8000) #define GEN_HASH(x,y) (((x & 0x1F80)>>7) + ((y & 0xFC0))) @@ -238,7 +239,7 @@ void AfterLoadVehicles(void) v->left_coord = INVALID_COORD; VehiclePositionChanged(v); - if (v->type == VEH_Train && (v->subtype == TS_Front_Engine || v->subtype == TS_Free_Car)) + if (v->type == VEH_Train && (IsFrontEngine(v) || IsFreeWagon(v))) TrainConsistChanged(v); } } @@ -503,7 +504,7 @@ Vehicle *GetFirstVehicleInChain(const Vehicle *v) assert(v != NULL); if (v->first != NULL) { - if (v->first->subtype == TS_Front_Engine) return v->first; + if (IsFrontEngine(v->first)) return v->first; DEBUG(misc, 0) ("v->first cache faulty. We shouldn't be here, rebuilding cache!"); } @@ -517,7 +518,7 @@ Vehicle *GetFirstVehicleInChain(const Vehicle *v) while ((u = GetPrevVehicleInChain_bruteforce(v)) != NULL) v = u; /* Set the first pointer of all vehicles in that chain to the first wagon */ - if (v->subtype == TS_Front_Engine) + if (IsFrontEngine(v)) for (u = (Vehicle *)v; u != NULL; u = u->next) u->first = (Vehicle *)v; return (Vehicle*)v; @@ -1490,10 +1491,13 @@ static Vehicle *GetNextEnginePart(Vehicle *v) { switch (v->type) { case VEH_Train: - if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) { - return GetRearEngine(v); + if (IsMultiheaded(v)) { + if (!IsTrainEngine(v)) + return v->u.rail.other_multiheaded_part; + else + return NULL; } - if (v->next != NULL && v->next->subtype == TS_Artic_Part) return v->next; + if (v->next != NULL && IsArticulatedPart(v->next)) return v->next; break; case VEH_Aircraft: @@ -1538,7 +1542,7 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) if (!CheckOwnership(v->owner)) return CMD_ERROR; - if (v->type == VEH_Train && v->subtype != TS_Front_Engine) return CMD_ERROR; + if (v->type == VEH_Train && !IsFrontEngine(v)) return CMD_ERROR; // check that we can allocate enough vehicles if (!(flags & DC_EXEC)) { @@ -1555,7 +1559,13 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) v = v_front; do { - cost = DoCommand(x, y, v->engine_type, 3, flags, CMD_BUILD_VEH(v->type)); + + if (IsMultiheaded(v) && !IsTrainEngine(v)) { + /* we build the rear ends of multiheaded trains with the front ones */ + continue; + } + + cost = DoCommand(x, y, v->engine_type, 2, flags, CMD_BUILD_VEH(v->type)); if (CmdFailed(cost)) return cost; @@ -1570,7 +1580,7 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) } } - if (v->type == VEH_Train && v->subtype != TS_Front_Engine) { + if (v->type == VEH_Train && !IsFrontEngine(v)) { // this s a train car // add this unit to the end of the train DoCommand(x, y, (w_rear->index << 16) | w->index, 1, flags, CMD_MOVE_RAIL_VEHICLE); @@ -1583,18 +1593,9 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) } } while (v->type == VEH_Train && (v = GetNextVehicle(v)) != NULL); - if (flags & DC_EXEC) { - v = v_front; - w = w_front; - if (v->type == VEH_Train) { - _new_train_id = w_front->index; // _new_train_id needs to be the front engine due to the callback function - - while (w != NULL && v != NULL) { // checking both just in case something went wrong - w->spritenum = v->spritenum; // makes sure that multiheaded engines are facing the correct way - w = w->next; - v = v->next; - } - } + if (flags & DC_EXEC && v_front->type == VEH_Train) { + // _new_train_id needs to be the front engine due to the callback function + _new_train_id = w_front->index; } return total_cost; } @@ -1665,12 +1666,12 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags) MoveVehicleCargo(new_v, old_v); - if (old_v->type == VEH_Train && old_v->u.rail.first_engine != INVALID_VEHICLE) { + if (old_v->type == VEH_Train && !IsFrontEngine(old_v)) { /* this is a railcar. We need to move the car into the train * We add the new engine after the old one instead of replacing it. It will give the same result anyway when we * sell the old engine in a moment */ - DoCommand(0, 0, (old_v->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + DoCommand(0, 0, (GetPrevVehicleInChain(old_v)->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); } else { // copy/clone the orders DoCommand(0, 0, (old_v->index << 16) | new_v->index, IsOrderListShared(old_v) ? CO_SHARE : CO_COPY, DC_EXEC, CMD_CLONE_ORDER); @@ -1737,6 +1738,11 @@ static void MaybeReplaceVehicle(Vehicle *v) cost = 0; w = v; do { + if (w->type == VEH_Train && IsMultiheaded(w) && !IsTrainEngine(w)) { + /* we build the rear ends of multiheaded trains with the front ones */ + continue; + } + // check if the vehicle should be replaced if (!p->engine_renew || w->age - w->max_age < (p->engine_renew_months * 30) || // replace if engine is too old @@ -1745,13 +1751,6 @@ static void MaybeReplaceVehicle(Vehicle *v) continue; } - /* if we are looking at the rear end of a multiheaded locomotive, skip it */ - if (w->type == VEH_Train) { - const RailVehicleInfo *rvi = RailVehInfo(w->engine_type); - if (rvi->flags & RVI_MULTIHEAD && rvi->image_index == w->spritenum - 1) - continue; - } - /* Now replace the vehicle */ temp_cost = ReplaceVehicle(&w, flags); @@ -2109,8 +2108,9 @@ static const SaveLoad _train_desc[] = { SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,pbs_end_trackdir), SLE_UINT8, 2, 255), SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,shortest_platform[0]), SLE_UINT8, 2, 255), // added with 16.1, but was blank since 2 SLE_CONDVARX(offsetof(Vehicle,u)+offsetof(VehicleRail,shortest_platform[1]), SLE_UINT8, 2, 255), // added with 16.1, but was blank since 2 - // reserve extra space in savegame here. (currently 5 bytes) - SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 5, 2, 255), + SLE_CONDREFX(offsetof(Vehicle,u)+offsetof(VehicleRail,other_multiheaded_part), REF_VEHICLE, 2, 255), // added with 17.1, but was blank since 2 + // reserve extra space in savegame here. (currently 3 bytes) + SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 3, 2, 255), SLE_END() }; @@ -2262,6 +2262,105 @@ static void Save_VEHS(void) } } +/* + * Converts all trains to the new subtype format introduced in savegame 16.2 + * It also links multiheaded engines or make them forget they are multiheaded if no suitable partner is found + */ +static inline void ConvertOldMultiheadToNew(void) +{ + Vehicle *v; + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_Train) { + v->u.rail.other_multiheaded_part = NULL; + SETBIT(v->subtype, 7); // indicates that it's the old format and needs to be converted in the next loop + } + } + + FOR_ALL_VEHICLES(v) { + if (v->type == VEH_Train) { + if (HASBIT(v->subtype, 7) && ((v->subtype & ~0x80) == 0 || (v->subtype & ~0x80) == 4)) { + Vehicle *u = v; + + BEGIN_ENUM_WAGONS(u) + const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); + CLRBIT(u->subtype, 7); + switch (u->subtype) { + case 0: /* TS_Front_Engine */ + if (rvi->flags & RVI_MULTIHEAD) { + SetMultiheaded(u); + } + SetFrontEngine(u); + SetTrainEngine(u); + break; + case 1: /* TS_Artic_Part */ + u->subtype = 0; + SetArticulatedPart(u); + break; + case 2: /* TS_Not_First */ + u->subtype = 0; + if (rvi->flags & RVI_WAGON) { + // normal wagon + SetTrainWagon(u); + break; + } + if (rvi->flags & RVI_MULTIHEAD && rvi->image_index == u->spritenum - 1) { + // rear end of a multiheaded engine + SetMultiheaded(u); + break; + } + if (rvi->flags & RVI_MULTIHEAD) { + SetMultiheaded(u); + } + SetTrainEngine(u); + break; + case 4: /* TS_Free_Car */ + u->subtype = 0; + SetTrainWagon(u); + SetFreeWagon(u); + break; + default: NOT_REACHED(); break; + } + END_ENUM_WAGONS(u) + u = v; + BEGIN_ENUM_WAGONS(u) + const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); + + if (u->u.rail.other_multiheaded_part != NULL) continue; + + if (rvi->flags & RVI_MULTIHEAD) { + if (!IsTrainEngine(u)) { + /* we got a rear car without a front car. We will convert it to a front one */ + SetTrainEngine(u); + u->spritenum--; + } + + { + Vehicle *w; + + for(w = u->next; w != NULL && (w->engine_type != u->engine_type || w->u.rail.other_multiheaded_part != NULL); w = GetNextVehicle(w)); + if (w != NULL) { + /* we found a car to partner with this engine. Now we will make sure it face the right way */ + if (IsTrainEngine(w)) { + ClearTrainEngine(w); + w->spritenum++; + } + } + + if (w != NULL) { + w->u.rail.other_multiheaded_part = u; + u->u.rail.other_multiheaded_part = w; + } else { + /* we got a front car and no rear cars. We will fake this one for forget that it should have been multiheaded */ + ClearMultiheaded(u); + } + } + } + END_ENUM_WAGONS(u) + } + } + } +} + // Will be called when vehicles need to be loaded. static void Load_VEHS(void) { @@ -2311,6 +2410,11 @@ static void Load_VEHS(void) } } } + + /* Connect front and rear engines of multiheaded trains and converts subtype to the new format */ + if (_sl_full_version < 0x1101) { + ConvertOldMultiheadToNew(); + } } const ChunkHandler _veh_chunk_handlers[] = { diff --git a/vehicle.h b/vehicle.h index 0c0e3dce78..59955de412 100644 --- a/vehicle.h +++ b/vehicle.h @@ -27,14 +27,6 @@ enum VehStatus { VS_CRASHED = 0x80, }; -// 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; - /* Effect vehicle types */ typedef enum EffectVehicle { EV_CHIMNEY_SMOKE = 0, @@ -93,6 +85,9 @@ typedef struct VehicleRail { * skip station and alike by setting it to 0. That way we will ensure that a complete loop is used to find the shortest station */ byte shortest_platform[2]; + + // Link between the two ends of a multiheaded engine + Vehicle *other_multiheaded_part; } VehicleRail; enum { @@ -311,7 +306,6 @@ void DecreaseVehicleValue(Vehicle *v); void CheckVehicleBreakdown(Vehicle *v); void AgeVehicle(Vehicle *v); void VehicleEnteredDepotThisTick(Vehicle *v); -Vehicle* GetRearEngine(const Vehicle* v); void BeginVehicleMove(Vehicle *v); void EndVehicleMove(Vehicle *v); @@ -399,41 +393,6 @@ static inline bool IsVehicleIndex(uint index) 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) { diff --git a/vehicle_gui.c b/vehicle_gui.c index f5f9fe5f4b..21ea66c322 100644 --- a/vehicle_gui.c +++ b/vehicle_gui.c @@ -18,6 +18,7 @@ #include "variables.h" #include "vehicle_gui.h" #include "viewport.h" +#include "train.h" Sorting _sorting; @@ -105,7 +106,7 @@ void ResortVehicleLists(void) void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID station) { - uint subtype = (type != VEH_Aircraft) ? TS_Front_Engine : 2; + uint subtype = (type != VEH_Aircraft) ? Train_Front : 2; uint n = 0; uint i; @@ -122,7 +123,9 @@ void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID sta if (station != INVALID_STATION) { const Vehicle *v; FOR_ALL_VEHICLES(v) { - if (v->type == type && v->subtype <= subtype) { + if (v->type == type && ( + (type == VEH_Train && IsFrontEngine(v)) || + (type != VEH_Train && v->subtype <= subtype))) { const Order *order; FOR_VEHICLE_ORDERS(v, order) { @@ -138,7 +141,9 @@ void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID sta } else { const Vehicle *v; FOR_ALL_VEHICLES(v) { - if (v->type == type && v->subtype <= subtype && v->owner == owner) { + if (v->type == type && v->owner == owner && ( + (type == VEH_Train && IsFrontEngine(v)) || + (type != VEH_Train && v->subtype <= subtype))) { _vehicle_sort[n].index = v->index; _vehicle_sort[n].owner = v->owner; ++n; diff --git a/viewport.c b/viewport.c index c807a3965c..71737ba26e 100644 --- a/viewport.c +++ b/viewport.c @@ -19,6 +19,7 @@ #include "signs.h" #include "waypoint.h" #include "variables.h" +#include "train.h" #define VIEWPORT_DRAW_MEM (65536 * 2) @@ -1700,7 +1701,7 @@ static void CheckClickOnLandscape(const ViewPort *vp, int x, int y) static void SafeShowTrainViewWindow(const Vehicle* v) { - if (v->subtype != TS_Front_Engine) v = GetFirstVehicleInChain(v); + if (!IsFrontEngine(v)) v = GetFirstVehicleInChain(v); ShowTrainViewWindow(v); } diff --git a/water_cmd.c b/water_cmd.c index 8530e2cb7b..a942cf1ef4 100644 --- a/water_cmd.c +++ b/water_cmd.c @@ -15,6 +15,7 @@ #include "sound.h" #include "depot.h" #include "vehicle_gui.h" +#include "train.h" const SpriteID _water_shore_sprites[15] = { 0, @@ -598,7 +599,7 @@ static void FloodVehicle(Vehicle *v) v = GetFirstVehicleInChain(v); u = v; - if (v->subtype == TS_Front_Engine) pass = 4; // driver + if (IsFrontEngine(v)) pass = 4; // driver // crash all wagons, and count passangers BEGIN_ENUM_WAGONS(v)