(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
This commit is contained in:
bjarni 2005-11-18 23:41:03 +00:00
parent 40ec9bb123
commit 22a4679837
17 changed files with 615 additions and 243 deletions

View File

@ -26,6 +26,7 @@
#include "variables.h" #include "variables.h"
#include "vehicle_gui.h" #include "vehicle_gui.h"
#include "ai/ai.h" #include "ai/ai.h"
#include "train.h"
// Score info // Score info
const ScoreInfo _score_info[] = { const ScoreInfo _score_info[] = {
@ -129,7 +130,7 @@ int UpdateCompanyRatingAndValue(Player *p, bool update)
FOR_ALL_VEHICLES(v) { FOR_ALL_VEHICLES(v) {
if (v->owner != owner) if (v->owner != owner)
continue; 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_Road ||
(v->type == VEH_Aircraft && v->subtype<=2) || (v->type == VEH_Aircraft && v->subtype<=2) ||
v->type == VEH_Ship) { v->type == VEH_Ship) {
@ -313,7 +314,7 @@ void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player)
if (v->owner == new_player) { if (v->owner == new_player) {
switch (v->type) { switch (v->type) {
case VEH_Train: case VEH_Train:
if (v->subtype == TS_Front_Engine) num_train++; if (IsFrontEngine(v)) num_train++;
break; break;
case VEH_Road: case VEH_Road:
num_road++; num_road++;
@ -338,7 +339,7 @@ void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player)
DeleteVehicle(v); DeleteVehicle(v);
} else { } else {
v->owner = new_player; 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; v->unitnumber = ++num_train;
else if (v->type == VEH_Road) else if (v->type == VEH_Road)
v->unitnumber = ++num_road; v->unitnumber = ++num_road;
@ -1289,7 +1290,7 @@ static bool LoadWait(const Vehicle *v, const Vehicle *u) {
} }
FOR_ALL_VEHICLES(x) { 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 u->last_station_visited == x->last_station_visited && // at the same station
!(x->vehstatus & VS_STOPPED) && // not stopped !(x->vehstatus & VS_STOPPED) && // not stopped
x->current_order.type == OT_LOADING && // loading x->current_order.type == OT_LOADING && // loading

View File

@ -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_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_8835_CAN_T_MODIFY_THIS_ORDER :{WHITE}Can't modify this order...
STR_8837_CAN_T_MOVE_VEHICLE :{WHITE}Can't move vehicle... 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_8838_N_A :N/A{SKIP}
STR_8839_CAN_T_SELL_RAILROAD_VEHICLE :{WHITE}Can't sell railway vehicle... 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 STR_883A_UNABLE_TO_FIND_ROUTE_TO :{WHITE}Unable to find route to local depot

View File

@ -26,6 +26,7 @@
#include "signs.h" #include "signs.h"
#include "waypoint.h" #include "waypoint.h"
#include "variables.h" #include "variables.h"
#include "train.h"
#include "network_data.h" #include "network_data.h"
#include "network_client.h" #include "network_client.h"
@ -799,7 +800,7 @@ static void ToolbarTrainClick(Window *w)
int dis = -1; int dis = -1;
FOR_ALL_VEHICLES(v) { 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); PopupMainPlayerToolbMenu(w, 310, 13, dis);
} }

View File

@ -5,6 +5,7 @@
#include "string.h" #include "string.h"
#include "strings.h" #include "strings.h"
#include "network_data.h" #include "network_data.h"
#include "train.h"
#ifdef ENABLE_NETWORK #ifdef ENABLE_NETWORK
@ -1271,7 +1272,7 @@ void NetworkPopulateCompanyInfo(void)
if (v->owner < MAX_PLAYERS) if (v->owner < MAX_PLAYERS)
switch (v->type) { switch (v->type) {
case VEH_Train: case VEH_Train:
if (v->subtype == TS_Front_Engine) if (IsFrontEngine(v))
_network_player_info[v->owner].num_vehicle[0]++; _network_player_info[v->owner].num_vehicle[0]++;
break; break;
case VEH_Road: case VEH_Road:

View File

@ -17,6 +17,7 @@
#include "viewport.h" #include "viewport.h"
#include "depot.h" #include "depot.h"
#include "waypoint.h" #include "waypoint.h"
#include "train.h"
static int OrderGetSel(const Window* w) 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 != v->type) return false;
if (u->type == VEH_Train && u->subtype != TS_Front_Engine) { if (u->type == VEH_Train && !IsFrontEngine(u)) {
u = GetFirstVehicleInChain(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 // v is vehicle getting orders. Only copy/clone orders if vehicle doesn't have any orders yet

View File

@ -15,6 +15,7 @@
#include "economy.h" #include "economy.h"
#include "network.h" #include "network.h"
#include "variables.h" #include "variables.h"
#include "train.h"
#ifdef ENABLE_NETWORK #ifdef ENABLE_NETWORK
#include "network_data.h" #include "network_data.h"
@ -432,7 +433,7 @@ static void DrawPlayerVehiclesAmount(PlayerID player)
FOR_ALL_VEHICLES(v) { FOR_ALL_VEHICLES(v) {
if (v->owner == player) { if (v->owner == player) {
switch (v->type) { 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_Road: road++; break;
case VEH_Aircraft: if (v->subtype <= 2) air++; break; case VEH_Aircraft: if (v->subtype <= 2) air++; break;
case VEH_Ship: ship++; break; case VEH_Ship: ship++; break;

View File

@ -30,7 +30,7 @@
enum { enum {
SAVEGAME_MAJOR_VERSION = 17, SAVEGAME_MAJOR_VERSION = 17,
SAVEGAME_MINOR_VERSION = 0, SAVEGAME_MINOR_VERSION = 1,
SAVEGAME_LOADABLE_VERSION = (SAVEGAME_MAJOR_VERSION << 8) + SAVEGAME_MINOR_VERSION SAVEGAME_LOADABLE_VERSION = (SAVEGAME_MAJOR_VERSION << 8) + SAVEGAME_MINOR_VERSION
}; };

View File

@ -26,6 +26,7 @@
#include "sprite.h" #include "sprite.h"
#include "depot.h" #include "depot.h"
#include "pbs.h" #include "pbs.h"
#include "train.h"
enum { enum {
/* Max stations: 64000 (64 * 1000) */ /* Max stations: 64000 (64 * 1000) */
@ -2255,7 +2256,7 @@ static uint32 VehicleEnter_Station(Vehicle *v, TileIndex tile, int x, int y)
byte dir; byte dir;
if (v->type == VEH_Train) { 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)) { !IsCompatibleTrainStationTile(tile + TileOffsByDir(v->direction >> 1), tile)) {
station_id = _m[tile].m2; station_id = _m[tile].m2;

208
train.h Normal file
View File

@ -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 */

View File

@ -22,9 +22,7 @@
#include "debug.h" #include "debug.h"
#include "waypoint.h" #include "waypoint.h"
#include "vehicle_gui.h" #include "vehicle_gui.h"
#include "train.h"
#define IS_FIRSTHEAD_SPRITE(spritenum) \
(is_custom_sprite(spritenum) ? IS_CUSTOM_FIRSTHEAD_SPRITE(spritenum) : _engine_sprite_add[spritenum] == 0)
static bool TrainCheckIfLineEnds(Vehicle *v); static bool TrainCheckIfLineEnds(Vehicle *v);
static void TrainController(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; vweight += (_cargoc.weights[u->cargo_type] * u->cargo_count) / 16;
// Vehicle weight is not added for articulated parts. // 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 // vehicle weight is the sum of the weight of the vehicle and the weight of its cargo
vweight += rvi->weight; vweight += rvi->weight;
@ -86,10 +84,10 @@ void TrainConsistChanged(Vehicle* v)
assert(v->type == VEH_Train); 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); 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; v->u.rail.cached_total_length = 0;
for (u = v; u != NULL; u = u->next) { for (u = v; u != NULL; u = u->next) {
@ -102,7 +100,7 @@ void TrainConsistChanged(Vehicle* v)
if (rvi_u->visual_effect != 0) { if (rvi_u->visual_effect != 0) {
u->u.rail.cached_vis_effect = rvi_u->visual_effect; u->u.rail.cached_vis_effect = rvi_u->visual_effect;
} else { } 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 // Wagons and articulated parts have no effect by default
u->u.rail.cached_vis_effect = 0x40; u->u.rail.cached_vis_effect = 0x40;
} else if (rvi_u->engclass == 0) { } 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 is the sum of the powers of all engines and powered wagons in the consist
power += rvi_u->power; power += rvi_u->power;
@ -277,7 +275,7 @@ static int GetTrainAcceleration(Vehicle *v, bool mode)
max_speed += (max_speed / 2) * v->u.rail.railtype; 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)) { if (TrainShouldStop(v, v->tile)) {
int station_length = 0; int station_length = 0;
TileIndex tile = v->tile; TileIndex tile = v->tile;
@ -361,7 +359,7 @@ void UpdateTrainAcceleration(Vehicle *v)
uint power = 0; uint power = 0;
uint weight = 0; uint weight = 0;
assert(v->subtype == TS_Front_Engine); assert(IsFrontEngine(v));
weight = v->u.rail.cached_weight; weight = v->u.rail.cached_weight;
power = v->u.rail.cached_power; power = v->u.rail.cached_power;
@ -491,7 +489,8 @@ static void AddArticulatedParts(const RailVehicleInfo *rvi, Vehicle **vl)
u->engine_type = engine_type; u->engine_type = engine_type;
u->value = 0; u->value = 0;
u->type = VEH_Train; u->type = VEH_Train;
u->subtype = TS_Artic_Part; u->subtype = 0;
SetArticulatedPart(u);
u->cur_image = 0xAC2; u->cur_image = 0xAC2;
VehiclePositionChanged(u); VehiclePositionChanged(u);
@ -531,7 +530,7 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags)
FOR_ALL_VEHICLES(w) { FOR_ALL_VEHICLES(w) {
if (w->type == VEH_Train && w->tile == tile && 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); u = GetLastVehicleInChain(w);
break; break;
} }
@ -555,10 +554,12 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags)
v->u.rail.track = 0x80; v->u.rail.track = 0x80;
v->vehstatus = VS_HIDDEN | VS_DEFPAL; v->vehstatus = VS_HIDDEN | VS_DEFPAL;
v->subtype = TS_Free_Car; v->subtype = 0;
SetTrainWagon(v);
if (u != NULL) { if (u != NULL) {
u->next = v; u->next = v;
v->subtype = TS_Not_First; } else {
SetFreeWagon(v);
} }
v->cargo_type = rvi->cargo_type; v->cargo_type = rvi->cargo_type;
@ -593,11 +594,11 @@ static void NormalizeTrainVehInDepot(const Vehicle* u)
const Vehicle* v; const Vehicle* v;
FOR_ALL_VEHICLES(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->tile == u->tile &&
v->u.rail.track == 0x80) { v->u.rail.track == 0x80) {
if (DoCommandByTile(0, v->index | (u->index << 16), 1, DC_EXEC, if (CmdFailed(DoCommandByTile(0, v->index | (u->index << 16), 1, DC_EXEC,
CMD_MOVE_RAIL_VEHICLE) == CMD_ERROR) CMD_MOVE_RAIL_VEHICLE)))
break; break;
} }
} }
@ -638,7 +639,8 @@ void AddRearEngineToMultiheadedTrain(Vehicle *v, Vehicle *u, bool building)
u->z_height = 6; u->z_height = 6;
u->u.rail.track = 0x80; u->u.rail.track = 0x80;
u->vehstatus = v->vehstatus & ~VS_STOPPED; u->vehstatus = v->vehstatus & ~VS_STOPPED;
u->subtype = TS_Not_First; u->subtype = 0;
SetMultiheaded(u);
u->spritenum = v->spritenum + 1; u->spritenum = v->spritenum + 1;
u->cargo_type = v->cargo_type; u->cargo_type = v->cargo_type;
u->cargo_cap = v->cargo_cap; 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->type = VEH_Train;
v->cur_image = 0xAC2; v->cur_image = 0xAC2;
v->subtype = 0;
SetFrontEngine(v);
SetTrainEngine(v);
v->u.rail.shortest_platform[0] = 255; v->u.rail.shortest_platform[0] = 255;
v->u.rail.shortest_platform[1] = 0; v->u.rail.shortest_platform[1] = 0;
VehiclePositionChanged(v); VehiclePositionChanged(v);
if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) { if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) {
SetMultiheaded(v);
AddRearEngineToMultiheadedTrain(vl[0], vl[1], true); 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 { } else {
AddArticulatedParts(rvi, vl); AddArticulatedParts(rvi, vl);
} }
@ -800,7 +813,7 @@ int CheckTrainStoppedInDepot(const Vehicle *v)
for (; v != NULL; v = v->next) { for (; v != NULL; v = v->next) {
count++; count++;
if (v->u.rail.track != 0x80 || v->tile != tile || 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; _error_message = STR_881A_TRAINS_CAN_ONLY_BE_ALTERED;
return -1; return -1;
} }
@ -824,7 +837,8 @@ static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first)
v = GetNextVehicle(v); v = GetNextVehicle(v);
if (v == NULL) return NULL; if (v == NULL) return NULL;
v->subtype = TS_Free_Car; if (IsTrainWagon(v)) SetFreeWagon(v);
return v; return v;
} }
@ -840,7 +854,7 @@ static Vehicle *FindGoodVehiclePos(const Vehicle *src)
TileIndex tile = src->tile; TileIndex tile = src->tile;
FOR_ALL_VEHICLES(dst) { FOR_ALL_VEHICLES(dst) {
if (dst->type == VEH_Train && dst->subtype == TS_Free_Car && if (dst->type == VEH_Train && IsFreeWagon(dst) &&
dst->tile == tile) { dst->tile == tile) {
// check so all vehicles in the line have the same engine. // check so all vehicles in the line have the same engine.
Vehicle *v = dst; Vehicle *v = dst;
@ -855,6 +869,45 @@ static Vehicle *FindGoodVehiclePos(const Vehicle *src)
return NULL; 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. /** Move a rail vehicle around inside the depot.
* @param x,y unused * @param x,y unused
* @param p1 various bitstuffed elements * @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 s = GB(p1, 0, 16);
VehicleID d = GB(p1, 16, 16); VehicleID d = GB(p1, 16, 16);
Vehicle *src, *dst, *src_head, *dst_head; Vehicle *src, *dst, *src_head, *dst_head;
bool is_loco;
if (!IsVehicleIndex(s)) return CMD_ERROR; 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; 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 nothing is selected as destination, try and find a matching vehicle to drag to.
if (d == INVALID_VEHICLE) { if (d == INVALID_VEHICLE) {
dst = NULL; dst = NULL;
if (!is_loco) dst = FindGoodVehiclePos(src); if (!IsTrainEngine(src)) dst = FindGoodVehiclePos(src);
} else { } else {
dst = GetVehicle(d); dst = GetVehicle(d);
} }
// if an articulated part is being handled, deal with its parent vehicle // 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) { if (dst != NULL) {
while (dst->subtype == TS_Artic_Part) dst = GetPrevVehicleInChain(dst); while (IsArticulatedPart(dst)) dst = GetPrevVehicleInChain(dst);
} }
// don't move the same vehicle.. // 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); dst = GetLastEnginePart(dst);
} }
/* clear the ->first cache */ if (dst != NULL && IsMultiheaded(dst) && !IsTrainEngine(dst) && IsTrainWagon(src)) {
{ /* We are moving a wagon to the rear part of a multiheaded engine */
Vehicle *u; if (dst->next == NULL) {
/* It's the last one, so we will add the wagon just before the rear engine */
for (u = src_head; u != NULL; u = u->next) u->first = NULL; dst = GetPrevVehicleInChain(dst);
for (u = dst_head; u != NULL; u = u->next) u->first = NULL; // 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 */ /* check if all vehicles in the source train are stopped inside a depot */
if (CheckTrainStoppedInDepot(src_head) < 0) return CMD_ERROR; 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); int num = CheckTrainStoppedInDepot(dst_head);
if (num < 0) return CMD_ERROR; 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); 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); 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; if (HASBIT(p2, 0) && src_head == dst_head) return 0;
// moving a loco to a new line?, then we need to assign a unitnumber. // 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); UnitID unit_num = GetFreeUnitNumber(VEH_Train);
if (unit_num > _patches.max_trains) if (unit_num > _patches.max_trains)
return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); 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? */ /* do it? */
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
Vehicle *new_front = GetNextVehicle(src); //used if next in line should make a train on it's own /* clear the ->first cache */
bool make_new_front = src->subtype == TS_Front_Engine; {
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)) { if (HASBIT(p2, 0)) {
// unlink ALL wagons // unlink ALL wagons
@ -977,26 +1051,27 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
} }
if (dst == NULL) { if (dst == NULL) {
// move the train to an empty line. for locomotives, we set the type to 0. for wagons, 4. // move the train to an empty line. for locomotives, we set the type to TS_Front. for wagons, 4.
if (is_loco) { if (IsTrainEngine(src)) {
if (src->subtype != TS_Front_Engine) { if (!IsFrontEngine(src)) {
// setting the type to 0 also involves setting up the orders field. // setting the type to 0 also involves setting up the orders field.
src->subtype = TS_Front_Engine; SetFrontEngine(src);
assert(src->orders == NULL); assert(src->orders == NULL);
src->num_orders = 0; src->num_orders = 0;
} }
} else { } else {
src->subtype = TS_Free_Car; SetFreeWagon(src);
} }
dst_head = src; dst_head = src;
} else { } 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. // the vehicle was previously a loco. need to free the order list and delete vehicle windows etc.
DeleteWindowById(WC_VEHICLE_VIEW, src->index); DeleteWindowById(WC_VEHICLE_VIEW, src->index);
DeleteVehicleOrders(src); DeleteVehicleOrders(src);
} }
src->subtype = TS_Not_First; ClearFrontEngine(src);
ClearFreeWagon(src);
src->unitnumber = 0; // doesn't occupy a unitnumber anymore. src->unitnumber = 0; // doesn't occupy a unitnumber anymore.
// link in the wagon(s) in the chain. // 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; 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 if (HASBIT(p2, 0) && src_head != NULL && src_head != src) {
* To do this, CmdMoveRailVehicle must be called once more /* if we stole a rear multiheaded engine, we better give it back to the front end */
* since we set p2 to a condition that makes the statement false, we can't loop forever with this one */ Vehicle *engine = NULL, *u;
if (make_new_front && new_front != NULL && !(HASBIT(p2, 0))) { for (u = src_head; u != NULL; u = u->next) {
if (!(RailVehInfo(new_front->engine_type)->flags & RVI_WAGON)) { if (IsMultiheaded(u)) {
CmdMoveRailVehicle(x, y, flags, new_front->index | (INVALID_VEHICLE << 16), 1); 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) { if (src_head) {
NormaliseTrainConsist(src_head);
TrainConsistChanged(src_head); TrainConsistChanged(src_head);
if (src_head->subtype == TS_Front_Engine) { if (IsFrontEngine(src_head)) {
UpdateTrainAcceleration(src_head); UpdateTrainAcceleration(src_head);
InvalidateWindow(WC_VEHICLE_DETAILS, src_head->index); InvalidateWindow(WC_VEHICLE_DETAILS, src_head->index);
/* Update the refit button and window */ /* 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) { if (dst_head) {
NormaliseTrainConsist(dst_head);
TrainConsistChanged(dst_head); TrainConsistChanged(dst_head);
if (dst_head->subtype == TS_Front_Engine) { if (IsFrontEngine(dst_head)) {
UpdateTrainAcceleration(dst_head); UpdateTrainAcceleration(dst_head);
InvalidateWindow(WC_VEHICLE_DETAILS, dst_head->index); InvalidateWindow(WC_VEHICLE_DETAILS, dst_head->index);
/* Update the refit button and window */ /* Update the refit button and window */
@ -1074,27 +1176,6 @@ int32 CmdStartStopTrain(int x, int y, uint32 flags, uint32 p1, uint32 p2)
return 0; 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. /** Sell a (single) train wagon/engine.
* @param x,y unused * @param x,y unused
* @param p1 the wagon/engine index * @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) int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{ {
Vehicle *v, *tmp, *first; Vehicle *v, *tmp, *first;
Vehicle *new_f = NULL;
int32 cost = 0; int32 cost = 0;
if (!IsVehicleIndex(p1) || p2 > 2) return CMD_ERROR; 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); SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
while (v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v); while (IsArticulatedPart(v)) v = GetPrevVehicleInChain(v);
first = GetFirstVehicleInChain(v); first = GetFirstVehicleInChain(v);
// make sure the vehicle is stopped in the depot // make sure the vehicle is stopped in the depot
if (CheckTrainStoppedInDepot(first) < 0) return CMD_ERROR; 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 (flags & DC_EXEC) {
if (v == first && first->subtype == TS_Front_Engine) { if (v == first && IsFrontEngine(first)) {
DeleteWindowById(WC_VEHICLE_VIEW, first->index); DeleteWindowById(WC_VEHICLE_VIEW, first->index);
} }
if (IsLocalPlayer() && (p1 == 1 || !(RailVehInfo(v->engine_type)->flags & RVI_WAGON))) { 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 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 /* 1. Delete the engine, if it is dualheaded also delete the matching
* rear engine of the loco (from the point of deletion onwards) */ * rear engine of the loco (from the point of deletion onwards) */
Vehicle* rear = (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) ? GetRearEngine(v) : NULL; Vehicle *rear = (IsMultiheaded(v) &&
IsTrainEngine(v)) ? v->u.rail.other_multiheaded_part : NULL;
if (rear != NULL) { if (rear != NULL) {
cost -= v->value; cost -= rear->value;
if (flags & DC_EXEC) { if (flags & DC_EXEC) {
v = UnlinkWagon(rear, v); UnlinkWagon(rear, first);
DeleteVehicle(rear); DeleteVehicle(rear);
} }
} }
/* 2. We are selling the first engine, some special action might be required /* 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) { 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 */ /* 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; 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, * 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 */ * promote it as a new train, retaining the unitnumber, orders */
if (new_f != NULL) { 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; switch_engine = true;
/* Copy important data from the front engine */ /* Copy important data from the front engine */
new_f->unitnumber = first->unitnumber; 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 /* 4 If the second wagon was an engine, update it to front_engine
* which UnlinkWagon() has changed to TS_Free_Car */ * 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. */ /* 5. If the train still exists, update its acceleration, window, etc. */
if (first != NULL) { if (first != NULL) {
NormaliseTrainConsist(first);
TrainConsistChanged(first); TrainConsistChanged(first);
if (first->subtype == TS_Front_Engine) { if (IsFrontEngine(first)) {
InvalidateWindow(WC_VEHICLE_DETAILS, first->index); InvalidateWindow(WC_VEHICLE_DETAILS, first->index);
InvalidateWindow(WC_VEHICLE_REFIT, first->index); InvalidateWindow(WC_VEHICLE_REFIT, first->index);
UpdateTrainAcceleration(first); 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 /* (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. * 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 * Totally braindead cause building a new engine adds all loco-less
* engines to its train anyways */ * engines to its train anyways */
if (p2 == 2 && ori_subtype == TS_Front_Engine) { if (p2 == 2 && HASBIT(ori_subtype, Train_Front)) {
for (v = first; v != NULL; v = tmp) { for (v = first; v != NULL; v = tmp) {
tmp = GetNextVehicle(v); tmp = GetNextVehicle(v);
DoCommandByTile(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); 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; } break;
case 1: { /* Delete wagon and all wagons after it given certain criteria */ case 1: { /* Delete wagon and all wagons after it given certain criteria */
/* 1. Count the number for first and rear engines for dualheads /* Start deleting every vehicle after the selected one
* to be able to deduce which ones go with which ones */ * If we encounter a matching rear-engine to a front-engine
int enf_count = 0; * earlier in the chain (before deletion), leave it alone */
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 */
for (; v != NULL; v = tmp) { for (; v != NULL; v = tmp) {
tmp = GetNextVehicle(v); tmp = GetNextVehicle(v);
if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) { if (IsMultiheaded(v)) {
if (IS_FIRSTHEAD_SPRITE(v->spritenum)) { if (IsTrainEngine(v)) {
/* Always delete newly encountered front-engines */ /* We got a front engine of a multiheaded set. Now we will sell the rear end too */
enf_count--; Vehicle *rear = v->u.rail.other_multiheaded_part;
} else if (enr_count > enf_count) {
/* More rear engines than front engines means this rear-engine does if (rear != NULL) {
* not belong to any front-engine; delete */ cost -= rear->value;
enr_count--; if (flags & DC_EXEC) {
} else { first = UnlinkWagon(rear, first);
/* Otherwise leave it alone */ DeleteVehicle(rear);
}
}
} else if (v->u.rail.other_multiheaded_part != NULL) {
/* The front to this engine is earlier in this train. Do nothing */
continue; 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 */ /* 3. If it is still a valid train after selling, update its acceleration and cached values */
if ((flags & DC_EXEC) && first != NULL) { if ((flags & DC_EXEC) && first != NULL) {
NormaliseTrainConsist(first);
TrainConsistChanged(first); TrainConsistChanged(first);
if (first->subtype == TS_Front_Engine) if (IsFrontEngine(first))
UpdateTrainAcceleration(first); UpdateTrainAcceleration(first);
InvalidateWindow(WC_VEHICLE_DETAILS, first->index);
InvalidateWindow(WC_VEHICLE_REFIT, first->index);
} }
} break; } break;
} }
@ -2620,7 +2705,7 @@ static bool CheckCompatibleRail(const Vehicle *v, TileIndex tile)
// tracks over roads, do owner check of tracks // tracks over roads, do owner check of tracks
return return
IsTileOwner(tile, v->owner) && ( IsTileOwner(tile, v->owner) && (
v->subtype != TS_Front_Engine || !IsFrontEngine(v) ||
IsCompatibleRail(v->u.rail.railtype, GB(_m[tile].m4, 0, 4)) IsCompatibleRail(v->u.rail.railtype, GB(_m[tile].m4, 0, 4))
); );
@ -2630,7 +2715,7 @@ static bool CheckCompatibleRail(const Vehicle *v, TileIndex tile)
return return
IsTileOwner(tile, v->owner) && ( IsTileOwner(tile, v->owner) && (
v->subtype != TS_Front_Engine || !IsFrontEngine(v) ||
IsCompatibleRail(v->u.rail.railtype, GetRailType(tile)) 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 * 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 * Reports the incident in a flashy news item, modifies station ratings and
* plays a sound. * plays a sound.
*/ */
@ -2778,7 +2863,7 @@ static void CheckTrainCollision(Vehicle *v)
num += 2 + CountPassengersInTrain(coll); num += 2 + CountPassengersInTrain(coll);
SetVehicleCrashed(v); SetVehicleCrashed(v);
if (coll->subtype == TS_Front_Engine) SetVehicleCrashed(coll); if (IsFrontEngine(coll)) SetVehicleCrashed(coll);
SetDParam(0, num); SetDParam(0, num);
AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL, AddNewsItem(STR_8868_TRAIN_CRASH_DIE_IN_FIREBALL,
@ -2800,7 +2885,7 @@ static void *CheckVehicleAtSignal(Vehicle *v, void *data)
{ {
const VehicleAtSignalData* vasd = 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) { v->tile == vasd->tile) {
byte diff = (v->direction - vasd->direction + 2) & 7; byte diff = (v->direction - vasd->direction + 2) & 7;
@ -2970,7 +3055,7 @@ green_light:
goto invalid_rail; 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)) { if (!(r&0x4)) {
v->tile = gp.new_tile; v->tile = gp.new_tile;
@ -2978,7 +3063,7 @@ green_light:
assert(v->u.rail.track); assert(v->u.rail.track);
} }
if (v->subtype == TS_Front_Engine) if (IsFrontEngine(v))
TrainMovedChangeSignals(gp.new_tile, enterdir); TrainMovedChangeSignals(gp.new_tile, enterdir);
/* Signals can only change when the first /* Signals can only change when the first
@ -3433,13 +3518,13 @@ void Train_Tick(Vehicle *v)
v->tick_counter++; v->tick_counter++;
if (v->subtype == TS_Front_Engine) { if (IsFrontEngine(v)) {
TrainLocoHandler(v, false); TrainLocoHandler(v, false);
// make sure vehicle wasn't deleted. // 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); 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 // Delete flooded standalone wagon
if (++v->u.rail.crash_anim_pos >= 4400) if (++v->u.rail.crash_anim_pos >= 4400)
DeleteVehicle(v); DeleteVehicle(v);
@ -3460,7 +3545,7 @@ void TrainEnterDepot(Vehicle *v, TileIndex tile)
{ {
SetSignalsOnBothDir(tile, _depot_track_ind[GB(_m[tile].m5, 0, 2)]); 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); VehicleServiceInDepot(v);
@ -3565,7 +3650,7 @@ void OnNewDay_Train(Vehicle *v)
if ((++v->day_counter & 7) == 0) if ((++v->day_counter & 7) == 0)
DecreaseVehicleValue(v); DecreaseVehicleValue(v);
if (v->subtype == TS_Front_Engine) { if (IsFrontEngine(v)) {
CheckVehicleBreakdown(v); CheckVehicleBreakdown(v);
AgeVehicle(v); AgeVehicle(v);
@ -3610,7 +3695,7 @@ void TrainsYearlyLoop(void)
Vehicle *v; Vehicle *v;
FOR_ALL_VEHICLES(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) // 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) { if (_patches.train_income_warn && v->owner == _local_player && v->age >= 730 && v->profit_this_year < 0) {

View File

@ -18,6 +18,7 @@
#include "engine.h" #include "engine.h"
#include "vehicle_gui.h" #include "vehicle_gui.h"
#include "depot.h" #include "depot.h"
#include "train.h"
int _traininfo_vehicle_pitch = 0; 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. // find a locomotive in the depot.
found = NULL; found = NULL;
FOR_ALL_VEHICLES(v) { 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->tile == tile &&
v->u.rail.track == 0x80) { v->u.rail.track == 0x80) {
if (found != NULL) // must be exactly one. if (found != NULL) // must be exactly one.
@ -392,12 +393,12 @@ static void DrawTrainDepotWindow(Window *w)
hnum = 8; hnum = 8;
FOR_ALL_VEHICLES(v) { FOR_ALL_VEHICLES(v) {
if (v->type == VEH_Train && if (v->type == VEH_Train &&
(v->subtype == TS_Front_Engine || v->subtype == TS_Free_Car) && (IsFrontEngine(v) || IsFreeWagon(v)) &&
v->tile == tile && v->tile == tile &&
v->u.rail.track == 0x80) { v->u.rail.track == 0x80) {
num++; num++;
// determine number of items in the X direction. // 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); hnum = max(hnum, v->u.rail.cached_total_length);
} }
} }
@ -422,7 +423,7 @@ static void DrawTrainDepotWindow(Window *w)
// draw all trains // draw all trains
FOR_ALL_VEHICLES(v) { 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 && v->tile == tile && v->u.rail.track == 0x80 &&
--num < 0 && num >= -w->vscroll.cap) { --num < 0 && num >= -w->vscroll.cap) {
DrawTrainImage(v, x+21, y, w->hscroll.cap, w->hscroll.pos, WP(w,traindepot_d).sel); 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 // draw all remaining vehicles
FOR_ALL_VEHICLES(v) { 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 && v->tile == tile && v->u.rail.track == 0x80 &&
--num < 0 && num >= -w->vscroll.cap) { --num < 0 && num >= -w->vscroll.cap) {
DrawTrainImage(v, x+50, y, w->hscroll.cap - 1, 0, WP(w,traindepot_d).sel); 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 */ /* go through all the locomotives */
FOR_ALL_VEHICLES(v) { FOR_ALL_VEHICLES(v) {
if (v->type == VEH_Train && if (v->type == VEH_Train &&
v->subtype == TS_Front_Engine && IsFrontEngine(v) &&
v->tile == w->window_number && v->tile == w->window_number &&
v->u.rail.track == 0x80 && v->u.rail.track == 0x80 &&
--row < 0) { --row < 0) {
@ -496,7 +497,7 @@ static int GetVehicleFromTrainDepotWndPt(const Window *w, int x, int y, GetDepot
/* and then the list of free wagons */ /* and then the list of free wagons */
FOR_ALL_VEHICLES(v) { FOR_ALL_VEHICLES(v) {
if (v->type == VEH_Train && if (v->type == VEH_Train &&
v->subtype == TS_Free_Car && IsFreeWagon(v) &&
v->tile == w->window_number && v->tile == w->window_number &&
v->u.rail.track == 0x80 && v->u.rail.track == 0x80 &&
--row < 0) --row < 0)
@ -513,7 +514,7 @@ found_it:
d->head = d->wagon = v; d->head = d->wagon = v;
/* either pressed the flag or the number, but only when it's a loco */ /* 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; return (x >= -10) ? -2 : -1;
// skip vehicles that are scrolled off the left side // skip vehicles that are scrolled off the left side
@ -526,7 +527,7 @@ found_it:
} }
// if an articulated part was selected, find its parent // 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; d->wagon = v;
@ -539,7 +540,7 @@ static void TrainDepotMoveVehicle(Vehicle *wagon, VehicleID sel, Vehicle *head)
v = GetVehicle(sel); v = GetVehicle(sel);
if (/*v->subtype == TS_Front_Engine ||*/ v == wagon) if (v == wagon)
return; return;
if (wagon == NULL) { if (wagon == NULL) {
@ -605,10 +606,10 @@ static void HandleCloneVehClick(const Vehicle* v, const Window* w)
if (v == NULL || v->type != VEH_Train) return; if (v == NULL || v->type != VEH_Train) return;
// for train vehicles: subtype 0 for locs and not zero for others // for train vehicles: subtype 0 for locs and not zero for others
if (v->subtype != TS_Front_Engine) { if (!IsFrontEngine(v)) {
v = GetFirstVehicleInChain(v); v = GetFirstVehicleInChain(v);
// Do nothing when clicking on a train in depot with no loc attached // 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, 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; 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)); DoCommandP(v->tile, v->index, sell_cmd, NULL, CMD_SELL_RAIL_WAGON | CMD_MSG(STR_8839_CAN_T_SELL_RAILROAD_VEHICLE));
} else { } else {
_backup_orders_tile = v->tile; _backup_orders_tile = v->tile;
@ -728,7 +729,7 @@ static void TrainDepotWndProc(Window *w, WindowEvent *e)
sel != INVALID_VEHICLE) { sel != INVALID_VEHICLE) {
if (gdvp.wagon == NULL || gdvp.wagon->index != sel) { if (gdvp.wagon == NULL || gdvp.wagon->index != sel) {
TrainDepotMoveVehicle(gdvp.wagon, sel, gdvp.head); 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); ShowTrainViewWindow(gdvp.head);
} }
} }
@ -1201,7 +1202,7 @@ static void DrawTrainDetailsWindow(Window *w)
DrawTrainImage(u, x + WagonLengthToPixels(dx), y, 1, 0, INVALID_VEHICLE); DrawTrainImage(u, x + WagonLengthToPixels(dx), y, 1, 0, INVALID_VEHICLE);
dx += u->u.rail.cached_veh_length; dx += u->u.rail.cached_veh_length;
u = u->next; 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); _train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + WagonLengthToPixels(dx) + 2, y + 2);
y += 14; y += 14;
} }
@ -1464,7 +1465,7 @@ static void PlayerTrainsWndProc(Window *w, WindowEvent *e)
v = GetVehicle(vl->sort_list[id_v].index); 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); ShowTrainViewWindow(v);
} }

View File

@ -22,6 +22,7 @@
#include "debug.h" #include "debug.h"
#include "variables.h" #include "variables.h"
#include "bridge.h" #include "bridge.h"
#include "train.h"
#include "table/bridge_land.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; vdir = v->direction >> 1;
if (v->u.rail.track != 0x40 && dir == vdir) { 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) if (v->spritenum < 4)
SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v); SndPlayVehicleFx(SND_05_TRAIN_THROUGH_TUNNEL, v);
return 0; return 0;
@ -1534,7 +1535,7 @@ static uint32 VehicleEnter_TunnelBridge(Vehicle *v, TileIndex tile, int x, int y
} }
} }
} else if (_m[tile].m5 & 0x80) { } 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; uint h;
if (GetTileSlope(tile, &h) != 0) if (GetTileSlope(tile, &h) != 0)

168
vehicle.c
View File

@ -23,6 +23,7 @@
#include "station.h" #include "station.h"
#include "gui.h" #include "gui.h"
#include "rail.h" #include "rail.h"
#include "train.h"
#define INVALID_COORD (-0x8000) #define INVALID_COORD (-0x8000)
#define GEN_HASH(x,y) (((x & 0x1F80)>>7) + ((y & 0xFC0))) #define GEN_HASH(x,y) (((x & 0x1F80)>>7) + ((y & 0xFC0)))
@ -238,7 +239,7 @@ void AfterLoadVehicles(void)
v->left_coord = INVALID_COORD; v->left_coord = INVALID_COORD;
VehiclePositionChanged(v); 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); TrainConsistChanged(v);
} }
} }
@ -503,7 +504,7 @@ Vehicle *GetFirstVehicleInChain(const Vehicle *v)
assert(v != NULL); assert(v != NULL);
if (v->first != 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!"); 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; while ((u = GetPrevVehicleInChain_bruteforce(v)) != NULL) v = u;
/* Set the first pointer of all vehicles in that chain to the first wagon */ /* 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; for (u = (Vehicle *)v; u != NULL; u = u->next) u->first = (Vehicle *)v;
return (Vehicle*)v; return (Vehicle*)v;
@ -1490,10 +1491,13 @@ static Vehicle *GetNextEnginePart(Vehicle *v)
{ {
switch (v->type) { switch (v->type) {
case VEH_Train: case VEH_Train:
if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) { if (IsMultiheaded(v)) {
return GetRearEngine(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; break;
case VEH_Aircraft: 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 (!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 // check that we can allocate enough vehicles
if (!(flags & DC_EXEC)) { if (!(flags & DC_EXEC)) {
@ -1555,7 +1559,13 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2)
v = v_front; v = v_front;
do { 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; 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 // this s a train car
// add this unit to the end of the train // add this unit to the end of the train
DoCommand(x, y, (w_rear->index << 16) | w->index, 1, flags, CMD_MOVE_RAIL_VEHICLE); 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); } while (v->type == VEH_Train && (v = GetNextVehicle(v)) != NULL);
if (flags & DC_EXEC) { if (flags & DC_EXEC && v_front->type == VEH_Train) {
v = v_front; // _new_train_id needs to be the front engine due to the callback function
w = w_front; _new_train_id = w_front->index;
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;
}
}
} }
return total_cost; return total_cost;
} }
@ -1665,12 +1666,12 @@ static int32 ReplaceVehicle(Vehicle **w, byte flags)
MoveVehicleCargo(new_v, old_v); 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 /* 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 * 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 * 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 { } else {
// copy/clone the orders // 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); 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; cost = 0;
w = v; w = v;
do { 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 // check if the vehicle should be replaced
if (!p->engine_renew || if (!p->engine_renew ||
w->age - w->max_age < (p->engine_renew_months * 30) || // replace if engine is too old 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; 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 */ /* Now replace the vehicle */
temp_cost = ReplaceVehicle(&w, flags); 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,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[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 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_CONDREFX(offsetof(Vehicle,u)+offsetof(VehicleRail,other_multiheaded_part), REF_VEHICLE, 2, 255), // added with 17.1, but was blank since 2
SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 5, 2, 255), // reserve extra space in savegame here. (currently 3 bytes)
SLE_CONDARR(NullStruct,null,SLE_FILE_U8 | SLE_VAR_NULL, 3, 2, 255),
SLE_END() 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. // Will be called when vehicles need to be loaded.
static void Load_VEHS(void) 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[] = { const ChunkHandler _veh_chunk_handlers[] = {

View File

@ -27,14 +27,6 @@ enum VehStatus {
VS_CRASHED = 0x80, 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 */ /* Effect vehicle types */
typedef enum EffectVehicle { typedef enum EffectVehicle {
EV_CHIMNEY_SMOKE = 0, 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 * 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]; byte shortest_platform[2];
// Link between the two ends of a multiheaded engine
Vehicle *other_multiheaded_part;
} VehicleRail; } VehicleRail;
enum { enum {
@ -311,7 +306,6 @@ void DecreaseVehicleValue(Vehicle *v);
void CheckVehicleBreakdown(Vehicle *v); void CheckVehicleBreakdown(Vehicle *v);
void AgeVehicle(Vehicle *v); void AgeVehicle(Vehicle *v);
void VehicleEnteredDepotThisTick(Vehicle *v); void VehicleEnteredDepotThisTick(Vehicle *v);
Vehicle* GetRearEngine(const Vehicle* v);
void BeginVehicleMove(Vehicle *v); void BeginVehicleMove(Vehicle *v);
void EndVehicleMove(Vehicle *v); void EndVehicleMove(Vehicle *v);
@ -399,41 +393,6 @@ static inline bool IsVehicleIndex(uint index)
return index < GetVehiclePoolSize(); 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 */ /* Returns order 'index' of a vehicle or NULL when it doesn't exists */
static inline Order *GetVehicleOrder(const Vehicle *v, int index) static inline Order *GetVehicleOrder(const Vehicle *v, int index)
{ {

View File

@ -18,6 +18,7 @@
#include "variables.h" #include "variables.h"
#include "vehicle_gui.h" #include "vehicle_gui.h"
#include "viewport.h" #include "viewport.h"
#include "train.h"
Sorting _sorting; Sorting _sorting;
@ -105,7 +106,7 @@ void ResortVehicleLists(void)
void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID station) 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 n = 0;
uint i; uint i;
@ -122,7 +123,9 @@ void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID sta
if (station != INVALID_STATION) { if (station != INVALID_STATION) {
const Vehicle *v; const Vehicle *v;
FOR_ALL_VEHICLES(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; const Order *order;
FOR_VEHICLE_ORDERS(v, order) { FOR_VEHICLE_ORDERS(v, order) {
@ -138,7 +141,9 @@ void BuildVehicleList(vehiclelist_d* vl, int type, PlayerID owner, StationID sta
} else { } else {
const Vehicle *v; const Vehicle *v;
FOR_ALL_VEHICLES(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].index = v->index;
_vehicle_sort[n].owner = v->owner; _vehicle_sort[n].owner = v->owner;
++n; ++n;

View File

@ -19,6 +19,7 @@
#include "signs.h" #include "signs.h"
#include "waypoint.h" #include "waypoint.h"
#include "variables.h" #include "variables.h"
#include "train.h"
#define VIEWPORT_DRAW_MEM (65536 * 2) #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) static void SafeShowTrainViewWindow(const Vehicle* v)
{ {
if (v->subtype != TS_Front_Engine) v = GetFirstVehicleInChain(v); if (!IsFrontEngine(v)) v = GetFirstVehicleInChain(v);
ShowTrainViewWindow(v); ShowTrainViewWindow(v);
} }

View File

@ -15,6 +15,7 @@
#include "sound.h" #include "sound.h"
#include "depot.h" #include "depot.h"
#include "vehicle_gui.h" #include "vehicle_gui.h"
#include "train.h"
const SpriteID _water_shore_sprites[15] = { const SpriteID _water_shore_sprites[15] = {
0, 0,
@ -598,7 +599,7 @@ static void FloodVehicle(Vehicle *v)
v = GetFirstVehicleInChain(v); v = GetFirstVehicleInChain(v);
u = v; u = v;
if (v->subtype == TS_Front_Engine) pass = 4; // driver if (IsFrontEngine(v)) pass = 4; // driver
// crash all wagons, and count passangers // crash all wagons, and count passangers
BEGIN_ENUM_WAGONS(v) BEGIN_ENUM_WAGONS(v)