From c719566a090d202ae8fed44f9f7fc4d26087e800 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Thu, 19 Jun 2025 21:09:37 +0200 Subject: [PATCH 1/5] Codechange: Use an enum for vehicle acceleration model. --- src/build_vehicle_gui.cpp | 2 +- src/engine_gui.cpp | 2 +- src/engine_type.h | 7 +++++++ src/ground_vehicle.cpp | 2 +- src/newgrf/newgrf_act0_railtypes.cpp | 2 +- src/rail.h | 2 +- src/roadveh.h | 6 +++--- src/table/railtypes.h | 8 ++++---- src/train.h | 2 +- src/train_cmd.cpp | 4 ++-- src/vehicle_gui.cpp | 2 +- 11 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index e713a7b5ec..578fc30644 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -637,7 +637,7 @@ static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engin 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) { + if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != VehicleAccelerationModel::Maglev) { DrawString(left, right, y, GetString(STR_PURCHASE_INFO_MAX_TE, e->GetDisplayMaxTractiveEffort())); y += GetCharacterHeight(FS_NORMAL); } diff --git a/src/engine_gui.cpp b/src/engine_gui.cpp index e219a45ff2..63a8a82620 100644 --- a/src/engine_gui.cpp +++ b/src/engine_gui.cpp @@ -181,7 +181,7 @@ 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 (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(e.u.rail.railtype)->acceleration_type != VehicleAccelerationModel::Maglev) { res << GetString(STR_ENGINE_PREVIEW_SPEED_POWER_MAX_TE, PackVelocity(e.GetDisplayMaxSpeed(), e.type), e.GetPower(), e.GetDisplayMaxTractiveEffort()); res << '\n'; } else { diff --git a/src/engine_type.h b/src/engine_type.h index 94f797430e..5d35a11bd4 100644 --- a/src/engine_type.h +++ b/src/engine_type.h @@ -43,6 +43,13 @@ 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; diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp index ca8fe65fcf..a5b638f09a 100644 --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -131,7 +131,7 @@ int GroundVehicle::GetAcceleration() const */ int64_t resistance = 0; - bool maglev = v->GetAccelerationType() == 2; + bool maglev = v->GetAccelerationType() == VehicleAccelerationModel::Maglev; const int area = v->GetAirDragArea(); if (!maglev) { diff --git a/src/newgrf/newgrf_act0_railtypes.cpp b/src/newgrf/newgrf_act0_railtypes.cpp index fe9e52132d..3d503cfc76 100644 --- a/src/newgrf/newgrf_act0_railtypes.cpp +++ b/src/newgrf/newgrf_act0_railtypes.cpp @@ -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(Clamp(buf.ReadByte(), 0, 2)); break; case 0x16: // Map colour diff --git a/src/rail.h b/src/rail.h index 323dd00c24..6e428382d7 100644 --- a/src/rail.h +++ b/src/rail.h @@ -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 diff --git a/src/roadveh.h b/src/roadveh.h index 56aae919c4..8657512d1e 100644 --- a/src/roadveh.h +++ b/src/roadveh.h @@ -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; } /** diff --git a/src/table/railtypes.h b/src/table/railtypes.h index 3f687545bb..b8065561a3 100644 --- a/src/table/railtypes.h +++ b/src/table/railtypes.h @@ -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, diff --git a/src/train.h b/src/train.h index b7ff1a6581..ee1b499c1c 100644 --- a/src/train.h +++ b/src/train.h @@ -302,7 +302,7 @@ protected: // These functions should not be called outside acceleration code. * Allows to know the acceleration type of a vehicle. * @return Acceleration type of the vehicle. */ - inline int GetAccelerationType() const + inline VehicleAccelerationModel GetAccelerationType() const { return GetRailTypeInfo(this->railtype)->acceleration_type; } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 4d3428e9f5..3715e87f94 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -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(GetRailTypeInfo(v->railtype)->acceleration_type)]; 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(GetRailTypeInfo(v->railtype)->acceleration_type)]; 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; } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 6727ed5796..306df4c62b 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -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)) { + GetRailTypeInfo(Train::From(v)->railtype)->acceleration_type == 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)); From f468ec79da8218b6b13b9ddaa273f08ae317677d Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 13 Jun 2025 21:01:31 +0200 Subject: [PATCH 2/5] Codechange: Store the rail type of rail engines as a RailTypes bitmask. --- src/articulated_vehicles.cpp | 2 +- src/autoreplace_cmd.cpp | 2 +- src/autoreplace_gui.cpp | 2 +- src/build_vehicle_gui.cpp | 14 +++++--- src/elrail.cpp | 13 ++++---- src/engine.cpp | 10 ++++-- src/engine_gui.cpp | 10 ++++-- src/engine_type.h | 4 +-- src/newgrf.cpp | 27 ++++++++++----- src/newgrf/newgrf_act0_trains.cpp | 15 +++++---- src/newgrf/newgrf_internal_vehicle.h | 2 +- src/newgrf_engine.cpp | 6 ++-- src/pathfinder/yapf/yapf_destrail.hpp | 2 +- src/pbs.cpp | 4 +-- src/rail.cpp | 16 +++++---- src/rail.h | 48 +++++++++++++++++++++++++++ src/rail_cmd.cpp | 4 +-- src/saveload/afterload.cpp | 6 ++-- src/saveload/oldloader_sl.cpp | 9 +++-- src/saveload/saveload.h | 2 ++ src/saveload/vehicle_sl.cpp | 9 ++++- src/script/api/script_engine.cpp | 6 ++-- src/script/api/script_engine.hpp | 4 +-- src/train.h | 24 +++++++------- src/train_cmd.cpp | 22 ++++++------ src/vehicle.cpp | 2 +- src/vehicle_gui.cpp | 2 +- 27 files changed, 180 insertions(+), 87 deletions(-) diff --git a/src/articulated_vehicles.cpp b/src/articulated_vehicles.cpp index ffb2fc052b..9ffc0a6e8f 100644 --- a/src/articulated_vehicles.cpp +++ b/src/articulated_vehicles.cpp @@ -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()) { diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index df64561cf3..b29ad5701c 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -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; diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 804dd92a9d..92132abaef 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -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; } diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 578fc30644..1186cc63ae 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -637,9 +637,15 @@ static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engin 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 != VehicleAccelerationModel::Maglev) { - 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 +1384,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 */ diff --git a/src/elrail.cpp b/src/elrail.cpp index 7b8f5208d0..978fea4dc4 100644 --- a/src/elrail.cpp +++ b/src/elrail.cpp @@ -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); } } diff --git a/src/engine.cpp b/src/engine.cpp index a63b5a1954..c962f3715e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -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 */ diff --git a/src/engine_gui.cpp b/src/engine_gui.cpp index 63a8a82620..764a2148c3 100644 --- a/src/engine_gui.cpp +++ b/src/engine_gui.cpp @@ -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,12 @@ 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 != VehicleAccelerationModel::Maglev) { + 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 { diff --git a/src/engine_type.h b/src/engine_type.h index 5d35a11bd4..1fefbe1ba1 100644 --- a/src/engine_type.h +++ b/src/engine_type.h @@ -55,8 +55,8 @@ 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. diff --git a/src/newgrf.cpp b/src/newgrf.cpp index c1d9904ad2..ef2aa7e7f4 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -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; } } diff --git a/src/newgrf/newgrf_act0_trains.cpp b/src/newgrf/newgrf_act0_trains.cpp index 445dbf294b..cc550130d1 100644 --- a/src/newgrf/newgrf_act0_trains.cpp +++ b/src/newgrf/newgrf_act0_trains.cpp @@ -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; diff --git a/src/newgrf/newgrf_internal_vehicle.h b/src/newgrf/newgrf_internal_vehicle.h index 185382c530..05a35b286a 100644 --- a/src/newgrf/newgrf_internal_vehicle.h +++ b/src/newgrf/newgrf_internal_vehicle.h @@ -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 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. diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index e37ed01cf4..114c41c583 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -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); diff --git a/src/pathfinder/yapf/yapf_destrail.hpp b/src/pathfinder/yapf/yapf_destrail.hpp index 1133a66eec..c5217038de 100644 --- a/src/pathfinder/yapf/yapf_destrail.hpp +++ b/src/pathfinder/yapf/yapf_destrail.hpp @@ -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) diff --git a/src/pbs.cpp b/src/pbs.cpp index 8537c17aa5..f47c990c6d 100644 --- a/src/pbs.cpp +++ b/src/pbs.cpp @@ -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)) { diff --git a/src/rail.cpp b/src/rail.cpp index c5e0d7a6cb..80e43e2e84 100644 --- a/src/rail.cpp +++ b/src/rail.cpp @@ -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); } } } diff --git a/src/rail.h b/src/rail.h index 6e428382d7..06d31b3634 100644 --- a/src/rail.h +++ b/src/rail.h @@ -317,6 +317,30 @@ inline RailType GetRailTypeInfoIndex(const RailTypeInfo *rti) return static_cast(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. diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp index 35ed96ade6..de667619b5 100644 --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -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); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 4e6899ea36..10f5d8e029 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -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") */ diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp index 46585f0eab..797bbd98d7 100644 --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -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(type == 0x25 ? 1 : 0); + Train::From(v)->railtypes = RailTypes(static_cast(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) { diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index ec78153db1..f231aee5bb 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -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 }; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 908793dc5c..78ab48366d 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -793,13 +793,16 @@ public: } }; +static RailType _old_railtype; + class SlVehicleTrain : public DefaultSaveLoadHandler { 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 diff --git a/src/script/api/script_engine.cpp b/src/script/api/script_engine.cpp index e7cf0a025a..92e72522ed 100644 --- a/src/script/api/script_engine.cpp +++ b/src/script/api/script_engine.cpp @@ -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,7 @@ 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(::RailVehInfo(engine_id)->railtypes.GetNthSetBit(0).value_or(::RailType::INVALID_RAILTYPE)); } /* static */ bool ScriptEngine::IsArticulated(EngineID engine_id) diff --git a/src/script/api/script_engine.hpp b/src/script/api/script_engine.hpp index b1dd9141ad..84bf229efa 100644 --- a/src/script/api/script_engine.hpp +++ b/src/script/api/script_engine.hpp @@ -248,11 +248,11 @@ public: static ScriptRoad::RoadType GetRoadType(EngineID engine_id); /** - * Get the RailType of the engine. + * Get the first RailType 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); diff --git a/src/train.h b/src/train.h index ee1b499c1c..f4bd7889ad 100644 --- a/src/train.h +++ b/src/train.h @@ -99,7 +99,7 @@ struct Train final : public GroundVehicle { 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 { 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 VehicleAccelerationModel GetAccelerationType() const - { - return GetRailTypeInfo(this->railtype)->acceleration_type; - } - /** * Returns the slope steepness used by this vehicle. * @return Slope steepness used by the vehicle. diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 3715e87f94..2859926bcf 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -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[static_cast(GetRailTypeInfo(v->railtype)->acceleration_type)]; + const AccelerationSlowdownParams *asp = &_accel_slowdown[static_cast(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[static_cast(GetRailTypeInfo(v->railtype)->acceleration_type)]; + const AccelerationSlowdownParams *asp = &_accel_slowdown[static_cast(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; } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 4765b07c41..cb7d26e46d 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -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; } diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 306df4c62b..af816df884 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -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 == VehicleAccelerationModel::Maglev)) { + 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)); From 164423a42429bfb1bda27f18879faa2d02c3e43c Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 13 Jun 2025 21:19:34 +0200 Subject: [PATCH 3/5] Add: [NewGRF] Train property to set multiple track types for an engine. --- src/newgrf/newgrf_act0_trains.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/newgrf/newgrf_act0_trains.cpp b/src/newgrf/newgrf_act0_trains.cpp index cc550130d1..8f8a4c4121 100644 --- a/src/newgrf/newgrf_act0_trains.cpp +++ b/src/newgrf/newgrf_act0_trains.cpp @@ -328,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; From d59095aa5e0c0309c76de9cab5b2991912e13626 Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 13 Jun 2025 21:20:29 +0200 Subject: [PATCH 4/5] Add: [Script] Function to get all rail types of an rail engine. --- src/script/api/ai_changelog.hpp | 2 ++ src/script/api/game_changelog.hpp | 2 ++ src/script/api/script_engine.cpp | 8 ++++++++ src/script/api/script_engine.hpp | 10 ++++++++++ src/script/api/script_rail.hpp | 8 ++++++++ src/script/squirrel.cpp | 2 +- src/script/squirrel.hpp | 12 +++++++++--- 7 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp index f76a586f07..6ac3bac4bb 100644 --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -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 * diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index 7bb7203d20..00beafe55f 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -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 * diff --git a/src/script/api/script_engine.cpp b/src/script/api/script_engine.cpp index 92e72522ed..5b6ced3e13 100644 --- a/src/script/api/script_engine.cpp +++ b/src/script/api/script_engine.cpp @@ -245,6 +245,14 @@ return static_cast(::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(::RailVehInfo(engine_id)->railtypes.base()); +} + /* static */ bool ScriptEngine::IsArticulated(EngineID engine_id) { if (!IsValidEngine(engine_id)) return false; diff --git a/src/script/api/script_engine.hpp b/src/script/api/script_engine.hpp index 84bf229efa..01eaa03509 100644 --- a/src/script/api/script_engine.hpp +++ b/src/script/api/script_engine.hpp @@ -249,6 +249,7 @@ public: /** * 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. @@ -256,6 +257,15 @@ public: */ 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. diff --git a/src/script/api/script_rail.hpp b/src/script/api/script_rail.hpp index 5ef7e70e49..dbc5942c2a 100644 --- a/src/script/api/script_rail.hpp +++ b/src/script/api/script_rail.hpp @@ -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. */ diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index 2086e74f75..36e699220e 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -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); diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 415f75ca10..847c8f38ba 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -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(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(value.base())); } /** * Adds a const to the stack. Depending on the current state this means From 1fec42024c6d342985e000b8d3e3d8ac8c3a459e Mon Sep 17 00:00:00 2001 From: Michael Lutz Date: Fri, 13 Jun 2025 23:00:51 +0200 Subject: [PATCH 5/5] Add: Show all railtypes in the build vehicle and engine preview dialogs. --- src/build_vehicle_gui.cpp | 11 +++++++++++ src/engine_gui.cpp | 12 ++++++++++++ src/lang/english.txt | 2 ++ 3 files changed, 25 insertions(+) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 1186cc63ae..e1d4696a4f 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -632,6 +632,17 @@ 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); diff --git a/src/engine_gui.cpp b/src/engine_gui.cpp index 764a2148c3..b0e2e42668 100644 --- a/src/engine_gui.cpp +++ b/src/engine_gui.cpp @@ -182,6 +182,18 @@ static std::string GetTrainEngineInfoString(const Engine &e) res << GetString(STR_ENGINE_PREVIEW_COST_WEIGHT, e.GetCost(), e.GetDisplayWeight()); res << '\n'; + 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; diff --git a/src/lang/english.txt b/src/lang/english.txt index 21d0e08940..20dedd52de 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -4170,6 +4170,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 @@ -4353,6 +4354,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}