1
0
Fork 0
pull/14357/merge
Michael Lutz 2025-06-24 04:45:21 +00:00 committed by GitHub
commit c3b3c2ebf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 278 additions and 101 deletions

View File

@ -363,7 +363,7 @@ void AddArticulatedParts(Vehicle *first)
t->subtype = 0;
t->track = front->track;
t->railtype = front->railtype;
t->railtypes = front->railtypes;
t->spritenum = e_artic->u.rail.image_index;
if (e_artic->CanCarryCargo()) {

View File

@ -71,7 +71,7 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company)
switch (type) {
case VEH_TRAIN: {
/* make sure the railtypes are compatible */
if (!GetRailTypeInfo(e_from->u.rail.railtype)->compatible_railtypes.Any(GetRailTypeInfo(e_to->u.rail.railtype)->compatible_railtypes)) return false;
if (!GetAllCompatibleRailTypes(e_from->u.rail.railtypes).Any(GetAllCompatibleRailTypes(e_to->u.rail.railtypes))) return false;
/* make sure we do not replace wagons with engines or vice versa */
if ((e_from->u.rail.railveh_type == RAILVEH_WAGON) != (e_to->u.rail.railveh_type == RAILVEH_WAGON)) return false;

View File

@ -111,7 +111,7 @@ class ReplaceVehicleWindow : public Window {
if (draw_left && this->sel_railtype != INVALID_RAILTYPE) {
/* Ensure that the railtype is specific to the selected one */
if (rvi->railtype != this->sel_railtype) return false;
if (!rvi->railtypes.Test(this->sel_railtype)) return false;
}
return true;
}

View File

@ -632,14 +632,31 @@ static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engin
}
y += GetCharacterHeight(FS_NORMAL);
/* Supported rail types */
std::string railtypes{};
std::string_view list_separator = GetListSeparator();
for (RailType rt : rvi->railtypes) {
if (!railtypes.empty()) railtypes += list_separator;
AppendStringInPlace(railtypes, GetRailTypeInfo(rt)->strings.name);
}
DrawString(left, right, y, GetString(STR_PURCHASE_INFO_RAILTYPES, railtypes));
y += GetCharacterHeight(FS_NORMAL);
/* Max speed - Engine power */
DrawString(left, right, y, GetString(STR_PURCHASE_INFO_SPEED_POWER, PackVelocity(e->GetDisplayMaxSpeed(), e->type), e->GetPower()));
y += GetCharacterHeight(FS_NORMAL);
/* Max tractive effort - not applicable if old acceleration or maglev */
if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
DrawString(left, right, y, GetString(STR_PURCHASE_INFO_MAX_TE, e->GetDisplayMaxTractiveEffort()));
y += GetCharacterHeight(FS_NORMAL);
if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) {
bool is_maglev = true;
for (RailType rt : rvi->railtypes) {
is_maglev &= GetRailTypeInfo(rt)->acceleration_type == VehicleAccelerationModel::Maglev;
}
if (!is_maglev) {
DrawString(left, right, y, GetString(STR_PURCHASE_INFO_MAX_TE, e->GetDisplayMaxTractiveEffort()));
y += GetCharacterHeight(FS_NORMAL);
}
}
/* Running cost */
@ -1378,7 +1395,7 @@ struct BuildVehicleWindow : Window {
EngineID eid = e->index;
const RailVehicleInfo *rvi = &e->u.rail;
if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
if (this->filter.railtype != INVALID_RAILTYPE && !HasPowerOnRail(rvi->railtypes, this->filter.railtype)) continue;
if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
/* Filter now! So num_engines and num_wagons is valid */

View File

@ -602,15 +602,13 @@ void SettingsDisableElrail(int32_t new_value)
void UpdateDisableElrailSettingState(bool disable, bool update_vehicles)
{
/* pick appropriate railtype for elrail engines depending on setting */
const RailType new_railtype = disable ? RAILTYPE_RAIL : RAILTYPE_ELECTRIC;
/* walk through all train engines */
for (Engine *e : Engine::IterateType(VEH_TRAIN)) {
RailVehicleInfo *rv_info = &e->u.rail;
/* update railtype of engines intended to use elrail */
if (rv_info->intended_railtype == RAILTYPE_ELECTRIC) {
rv_info->railtype = new_railtype;
if (rv_info->intended_railtypes.Test(RAILTYPE_ELECTRIC)) {
rv_info->railtypes.Set(RAILTYPE_ELECTRIC, !disable);
rv_info->railtypes.Set(RAILTYPE_RAIL, disable);
}
}
@ -618,11 +616,12 @@ void UpdateDisableElrailSettingState(bool disable, bool update_vehicles)
* normal rail too */
if (disable) {
for (Train *t : Train::Iterate()) {
if (t->railtype == RAILTYPE_ELECTRIC) {
if (t->railtypes.Test(RAILTYPE_ELECTRIC)) {
/* this railroad vehicle is now compatible only with elrail,
* so add there also normal rail compatibility */
t->compatible_railtypes.Set(RAILTYPE_RAIL);
t->railtype = RAILTYPE_RAIL;
t->railtypes.Reset(RAILTYPE_ELECTRIC);
t->railtypes.Set(RAILTYPE_RAIL);
t->flags.Set(VehicleRailFlag::AllowedOnNormalRail);
}
}

View File

@ -1107,8 +1107,12 @@ static void NewVehicleAvailable(Engine *e)
if (e->type == VEH_TRAIN) {
/* maybe make another rail type available */
assert(e->u.rail.railtype < RAILTYPE_END);
for (Company *c : Company::Iterate()) c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes | GetRailTypeInfo(e->u.rail.railtype)->introduces_railtypes, TimerGameCalendar::date);
assert(e->u.rail.railtypes != RailTypes{});
RailTypes introduced{};
for (RailType rt : e->u.rail.railtypes) {
introduced |= GetRailTypeInfo(rt)->introduces_railtypes;
}
for (Company *c : Company::Iterate()) c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes | introduced, TimerGameCalendar::date);
} else if (e->type == VEH_ROAD) {
/* maybe make another road type available */
assert(e->u.road.roadtype < ROADTYPE_END);
@ -1267,7 +1271,7 @@ bool IsEngineBuildable(EngineID engine, VehicleType type, CompanyID company)
if (type == VEH_TRAIN && company != OWNER_DEITY) {
/* Check if the rail type is available to this company */
const Company *c = Company::Get(company);
if (!GetRailTypeInfo(e->u.rail.railtype)->compatible_railtypes.Any(c->avail_railtypes)) return false;
if (!GetAllCompatibleRailTypes(e->u.rail.railtypes).Any(c->avail_railtypes)) return false;
}
if (type == VEH_ROAD && company != OWNER_DEITY) {
/* Check if the road type is available to this company */

View File

@ -47,7 +47,8 @@ StringID GetEngineCategoryName(EngineID engine)
case VEH_AIRCRAFT: return STR_ENGINE_PREVIEW_AIRCRAFT;
case VEH_SHIP: return STR_ENGINE_PREVIEW_SHIP;
case VEH_TRAIN:
return GetRailTypeInfo(e->u.rail.railtype)->strings.new_loco;
assert(e->u.rail.railtypes.Any());
return GetRailTypeInfo(e->u.rail.railtypes.GetNthSetBit(0).value())->strings.new_loco;
}
}
@ -181,7 +182,24 @@ static std::string GetTrainEngineInfoString(const Engine &e)
res << GetString(STR_ENGINE_PREVIEW_COST_WEIGHT, e.GetCost(), e.GetDisplayWeight());
res << '\n';
if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(e.u.rail.railtype)->acceleration_type != 2) {
if (e.u.rail.railtypes.Count() > 1) {
std::string railtypes{};
std::string_view list_separator = GetListSeparator();
for (RailType rt : e.u.rail.railtypes) {
if (!railtypes.empty()) railtypes += list_separator;
AppendStringInPlace(railtypes, GetRailTypeInfo(rt)->strings.name);
}
res << GetString(STR_ENGINE_PREVIEW_RAILTYPES, railtypes);
res << '\n';
}
bool is_maglev = true;
for (RailType rt : e.u.rail.railtypes) {
is_maglev &= GetRailTypeInfo(rt)->acceleration_type == VehicleAccelerationModel::Maglev;
}
if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && !is_maglev) {
res << GetString(STR_ENGINE_PREVIEW_SPEED_POWER_MAX_TE, PackVelocity(e.GetDisplayMaxSpeed(), e.type), e.GetPower(), e.GetDisplayMaxTractiveEffort());
res << '\n';
} else {

View File

@ -43,13 +43,20 @@ enum EngineClass : uint8_t {
EC_MAGLEV, ///< Maglev engine.
};
/** Acceleration model of a vehicle. */
enum class VehicleAccelerationModel : uint8_t {
Normal, ///< Default acceleration model.
Monorail, ///< Monorail acceleration model.
Maglev, ///< Maglev acceleration model.
};
/** Information about a rail vehicle. */
struct RailVehicleInfo {
uint8_t image_index = 0;
RailVehicleTypes railveh_type{};
uint8_t cost_factor = 0; ///< Purchase cost factor; For multiheaded engines the sum of both engine prices.
RailType railtype{}; ///< Railtype, mangled if elrail is disabled.
RailType intended_railtype{}; ///< Intended railtype, regardless of elrail being enabled or disabled.
RailTypes railtypes{}; ///< Railtypes, mangled if elrail is disabled.
RailTypes intended_railtypes{}; ///< Intended railtypes, regardless of elrail being enabled or disabled.
uint8_t ai_passenger_only = 0; ///< Bit value to tell AI that this engine is for passenger use only
uint16_t max_speed = 0; ///< Maximum speed (1 unit = 1/1.6 mph = 1 km-ish/h)
uint16_t power = 0; ///< Power of engine (hp); For multiheaded engines the sum of both engine powers.

View File

@ -131,7 +131,7 @@ int GroundVehicle<T, Type>::GetAcceleration() const
*/
int64_t resistance = 0;
bool maglev = v->GetAccelerationType() == 2;
bool maglev = v->GetAccelerationType() == VehicleAccelerationModel::Maglev;
const int area = v->GetAirDragArea();
if (!maglev) {

View File

@ -4173,6 +4173,7 @@ STR_PURCHASE_INFO_ALL_BUT :All but {CARGO_
STR_PURCHASE_INFO_MAX_TE :{BLACK}Max. Tractive Effort: {GOLD}{FORCE}
STR_PURCHASE_INFO_AIRCRAFT_RANGE :{BLACK}Range: {GOLD}{COMMA} tiles
STR_PURCHASE_INFO_AIRCRAFT_TYPE :{BLACK}Aircraft type: {GOLD}{STRING}
STR_PURCHASE_INFO_RAILTYPES :{BLACK}Rail types: {GOLD}{RAW_STRING}
###length 3
STR_CARGO_TYPE_FILTER_ALL :All cargo types
@ -4356,6 +4357,7 @@ STR_ENGINE_PREVIEW_RUNCOST_YEAR :Running Cost: {
STR_ENGINE_PREVIEW_RUNCOST_PERIOD :Running Cost: {CURRENCY_LONG}/period
STR_ENGINE_PREVIEW_CAPACITY :Capacity: {CARGO_LONG}
STR_ENGINE_PREVIEW_CAPACITY_2 :Capacity: {CARGO_LONG}, {CARGO_LONG}
STR_ENGINE_PREVIEW_RAILTYPES :Rail types: {RAW_STRING}
# Autoreplace window
STR_REPLACE_VEHICLES_WHITE :{WHITE}Replace {STRING} - {STRING1}

View File

@ -270,7 +270,8 @@ Engine *GetNewEngine(const GRFFile *file, VehicleType type, uint16_t internal_id
_gted.resize(Engine::GetPoolSize());
}
if (type == VEH_TRAIN) {
_gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label;
_gted[e->index].railtypelabels.clear();
for (RailType rt : e->u.rail.railtypes) _gted[e->index].railtypelabels.push_back(GetRailTypeInfo(rt)->label);
}
GrfMsg(5, "Created new engine at index {} for GRFID {:x}, type {}, index {}", e->index, std::byteswap(file->grfid), type, internal_id);
@ -424,7 +425,8 @@ void ResetNewGRFData()
/* Fill rail type label temporary data for default trains */
for (const Engine *e : Engine::IterateType(VEH_TRAIN)) {
_gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label;
_gted[e->index].railtypelabels.clear();
for (RailType rt : e->u.rail.railtypes) _gted[e->index].railtypelabels.push_back(GetRailTypeInfo(rt)->label);
}
/* Reset GRM reservations */
@ -861,7 +863,11 @@ static void FinaliseEngineArray()
if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue;
switch (e->type) {
case VEH_TRAIN: AppendCopyableBadgeList(e->badges, GetRailTypeInfo(e->u.rail.railtype)->badges, GSF_TRAINS); break;
case VEH_TRAIN:
for (RailType rt : e->u.rail.railtypes) {
AppendCopyableBadgeList(e->badges, GetRailTypeInfo(rt)->badges, GSF_TRAINS);
}
break;
case VEH_ROAD: AppendCopyableBadgeList(e->badges, GetRoadTypeInfo(e->u.road.roadtype)->badges, GSF_ROADVEHICLES); break;
default: break;
}
@ -1708,13 +1714,18 @@ static void AfterLoadGRFs()
}
for (Engine *e : Engine::IterateType(VEH_TRAIN)) {
RailType railtype = GetRailTypeByLabel(_gted[e->index].railtypelabel);
if (railtype == INVALID_RAILTYPE) {
RailTypes railtypes{};
for (RailTypeLabel label : _gted[e->index].railtypelabels) {
auto rt = GetRailTypeByLabel(label);
if (rt != INVALID_RAILTYPE) railtypes.Set(rt);
}
if (railtypes.Any()) {
e->u.rail.railtypes = railtypes;
e->u.rail.intended_railtypes = railtypes;
} else {
/* Rail type is not available, so disable this engine */
e->info.climates = {};
} else {
e->u.rail.railtype = railtype;
e->u.rail.intended_railtype = railtype;
}
}

View File

@ -117,7 +117,7 @@ static ChangeInfoResult RailTypeChangeInfo(uint first, uint last, int prop, Byte
break;
case 0x15: // Acceleration model
rti->acceleration_type = Clamp(buf.ReadByte(), 0, 2);
rti->acceleration_type = static_cast<VehicleAccelerationModel>(Clamp(buf.ReadByte(), 0, 2));
break;
case 0x16: // Map colour

View File

@ -41,15 +41,16 @@ ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, ByteRead
case 0x05: { // Track type
uint8_t tracktype = buf.ReadByte();
_gted[e->index].railtypelabels.clear();
if (tracktype < _cur_gps.grffile->railtype_list.size()) {
_gted[e->index].railtypelabel = _cur_gps.grffile->railtype_list[tracktype];
_gted[e->index].railtypelabels.push_back(_cur_gps.grffile->railtype_list[tracktype]);
break;
}
switch (tracktype) {
case 0: _gted[e->index].railtypelabel = rvi->engclass >= 2 ? RAILTYPE_LABEL_ELECTRIC : RAILTYPE_LABEL_RAIL; break;
case 1: _gted[e->index].railtypelabel = RAILTYPE_LABEL_MONO; break;
case 2: _gted[e->index].railtypelabel = RAILTYPE_LABEL_MAGLEV; break;
case 0: _gted[e->index].railtypelabels.push_back(rvi->engclass >= 2 ? RAILTYPE_LABEL_ELECTRIC : RAILTYPE_LABEL_RAIL); break;
case 1: _gted[e->index].railtypelabels.push_back(RAILTYPE_LABEL_MONO); break;
case 2: _gted[e->index].railtypelabels.push_back(RAILTYPE_LABEL_MAGLEV); break;
default:
GrfMsg(1, "RailVehicleChangeInfo: Invalid track type {} specified, ignoring", tracktype);
break;
@ -179,11 +180,11 @@ ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, ByteRead
break;
}
if (_cur_gps.grffile->railtype_list.empty()) {
if (_cur_gps.grffile->railtype_list.empty() && !_gted[e->index].railtypelabels.empty()) {
/* Use traction type to select between normal and electrified
* rail only when no translation list is in place. */
if (_gted[e->index].railtypelabel == RAILTYPE_LABEL_RAIL && engclass >= EC_ELECTRIC) _gted[e->index].railtypelabel = RAILTYPE_LABEL_ELECTRIC;
if (_gted[e->index].railtypelabel == RAILTYPE_LABEL_ELECTRIC && engclass < EC_ELECTRIC) _gted[e->index].railtypelabel = RAILTYPE_LABEL_RAIL;
if (_gted[e->index].railtypelabels[0] == RAILTYPE_LABEL_RAIL && engclass >= EC_ELECTRIC) _gted[e->index].railtypelabels[0] = RAILTYPE_LABEL_ELECTRIC;
if (_gted[e->index].railtypelabels[0] == RAILTYPE_LABEL_ELECTRIC && engclass < EC_ELECTRIC) _gted[e->index].railtypelabels[0] = RAILTYPE_LABEL_RAIL;
}
rvi->engclass = engclass;
@ -327,6 +328,22 @@ ChangeInfoResult RailVehicleChangeInfo(uint first, uint last, int prop, ByteRead
e->badges = ReadBadgeList(buf, GSF_TRAINS);
break;
case 0x34: { // List of track types
uint8_t count = buf.ReadByte();
_gted[e->index].railtypelabels.clear();
while (count--) {
uint8_t tracktype = buf.ReadByte();
if (tracktype < _cur_gps.grffile->railtype_list.size()) {
_gted[e->index].railtypelabels.push_back(_cur_gps.grffile->railtype_list[tracktype]);
} else {
GrfMsg(1, "RailVehicleChangeInfo: Invalid track type {} specified, ignoring", tracktype);
}
}
break;
}
default:
ret = CommonVehicleChangeInfo(ei, prop, buf);
break;

View File

@ -27,7 +27,7 @@ struct GRFTempEngineData {
CargoClasses cargo_allowed; ///< Bitmask of cargo classes that are allowed as a refit.
CargoClasses cargo_allowed_required; ///< Bitmask of cargo classes that are required to be all present to allow a cargo as a refit.
CargoClasses cargo_disallowed; ///< Bitmask of cargo classes that are disallowed as a refit.
RailTypeLabel railtypelabel;
std::vector<RailTypeLabel> railtypelabels;
uint8_t roadtramtype;
const GRFFile *defaultcargo_grf; ///< GRF defining the cargo translation table to use if the default cargo is the 'first refittable'.
Refittability refittability; ///< Did the newgrf set any refittability property? If not, default refittability will be applied.

View File

@ -565,7 +565,7 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
RailType rt = GetTileRailType(v->tile);
const RailTypeInfo *rti = GetRailTypeInfo(rt);
return (rti->flags.Test(RailTypeFlag::Catenary) ? 0x200 : 0) |
(HasPowerOnRail(Train::From(v)->railtype, rt) ? 0x100 : 0) |
(HasPowerOnRail(Train::From(v)->railtypes, rt) ? 0x100 : 0) |
GetReverseRailTypeTranslation(rt, object->ro.grffile);
}
@ -721,7 +721,7 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
const Train *u = is_powered_wagon ? t->First() : t; // for powered wagons the engine defines the type of engine (i.e. railtype)
RailType railtype = GetRailType(v->tile);
bool powered = t->IsEngine() || is_powered_wagon;
bool has_power = HasPowerOnRail(u->railtype, railtype);
bool has_power = HasPowerOnRail(u->railtypes, railtype);
if (powered && has_power) SetBit(modflags, 5);
if (powered && !has_power) SetBit(modflags, 6);
@ -904,7 +904,7 @@ static uint32_t VehicleGetVariable(Vehicle *v, const VehicleScopeResolver *objec
Train *t = Train::From(v);
switch (variable - 0x80) {
case 0x62: return t->track;
case 0x66: return t->railtype;
case 0x66: return t->railtypes.GetNthSetBit(0).value_or(RailType::INVALID_RAILTYPE);
case 0x73: return 0x80 + VEHICLE_LENGTH - t->gcache.cached_veh_length;
case 0x74: return t->gcache.cached_power;
case 0x75: return GB(t->gcache.cached_power, 8, 24);

View File

@ -22,7 +22,7 @@ public:
void SetDestination(const Train *v, bool override_rail_type = false)
{
this->compatible_railtypes = v->compatible_railtypes;
if (override_rail_type) this->compatible_railtypes.Set(GetRailTypeInfo(v->railtype)->compatible_railtypes);
if (override_rail_type) this->compatible_railtypes.Set(GetAllCompatibleRailTypes(v->railtypes));
}
bool IsCompatibleRailType(RailType rt)

View File

@ -296,7 +296,7 @@ PBSTileInfo FollowTrainReservation(const Train *v, Vehicle **train_on_res)
if (IsRailDepotTile(tile) && !GetDepotReservationTrackBits(tile)) return PBSTileInfo(tile, trackdir, false);
FindTrainOnTrackInfo ftoti;
ftoti.res = FollowReservation(v->owner, GetRailTypeInfo(v->railtype)->compatible_railtypes, tile, trackdir);
ftoti.res = FollowReservation(v->owner, GetAllCompatibleRailTypes(v->railtypes), tile, trackdir);
ftoti.res.okay = IsSafeWaitingPosition(v, ftoti.res.tile, ftoti.res.trackdir, true, _settings_game.pf.forbid_90_deg);
if (train_on_res != nullptr) {
CheckTrainsOnTrack(ftoti, ftoti.res.tile);
@ -388,7 +388,7 @@ bool IsSafeWaitingPosition(const Train *v, TileIndex tile, Trackdir trackdir, bo
}
/* Check next tile. For performance reasons, we check for 90 degree turns ourself. */
CFollowTrackRail ft(v, GetRailTypeInfo(v->railtype)->compatible_railtypes);
CFollowTrackRail ft(v, GetAllCompatibleRailTypes(v->railtypes));
/* End of track? */
if (!ft.Follow(tile, trackdir)) {

View File

@ -262,11 +262,13 @@ RailTypes GetCompanyRailTypes(CompanyID company, bool introduces)
const RailVehicleInfo *rvi = &e->u.rail;
if (rvi->railveh_type != RAILVEH_WAGON) {
assert(rvi->railtype < RAILTYPE_END);
assert(rvi->railtypes.Any());
if (introduces) {
rts.Set(GetRailTypeInfo(rvi->railtype)->introduces_railtypes);
for (RailType rt : rvi->railtypes) {
rts.Set(GetRailTypeInfo(rt)->introduces_railtypes);
}
} else {
rts.Set(rvi->railtype);
rts.Set(rvi->railtypes);
}
}
}
@ -291,11 +293,13 @@ RailTypes GetRailTypes(bool introduces)
const RailVehicleInfo *rvi = &e->u.rail;
if (rvi->railveh_type != RAILVEH_WAGON) {
assert(rvi->railtype < RAILTYPE_END);
assert(rvi->railtypes.Any());
if (introduces) {
rts.Set(GetRailTypeInfo(rvi->railtype)->introduces_railtypes);
for (RailType rt : rvi->railtypes) {
rts.Set(GetRailTypeInfo(rt)->introduces_railtypes);
}
} else {
rts.Set(rvi->railtype);
rts.Set(rvi->railtypes);
}
}
}

View File

@ -214,7 +214,7 @@ public:
/**
* Acceleration type of this rail type
*/
uint8_t acceleration_type;
VehicleAccelerationModel acceleration_type;
/**
* Maximum speed for vehicles travelling on this rail type
@ -317,6 +317,30 @@ inline RailType GetRailTypeInfoIndex(const RailTypeInfo *rti)
return static_cast<RailType>(index);
}
/**
* Returns all compatible railtypes for a set of railtypes.
* @param railtypes Set of railtypes to get the compatible railtypes from.
* @return Union of all compatible railtypes.
*/
inline RailTypes GetAllCompatibleRailTypes(RailTypes railtypes)
{
RailTypes compatible{};
for (RailType rt : railtypes) compatible |= GetRailTypeInfo(rt)->compatible_railtypes;
return compatible;
}
/**
* Returns all powered railtypes for a set of railtypes.
* @param railtypes Set of railtypes to get the powered railtypes from.
* @return Union of all powered railtypes.
*/
inline RailTypes GetAllPoweredRailTypes(RailTypes railtypes)
{
RailTypes powered{};
for (RailType rt : railtypes) powered |= GetRailTypeInfo(rt)->powered_railtypes;
return powered;
}
/**
* Checks if an engine of the given RailType can drive on a tile with a given
* RailType. This would normally just be an equality check, but for electric
@ -330,6 +354,18 @@ inline bool IsCompatibleRail(RailType enginetype, RailType tiletype)
return GetRailTypeInfo(enginetype)->compatible_railtypes.Test(tiletype);
}
/**
* Checks if an engine of the given RailTypes can drive on a tile with a given
* RailType.
* @param enginetype The RailTypes of the engine we are considering.
* @param tiletype The RailType of the tile we are considering.
* @return Whether the engine can drive on this tile.
*/
inline bool IsCompatibleRail(RailTypes enginetype, RailType tiletype)
{
return GetAllCompatibleRailTypes(enginetype).Test(tiletype);
}
/**
* Checks if an engine of the given RailType got power on a tile with a given
* RailType. This would normally just be an equality check, but for electric
@ -343,6 +379,18 @@ inline bool HasPowerOnRail(RailType enginetype, RailType tiletype)
return GetRailTypeInfo(enginetype)->powered_railtypes.Test(tiletype);
}
/**
* Checks if an engine of the given RailTypes got power on a tile with a given
* RailType.
* @param enginetype The RailTypes of the engine we are considering.
* @param tiletype The RailType of the tile we are considering.
* @return Whether the engine got power on this tile.
*/
inline bool HasPowerOnRail(RailTypes enginetype, RailType tiletype)
{
return GetAllPoweredRailTypes(enginetype).Test(tiletype);
}
/**
* Test if a RailType disallows build of level crossings.
* @param rt The RailType to check.

View File

@ -1605,7 +1605,7 @@ CommandCost CmdConvertRail(DoCommandFlags flags, TileIndex tile, TileIndex area_
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *v = GetTrainForReservation(tile, track);
if (v != nullptr && !HasPowerOnRail(v->railtype, totype)) {
if (v != nullptr && !HasPowerOnRail(v->railtypes, totype)) {
/* No power on new rail type, reroute. */
FreeTrainTrackReservation(v);
vehicles_affected.push_back(v);
@ -1691,7 +1691,7 @@ CommandCost CmdConvertRail(DoCommandFlags flags, TileIndex tile, TileIndex area_
Track track = DiagDirToDiagTrack(GetTunnelBridgeDirection(tile));
if (HasTunnelBridgeReservation(tile)) {
Train *v = GetTrainForReservation(tile, track);
if (v != nullptr && !HasPowerOnRail(v->railtype, totype)) {
if (v != nullptr && !HasPowerOnRail(v->railtypes, totype)) {
/* No power on new rail type, reroute. */
FreeTrainTrackReservation(v);
vehicles_affected.push_back(v);

View File

@ -250,11 +250,11 @@ protected: // These functions should not be called outside acceleration code.
/**
* Allows to know the acceleration type of a vehicle.
* @return Zero, road vehicles always use a normal acceleration method.
* @return \c VehicleAccelerationModel::Normal, road vehicles always use a normal acceleration method.
*/
inline int GetAccelerationType() const
inline VehicleAccelerationModel GetAccelerationType() const
{
return 0;
return VehicleAccelerationModel::Normal;
}
/**

View File

@ -1333,10 +1333,10 @@ bool AfterLoadGame()
RailType min_rail = RAILTYPE_ELECTRIC;
for (Train *v : Train::Iterate()) {
RailType rt = RailVehInfo(v->engine_type)->railtype;
RailTypes rts = RailVehInfo(v->engine_type)->railtypes;
v->railtype = rt;
if (rt == RAILTYPE_ELECTRIC) min_rail = RAILTYPE_RAIL;
v->railtypes = rts;
if (rts.Test(RAILTYPE_ELECTRIC)) min_rail = RAILTYPE_RAIL;
}
/* .. so we convert the entire map from normal to elrail (so maintain "fairness") */

View File

@ -1050,12 +1050,13 @@ static bool LoadOldCompany(LoadgameState &ls, int num)
static uint32_t _old_order_ptr;
static uint16_t _old_next_ptr;
static typename VehicleID::BaseType _current_vehicle_id;
static RailType _old_railtype;
static const OldChunks vehicle_train_chunk[] = {
OCL_SVAR( OC_UINT8, Train, track ),
OCL_SVAR( OC_UINT8, Train, force_proceed ),
OCL_SVAR( OC_UINT16, Train, crash_anim_pos ),
OCL_SVAR( OC_UINT8, Train, railtype ),
OCL_VAR ( OC_UINT8, 1, &_old_railtype),
OCL_NULL( 5 ), ///< Junk
@ -1296,7 +1297,7 @@ bool LoadOldVehicle(LoadgameState &ls, int num)
if (v->spritenum / 2 >= lengthof(spriteset_rail)) return false;
v->spritenum = spriteset_rail[v->spritenum / 2]; // adjust railway sprite set offset
/* Should be the original values for monorail / rail, can't use RailType constants */
Train::From(v)->railtype = static_cast<RailType>(type == 0x25 ? 1 : 0);
Train::From(v)->railtypes = RailTypes(static_cast<RailType>(type == 0x25 ? 1 : 0));
break;
}
@ -1357,6 +1358,10 @@ bool LoadOldVehicle(LoadgameState &ls, int num)
Debug(oldloader, 0, "Loading failed - vehicle-array is invalid");
return false;
}
if (v->type == VEH_TRAIN) {
Train::From(v)->railtypes = RailTypes(_old_railtype);
}
}
if (_old_order_ptr != 0 && _old_order_ptr != 0xFFFFFFFF) {

View File

@ -403,6 +403,8 @@ enum SaveLoadVersion : uint16_t {
SLV_FIX_SCC_ENCODED_NEGATIVE, ///< 353 PR#14049 Fix encoding of negative parameters.
SLV_ORDERS_OWNED_BY_ORDERLIST, ///< 354 PR#13948 Orders stored in OrderList, pool removed.
SLV_ENGINE_MULTI_RAILTYPE, ///< 355 PR#14357 Train engines can have multiple railtypes.
SL_MAX_VERSION, ///< Highest possible saveload version
};

View File

@ -793,13 +793,16 @@ public:
}
};
static RailType _old_railtype;
class SlVehicleTrain : public DefaultSaveLoadHandler<SlVehicleTrain, Vehicle> {
public:
static inline const SaveLoad description[] = {
SLEG_STRUCT("common", SlVehicleCommon),
SLE_VAR(Train, crash_anim_pos, SLE_UINT16),
SLE_VAR(Train, force_proceed, SLE_UINT8),
SLE_VAR(Train, railtype, SLE_UINT8),
SLEG_CONDVAR("railtype", _old_railtype, SLE_UINT8, SL_MIN_VERSION, SLV_ENGINE_MULTI_RAILTYPE),
SLE_VAR(Train, railtypes, SLE_UINT64),
SLE_VAR(Train, track, SLE_UINT8),
SLE_CONDVAR(Train, flags, SLE_FILE_U8 | SLE_VAR_U16, SLV_2, SLV_100),
@ -819,6 +822,10 @@ public:
{
if (v->type != VEH_TRAIN) return;
SlObject(v, this->GetLoadDescription());
if (IsSavegameVersionBefore(SLV_ENGINE_MULTI_RAILTYPE)) {
Train::From(v)->railtypes = RailTypes(_old_railtype);
}
}
void FixPointers(Vehicle *v) const override

View File

@ -28,6 +28,7 @@
* \li AICargo::CC_POTABLE
* \li AICargo::CC_NON_POTABLE
* \li AIVehicleList_Waypoint
* \li AIRail::GetAllRailTypes
*
* Other changes:
* \li AIBridge::GetBridgeID renamed to AIBridge::GetBridgeType
@ -35,6 +36,7 @@
* \li AIList instances can now be saved
* \li AIVehicleList_Station accepts an optional AIVehicle::VehicleType parameter
* \li AIList instances can now be cloned
* \li AIRail::GetRailType will only return the first RailType of an engine, use AIRail::GetAllRailTypes instead
*
* \b 14.0
*

View File

@ -29,6 +29,7 @@
* \li GSCargo::CC_NON_POTABLE
* \li GSVehicleList_Waypoint
* \li GSBaseStation::GetOwner
* \li GSRail::GetAllRailTypes
*
* Other changes:
* \li GSBridge::GetBridgeID renamed to GSBridge::GetBridgeType
@ -36,6 +37,7 @@
* \li GSList instances can now be saved
* \li GSVehicleList_Station accepts an optional GSVehicle::VehicleType parameter
* \li GSList instances can now be cloned
* \li GSRail::GetRailType will only return the first RailType of an engine, use GSRail::GetAllRailTypes instead
*
* \b 14.0
*

View File

@ -203,7 +203,7 @@
if (GetVehicleType(engine_id) != ScriptVehicle::VT_RAIL) return false;
if (!ScriptRail::IsRailTypeAvailable(track_rail_type)) return false;
return ::IsCompatibleRail((::RailType)::RailVehInfo(engine_id)->railtype, (::RailType)track_rail_type);
return ::IsCompatibleRail(::RailVehInfo(engine_id)->railtypes, (::RailType)track_rail_type);
}
/* static */ bool ScriptEngine::HasPowerOnRail(EngineID engine_id, ScriptRail::RailType track_rail_type)
@ -212,7 +212,7 @@
if (GetVehicleType(engine_id) != ScriptVehicle::VT_RAIL) return false;
if (!ScriptRail::IsRailTypeAvailable(track_rail_type)) return false;
return ::HasPowerOnRail((::RailType)::RailVehInfo(engine_id)->railtype, (::RailType)track_rail_type);
return ::HasPowerOnRail(::RailVehInfo(engine_id)->railtypes, (::RailType)track_rail_type);
}
/* static */ bool ScriptEngine::CanRunOnRoad(EngineID engine_id, ScriptRoad::RoadType road_type)
@ -242,7 +242,15 @@
if (!IsValidEngine(engine_id)) return ScriptRail::RAILTYPE_INVALID;
if (GetVehicleType(engine_id) != ScriptVehicle::VT_RAIL) return ScriptRail::RAILTYPE_INVALID;
return (ScriptRail::RailType)(uint)::RailVehInfo(engine_id)->railtype;
return static_cast<ScriptRail::RailType>(::RailVehInfo(engine_id)->railtypes.GetNthSetBit(0).value_or(::RailType::INVALID_RAILTYPE));
}
/* static */ ScriptRail::RailTypes ScriptEngine::GetAllRailTypes(EngineID engine_id)
{
if (!IsValidEngine(engine_id)) return ScriptRail::INVALID_RAILTYPES;
if (GetVehicleType(engine_id) != ScriptVehicle::VT_RAIL) return ScriptRail::INVALID_RAILTYPES;
return static_cast<ScriptRail::RailTypes>(::RailVehInfo(engine_id)->railtypes.base());
}
/* static */ bool ScriptEngine::IsArticulated(EngineID engine_id)

View File

@ -248,14 +248,24 @@ public:
static ScriptRoad::RoadType GetRoadType(EngineID engine_id);
/**
* Get the RailType of the engine.
* Get the first RailType of the engine.
* @note This will only return the first RailType of a multi-system engine. Use GetAllRailTypes to get all rail types of the engine.
* @param engine_id The engine to get the RailType of.
* @pre IsValidEngine(engine_id).
* @pre GetVehicleType(engine_id) == ScriptVehicle::VT_RAIL.
* @return The RailType the engine has.
* @return The first RailType the engine has.
*/
static ScriptRail::RailType GetRailType(EngineID engine_id);
/**
* Get all RailType's of the engine.
* @param engine_id The engine to get all RailTypes of.
* @pre IsValidEngine(engine_id).
* @pre GetVehicleType(engine_id) == ScriptVehicle::VT_RAIL.
* @return All rail types of the engine.
*/
static ScriptRail::RailTypes GetAllRailTypes(EngineID engine_id);
/**
* Check if the engine is articulated.
* @param engine_id The engine to check.

View File

@ -49,6 +49,14 @@ public:
RAILTYPE_INVALID = ::INVALID_RAILTYPE, ///< Invalid RailType.
};
/**
* A bitmap with all possible rail types.
*/
enum RailTypes : int64_t {
/* Note: these values represent part of the in-game RailTypes enum */
INVALID_RAILTYPES = INT64_MAX, ///< Invalid RailTypes.
};
/**
* A bitmap with all possible rail tracks on a tile.
*/

View File

@ -270,7 +270,7 @@ void Squirrel::AddMethod(std::string_view method_name, SQFUNCTION proc, std::str
sq_newslot(this->vm, -3, SQFalse);
}
void Squirrel::AddConst(std::string_view var_name, int value)
void Squirrel::AddConst(std::string_view var_name, SQInteger value)
{
ScriptAllocatorScope alloc_scope(this);

View File

@ -104,15 +104,21 @@ public:
* Adds a const to the stack. Depending on the current state this means
* either a const to a class or to the global space.
*/
void AddConst(std::string_view var_name, int value);
void AddConst(std::string_view var_name, SQInteger value);
/**
* Adds a const to the stack. Depending on the current state this means
* either a const to a class or to the global space.
*/
void AddConst(std::string_view var_name, uint value) { this->AddConst(var_name, (int)value); }
void AddConst(std::string_view var_name, uint value) { this->AddConst(var_name, (SQInteger)value); }
void AddConst(std::string_view var_name, const ConvertibleThroughBase auto &value) { this->AddConst(var_name, static_cast<int>(value.base())); }
/**
* Adds a const to the stack. Depending on the current state this means
* either a const to a class or to the global space.
*/
void AddConst(std::string_view var_name, int value) { this->AddConst(var_name, (SQInteger)value); }
void AddConst(std::string_view var_name, const ConvertibleThroughBase auto &value) { this->AddConst(var_name, static_cast<SQInteger>(value.base())); }
/**
* Adds a const to the stack. Depending on the current state this means

View File

@ -86,7 +86,7 @@ static const RailTypeInfo _original_railtypes[] = {
8,
/* acceleration type */
0,
VehicleAccelerationModel::Normal,
/* max speed */
0,
@ -188,7 +188,7 @@ static const RailTypeInfo _original_railtypes[] = {
12,
/* acceleration type */
0,
VehicleAccelerationModel::Normal,
/* max speed */
0,
@ -286,7 +286,7 @@ static const RailTypeInfo _original_railtypes[] = {
16,
/* acceleration type */
1,
VehicleAccelerationModel::Monorail,
/* max speed */
0,
@ -384,7 +384,7 @@ static const RailTypeInfo _original_railtypes[] = {
24,
/* acceleration type */
2,
VehicleAccelerationModel::Maglev,
/* max speed */
0,

View File

@ -99,7 +99,7 @@ struct Train final : public GroundVehicle<Train, VEH_TRAIN> {
Train *other_multiheaded_part = nullptr;
RailTypes compatible_railtypes{};
RailType railtype = INVALID_RAILTYPE;
RailTypes railtypes{};
TrackBits track{};
TrainForceProceeding force_proceed{};
@ -180,6 +180,15 @@ struct Train final : public GroundVehicle<Train, VEH_TRAIN> {
return this->gcache.cached_veh_length / 2 + (this->Next() != nullptr ? this->Next()->gcache.cached_veh_length + 1 : 0) / 2;
}
/**
* Allows to know the acceleration type of a vehicle.
* @return Acceleration type of the vehicle.
*/
inline VehicleAccelerationModel GetAccelerationType() const
{
return GetRailTypeInfo(GetRailType(this->tile))->acceleration_type;
}
protected: // These functions should not be called outside acceleration code.
/**
@ -189,7 +198,7 @@ protected: // These functions should not be called outside acceleration code.
inline uint16_t GetPower() const
{
/* Power is not added for articulated parts */
if (!this->IsArticulatedPart() && HasPowerOnRail(this->railtype, GetRailType(this->tile))) {
if (!this->IsArticulatedPart() && HasPowerOnRail(this->railtypes, GetRailType(this->tile))) {
uint16_t power = GetVehicleProperty(this, PROP_TRAIN_POWER, RailVehInfo(this->engine_type)->power);
/* Halve power for multiheaded parts */
if (this->IsMultiheaded()) power /= 2;
@ -206,7 +215,7 @@ protected: // These functions should not be called outside acceleration code.
inline uint16_t GetPoweredPartPower(const Train *head) const
{
/* For powered wagons the engine defines the type of engine (i.e. railtype) */
if (this->flags.Test(VehicleRailFlag::PoweredWagon) && HasPowerOnRail(head->railtype, GetRailType(this->tile))) {
if (this->flags.Test(VehicleRailFlag::PoweredWagon) && HasPowerOnRail(head->railtypes, GetRailType(this->tile))) {
return RailVehInfo(this->gcache.first_engine)->pow_wag_power;
}
@ -298,15 +307,6 @@ protected: // These functions should not be called outside acceleration code.
return 15 * (512 + this->GetCurrentSpeed()) / 512;
}
/**
* Allows to know the acceleration type of a vehicle.
* @return Acceleration type of the vehicle.
*/
inline int GetAccelerationType() const
{
return GetRailTypeInfo(this->railtype)->acceleration_type;
}
/**
* Returns the slope steepness used by this vehicle.
* @return Slope steepness used by the vehicle.

View File

@ -127,7 +127,7 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes)
/* update the 'first engine' */
u->gcache.first_engine = this == u ? EngineID::Invalid() : first_engine;
u->railtype = rvi_u->railtype;
u->railtypes = rvi_u->railtypes;
if (u->IsEngine()) first_engine = u->engine_type;
@ -172,13 +172,13 @@ void Train::ConsistChanged(ConsistChangeFlags allowed_changes)
/* Do not count powered wagons for the compatible railtypes, as wagons always
have railtype normal */
if (rvi_u->power > 0) {
this->compatible_railtypes.Set(GetRailTypeInfo(u->railtype)->powered_railtypes);
this->compatible_railtypes.Set(GetAllPoweredRailTypes(u->railtypes));
}
/* Some electric engines can be allowed to run on normal rail. It happens to all
* existing electric engines when elrails are disabled and then re-enabled */
if (u->flags.Test(VehicleRailFlag::AllowedOnNormalRail)) {
u->railtype = RAILTYPE_RAIL;
u->railtypes.Set(RAILTYPE_RAIL);
u->compatible_railtypes.Set(RAILTYPE_RAIL);
}
@ -639,7 +639,7 @@ static CommandCost CmdBuildRailWagon(DoCommandFlags flags, TileIndex tile, const
const RailVehicleInfo *rvi = &e->u.rail;
/* Check that the wagon can drive on the track in question */
if (!IsCompatibleRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR;
if (!IsCompatibleRail(rvi->railtypes, GetRailType(tile))) return CMD_ERROR;
if (flags.Test(DoCommandFlag::Execute)) {
Train *v = new Train();
@ -674,7 +674,7 @@ static CommandCost CmdBuildRailWagon(DoCommandFlags flags, TileIndex tile, const
v->cargo_cap = rvi->capacity;
v->refit_cap = 0;
v->railtype = rvi->railtype;
v->railtypes = rvi->railtypes;
v->date_of_last_service = TimerGameEconomy::date;
v->date_of_last_service_newgrf = TimerGameCalendar::date;
@ -741,7 +741,7 @@ static void AddRearEngineToMultiheadedTrain(Train *v)
u->cargo_subtype = v->cargo_subtype;
u->cargo_cap = v->cargo_cap;
u->refit_cap = v->refit_cap;
u->railtype = v->railtype;
u->railtypes = v->railtypes;
u->engine_type = v->engine_type;
u->date_of_last_service = v->date_of_last_service;
u->date_of_last_service_newgrf = v->date_of_last_service_newgrf;
@ -776,7 +776,7 @@ CommandCost CmdBuildRailVehicle(DoCommandFlags flags, TileIndex tile, const Engi
/* Check if depot and new engine uses the same kind of tracks *
* We need to see if the engine got power on the tile to avoid electric engines in non-electric depots */
if (!HasPowerOnRail(rvi->railtype, GetRailType(tile))) return CMD_ERROR;
if (!HasPowerOnRail(rvi->railtypes, GetRailType(tile))) return CMD_ERROR;
if (flags.Test(DoCommandFlag::Execute)) {
DiagDirection dir = GetRailDepotDirection(tile);
@ -808,7 +808,7 @@ CommandCost CmdBuildRailVehicle(DoCommandFlags flags, TileIndex tile, const Engi
v->reliability_spd_dec = e->reliability_spd_dec;
v->max_age = e->GetLifeLengthInDays();
v->railtype = rvi->railtype;
v->railtypes = rvi->railtypes;
v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_trains);
v->date_of_last_service = TimerGameEconomy::date;
@ -2427,7 +2427,7 @@ void FreeTrainTrackReservation(const Train *v)
/* Don't free reservation if it's not ours. */
if (TracksOverlap(GetReservedTrackbits(tile) | TrackToTrackBits(TrackdirToTrack(td)))) return;
CFollowTrackRail ft(v, GetRailTypeInfo(v->railtype)->compatible_railtypes);
CFollowTrackRail ft(v, GetAllCompatibleRailTypes(v->railtypes));
while (ft.Follow(tile, td)) {
tile = ft.new_tile;
TrackdirBits bits = ft.new_td_bits & TrackBitsToTrackdirBits(GetReservedTrackbits(tile));
@ -3083,7 +3083,7 @@ static inline void AffectSpeedByZChange(Train *v, int old_z)
{
if (old_z == v->z_pos || _settings_game.vehicle.train_acceleration_model != AM_ORIGINAL) return;
const AccelerationSlowdownParams *asp = &_accel_slowdown[GetRailTypeInfo(v->railtype)->acceleration_type];
const AccelerationSlowdownParams *asp = &_accel_slowdown[static_cast<int>(v->GetAccelerationType())];
if (old_z < v->z_pos) {
v->cur_speed -= (v->cur_speed * asp->z_up >> 8);
@ -3490,7 +3490,7 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
if (chosen_dir != v->direction) {
if (prev == nullptr && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
const AccelerationSlowdownParams *asp = &_accel_slowdown[GetRailTypeInfo(v->railtype)->acceleration_type];
const AccelerationSlowdownParams *asp = &_accel_slowdown[static_cast<int>(v->GetAccelerationType())];
DirDiff diff = DirDifference(v->direction, chosen_dir);
v->cur_speed -= (diff == DIRDIFF_45RIGHT || diff == DIRDIFF_45LEFT ? asp->small_turn : asp->large_turn) * v->cur_speed >> 8;
}

View File

@ -2810,7 +2810,7 @@ void Vehicle::ShowVisualEffect() const
IsDepotTile(v->tile) ||
IsTunnelTile(v->tile) ||
(v->type == VEH_TRAIN &&
!HasPowerOnRail(Train::From(v)->railtype, GetTileRailType(v->tile)))) {
!HasPowerOnRail(Train::From(v)->railtypes, GetTileRailType(v->tile)))) {
continue;
}

View File

@ -2596,7 +2596,7 @@ struct VehicleDetailsWindow : Window {
(v->type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL)) {
const GroundVehicleCache *gcache = v->GetGroundVehicleCache();
if (v->type == VEH_TRAIN && (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL ||
GetRailTypeInfo(Train::From(v)->railtype)->acceleration_type == 2)) {
Train::From(v)->GetAccelerationType() == VehicleAccelerationModel::Maglev)) {
DrawString(tr, GetString(STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED, gcache->cached_weight, gcache->cached_power, max_speed));
} else {
DrawString(tr, GetString(STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE, gcache->cached_weight, gcache->cached_power, max_speed, gcache->cached_max_te));