From e00b8520d5fa1803e40e5e7fa3c6f788b200bc10 Mon Sep 17 00:00:00 2001 From: Tyler Trahan Date: Tue, 2 Apr 2024 16:54:04 -0400 Subject: [PATCH] Feature: Engine expiry setting to keep owned engines available for purchase --- src/engine.cpp | 31 ++++++++++++++++++++++++++-- src/engine_type.h | 9 ++++++++ src/lang/english.txt | 7 ++++++- src/saveload/afterload.cpp | 5 +++++ src/saveload/saveload.h | 2 ++ src/settings_gui.cpp | 2 +- src/settings_type.h | 5 ++++- src/table/settings/game_settings.ini | 14 +++++++++++++ 8 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 979c6a7e3199f..7d66e5421d161 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -24,6 +24,7 @@ #include "engine_gui.h" #include "engine_func.h" #include "engine_base.h" +#include "engine_type.h" #include "company_base.h" #include "vehicle_func.h" #include "articulated_vehicles.h" @@ -604,6 +605,32 @@ static void ClearLastVariant(EngineID engine_id, VehicleType type) } } +/** + * Is a given engine allowed to expire? + * @note This does not age or retire the engine, just check if expiry is allowed. + * @param e The engine in question. + * @return True iff the engine can be checked for retirement. + */ +static bool AllowEngineExpiry(Engine *e) +{ + /* The vehicle is set to never expire. */ + if (e->info.base_life == 0xFF) return false; + + /* The player will not allow any vehicle to expire. */ + if (_settings_game.vehicle.engine_expiry == EngineExpiryMode::Never) return false; + + /* The player wants to keep buying vehicles someone already owns. */ + if (_settings_game.vehicle.engine_expiry == EngineExpiryMode::Owned) { + for (const Company *c : Company::Iterate()) { + if (c == nullptr) continue; + if (GetGroupNumEngines(c->index, ALL_GROUP, e->index) > 0) return false; + } + } + + /* Vehicles expire normally. */ + return true; +} + /** * Update #Engine::reliability and (if needed) update the engine GUIs. * @param e %Engine to update. @@ -620,7 +647,7 @@ void CalcEngineReliability(Engine *e, bool new_month) if (new_month && re->index > e->index && age != INT32_MAX) age++; /* parent variant's age has not yet updated. */ /* Check for early retirement */ - if (e->company_avail != 0 && !_settings_game.vehicle.never_expire_vehicles && e->info.base_life != 0xFF) { + if (e->company_avail != 0 && AllowEngineExpiry(e)) { int retire_early = e->info.retire_early; uint retire_early_max_age = std::max(0, e->duration_phase_1 + e->duration_phase_2 - retire_early * 12); if (retire_early != 0 && age >= retire_early_max_age) { @@ -634,7 +661,7 @@ void CalcEngineReliability(Engine *e, bool new_month) if (age < e->duration_phase_1) { uint start = e->reliability_start; e->reliability = age * (e->reliability_max - start) / e->duration_phase_1 + start; - } else if ((age -= e->duration_phase_1) < e->duration_phase_2 || _settings_game.vehicle.never_expire_vehicles || e->info.base_life == 0xFF) { + } else if ((age -= e->duration_phase_1) < e->duration_phase_2 || !AllowEngineExpiry(e)) { /* We are at the peak of this engines life. It will have max reliability. * This is also true if the engines never expire. They will not go bad over time */ e->reliability = e->reliability_max; diff --git a/src/engine_type.h b/src/engine_type.h index d5a28ced3ea15..851b857fbd5c7 100644 --- a/src/engine_type.h +++ b/src/engine_type.h @@ -195,6 +195,15 @@ enum EngineNameContext : uint8_t { AutoreplaceVehicleInUse = 0x22, ///< Name is show in the autoreplace window 'Vehicles in use' panel. }; +/** + * Possible values of the "engine_expiry" setting. + */ +enum EngineExpiryMode : uint8_t { + Off = 0, ///< Engines expire normally. + Never = 1, ///< Engines never expire. + Owned = 2, ///< Engines which are currently owned by any company never expire. Unowned vehicles expire normally. +}; + /** Combine an engine ID and a name context to an engine name dparam. */ inline uint64_t PackEngineNameDParam(EngineID engine_id, EngineNameContext context, uint32_t extra_data = 0) { diff --git a/src/lang/english.txt b/src/lang/english.txt index 36fea5c49165d..832ed64cbfa92 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1492,7 +1492,12 @@ STR_CONFIG_SETTING_WARN_INCOME_LESS_HELPTEXT :When enabled, a STR_CONFIG_SETTING_WARN_INCOME_LESS_HELPTEXT_PERIOD :When enabled, a news message gets sent when a vehicle has not made any profit within a period STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES :Vehicles never expire: {STRING2} -STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_HELPTEXT :When enabled, all vehicle models remain available forever after their introduction +STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_HELPTEXT :Choose if vehicle models remain available forever after their introduction + +###length 3 +STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_OFF :Off +STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_NEVER :All available forever +STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_OWNED :Owned vehicles available forever STR_CONFIG_SETTING_TIMEKEEPING_UNITS :Timekeeping: {STRING2} STR_CONFIG_SETTING_TIMEKEEPING_UNITS_HELPTEXT :Select the timekeeping units of the game. This cannot be changed later.{}{}Calendar-based is the classic OpenTTD experience, with a year consisting of 12 months, and each month having 28-31 days.{}{}In Wallclock-based time, cargo production and financials are instead based on one-minute increments, which is about as long as a 30 day month takes in Calendar-based mode. These are grouped into 12-minute periods, equivalent to a year in Calendar-based mode.{}{}In either mode there is always a classic calendar, which is used for introduction dates of vehicles, houses, and other infrastructure diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index a1d016a1f178b..9aa78c37f0fee 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -795,6 +795,11 @@ bool AfterLoadGame() _settings_game.linkgraph.recalc_time *= CalendarTime::SECONDS_PER_DAY; } + /* Convert old engine expiry setting. */ + if (IsSavegameVersionBefore(SLV_ENGINE_EXPIRY_OWNED)) { + _settings_game.vehicle.engine_expiry = _settings_game.vehicle.never_expire_vehicles ? EngineExpiryMode::Never : EngineExpiryMode::Off; + } + /* Load the sprites */ GfxLoadSprites(); LoadStringWidthTable(); diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 05ffac1c043a1..99db9910721ec 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -379,6 +379,8 @@ enum SaveLoadVersion : uint16_t { SLV_SCRIPT_RANDOMIZER, ///< 333 PR#12063 v14.0-RC1 Save script randomizers. SLV_VEHICLE_ECONOMY_AGE, ///< 334 PR#12141 v14.0 Add vehicle age in economy year, for profit stats minimum age + SLV_ENGINE_EXPIRY_OWNED, ///< 335 PR#12598 Add engine expiry mode to keep owned engines available for purchase. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 0ef8fb330e258..b07cda2efa2aa 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2144,7 +2144,7 @@ static SettingsContainer &GetSettingsTree() limitations->Add(new SettingEntry("construction.max_bridge_height")); limitations->Add(new SettingEntry("construction.max_tunnel_length")); limitations->Add(new SettingEntry("station.never_expire_airports")); - limitations->Add(new SettingEntry("vehicle.never_expire_vehicles")); + limitations->Add(new SettingEntry("vehicle.engine_expiry")); limitations->Add(new SettingEntry("vehicle.max_trains")); limitations->Add(new SettingEntry("vehicle.max_roadveh")); limitations->Add(new SettingEntry("vehicle.max_aircraft")); diff --git a/src/settings_type.h b/src/settings_type.h index 017a74664ef31..25b00e2437f5e 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -14,6 +14,7 @@ #include "economy_type.h" #include "town_type.h" #include "transport_type.h" +#include "engine_type.h" #include "network/network_type.h" #include "company_type.h" #include "cargotype.h" @@ -516,6 +517,8 @@ struct OrderSettings { /** Settings related to vehicles. */ struct VehicleSettings { + bool never_expire_vehicles; ///< Unused value, used to load old savegames. + uint8_t max_train_length; ///< maximum length for trains uint8_t smoke_amount; ///< amount of smoke/sparks locomotives produce uint8_t train_acceleration_model; ///< realistic acceleration for trains @@ -531,7 +534,7 @@ struct VehicleSettings { uint8_t plane_speed; ///< divisor for speed of aircraft uint8_t freight_trains; ///< value to multiply the weight of cargo by bool dynamic_engines; ///< enable dynamic allocation of engine data - bool never_expire_vehicles; ///< never expire vehicles + EngineExpiryMode engine_expiry; ///< engine expiry uint8_t extend_vehicle_life; ///< extend vehicle life by this many years uint8_t road_side; ///< the side of the road vehicles drive on uint8_t plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini index 07adda5cc9d50..a3cb234a98ca5 100644 --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -10,6 +10,7 @@ [pre-amble] static constexpr std::initializer_list _roadsides{"left", "right"}; +static constexpr std::initializer_list _engine_expiry_modes{"Off", "Never", "Owned"}; static void StationSpreadChanged(int32_t new_value); static void UpdateConsists(int32_t new_value); @@ -243,8 +244,21 @@ strval = STR_CONFIG_SETTING_NONE var = vehicle.never_expire_vehicles flags = SF_NO_NETWORK def = false +to = SLV_ENGINE_EXPIRY_OWNED + +[SDT_OMANY] +var = vehicle.engine_expiry +type = SLE_UINT8 +from = SLV_ENGINE_EXPIRY_OWNED +flags = SF_GUI_DROPDOWN | SF_NO_NETWORK +def = EngineExpiryMode::Off +min = EngineExpiryMode::Off +max = EngineExpiryMode::Owned +full = _engine_expiry_modes str = STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES strhelp = STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_HELPTEXT +strval = STR_CONFIG_SETTING_NEVER_EXPIRE_VEHICLES_OFF +cat = SC_BASIC [SDT_VAR] var = vehicle.max_trains