# HG changeset patch # User Ruby Dennington # Date 2022-03-20 13:57:20 # Node ID f7347205838e520d6f3ee74dbe75b3f4fc93362b # Parent 4bc7915a215632e2771c638487c5d5cecb335c9e Ginever.net OpenTTD Patchpack 6.0.0 diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ docs/aidocs/* docs/gamedocs/* docs/source/* /out +/.ottdrev-gpp +/media/baseset/openttd.grf +/media/baseset/orig_extra.grf \ No newline at end of file diff --git a/cmake/scripts/FindVersion.cmake b/cmake/scripts/FindVersion.cmake --- a/cmake/scripts/FindVersion.cmake +++ b/cmake/scripts/FindVersion.cmake @@ -103,6 +103,20 @@ if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_ # Restore LC_ALL set(ENV{LC_ALL} "${SAVED_LC_ALL}") +elseif(EXISTS "${CMAKE_SOURCE_DIR}/versiondump.py") + execute_process(COMMAND py -3 versiondump.py + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + file(READ "${CMAKE_SOURCE_DIR}/.ottdrev-gpp" OTTDREV) + string(REPLACE "\n" "" OTTDREV "${OTTDREV}") + string(REPLACE "\t" ";" OTTDREV "${OTTDREV}") + list(GET OTTDREV 0 REV_VERSION) + list(GET OTTDREV 1 REV_ISODATE) + list(GET OTTDREV 2 REV_MODIFIED) + list(GET OTTDREV 3 REV_HASH) + list(GET OTTDREV 4 REV_ISTAG) + list(GET OTTDREV 5 REV_ISSTABLETAG) + list(GET OTTDREV 6 REV_YEAR) elseif(EXISTS "${CMAKE_SOURCE_DIR}/.ottdrev") file(READ "${CMAKE_SOURCE_DIR}/.ottdrev" OTTDREV) string(REPLACE "\n" "" OTTDREV "${OTTDREV}") diff --git a/media/baseset/openttd.grf b/media/baseset/openttd.grf deleted file mode 100644 index 837e4b12d2553699331ad6ba878d38afda6aa109..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -149,8 +149,8 @@ static StationID FindNearestHangar(const /* don't crash the plane if we know it can't land at the airport */ if ((afc->flags & AirportFTAClass::SHORT_STRIP) && (avi->subtype & AIR_FAST) && !_cheats.no_jetcrash.value) continue; - /* the plane won't land at any helicopter station */ - if (!(afc->flags & AirportFTAClass::AIRPLANES) && (avi->subtype & AIR_CTOL)) continue; + /* the plane won't land at any helicopter station and vice versa*/ + if (!CanVehicleUseStation(v, st)) continue; /* Check if our last and next destinations can be reached from the depot airport. */ if (max_range != 0) { @@ -326,6 +326,9 @@ CommandCost CmdBuildAircraft(DoCommandFl v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + /* higher speed means higher breakdown chance */ + /* to somewhat compensate for the fact that fast aircraft spend less time in the air */ + v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255); v->max_age = e->GetLifeLengthInDays(); v->pos = GetVehiclePosOnBuild(tile); @@ -437,6 +440,7 @@ Money Aircraft::GetRunningCost() const { const Engine *e = this->GetEngine(); uint cost_factor = GetVehicleProperty(this, PROP_AIRCRAFT_RUNNING_COST_FACTOR, e->u.air.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_air; return GetPrice(PR_RUNNING_AIRCRAFT, cost_factor, e->GetGRF()); } @@ -607,7 +611,7 @@ void UpdateAircraftCache(Aircraft *v, bo /* Update aircraft range. */ if (update_range) { - v->acache.cached_max_range = GetVehicleProperty(v, PROP_AIRCRAFT_RANGE, AircraftVehInfo(v->engine_type)->max_range); + v->acache.cached_max_range = (GetVehicleProperty(v, PROP_AIRCRAFT_RANGE, AircraftVehInfo(v->engine_type)->max_range) * _settings_game.vehicle.plane_range_multiplier); /* Squared it now so we don't have to do it later all the time. */ v->acache.cached_max_range_sqr = v->acache.cached_max_range * v->acache.cached_max_range; } @@ -649,9 +653,17 @@ static int UpdateAircraftSpeed(Aircraft speed_limit *= _settings_game.vehicle.plane_speed; /* adjust speed for broken vehicles */ - if (v->vehstatus & VS_AIRCRAFT_BROKEN) { - if (SPEED_LIMIT_BROKEN < speed_limit) hard_limit = false; - speed_limit = std::min(speed_limit, SPEED_LIMIT_BROKEN); + if (v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) { + if (v->breakdown_severity < 10) + { + // stupid workaround for fixing old saves with severity 0, also FGSP + v->breakdown_severity = 10; + } + uint percentage_speed_limit = v->breakdown_severity; + uint cached_max_speed = v->vcache.cached_max_speed; + uint broken_down_max_speed = ((cached_max_speed * percentage_speed_limit) + 99) / 100; + if (broken_down_max_speed < speed_limit) hard_limit = false; + speed_limit = std::min(speed_limit, broken_down_max_speed); } if (v->vcache.cached_max_speed < speed_limit) { @@ -1156,6 +1168,39 @@ static bool AircraftController(Aircraft } /** + * Send a broken plane that needs to visit a depot to the correct location. + * @param v The airplane in question + */ +void FindBreakdownDestination(Aircraft *v) +{ + assert(v->type == VEH_AIRCRAFT && v->breakdown_ctr == 1); + + DestinationID destination = INVALID_STATION; + if (v->breakdown_type == BREAKDOWN_AIRCRAFT_DEPOT) { + /* Go to a hangar, if possible at our current destination */ + v->FindClosestDepot(NULL, &destination, NULL); + } else if (v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) { + /* Go to the nearest airport with a hangar */ + destination = FindNearestHangar(v); + } else { + NOT_REACHED(); + } + + if(destination != INVALID_STATION) { + if(destination != v->current_order.GetDestination()) { + v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN); + AircraftNextAirportPos_and_Order(v); + } else { + v->current_order.MakeGoToDepot(destination, ODTFB_BREAKDOWN); + } + } else { + /* If no hangar was found, crash */ + v->targetairport = INVALID_STATION; + CrashAirplane(v); + } +} + +/** * Handle crashed aircraft \a v. * @param v Crashed aircraft. */ @@ -1235,8 +1280,9 @@ static void HandleAircraftSmoke(Aircraft if (!(v->vehstatus & VS_AIRCRAFT_BROKEN)) return; - /* Stop smoking when landed */ - if (v->cur_speed < 10) { + /* breakdown-related speed limits are lifted when we are on the ground */ + if(v->state != FLYING && v->state != LANDING && v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED) { + /* Stop smoking when landed */ v->vehstatus &= ~VS_AIRCRAFT_BROKEN; v->breakdown_ctr = 0; return; @@ -1365,8 +1411,12 @@ static void MaybeCrashAirplane(Aircraft (AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) && !_cheats.no_jetcrash.value) { prob = 3276; + } else if (_settings_game.vehicle.plane_crashes == 0) { + return; + } else if (_settings_game.vehicle.improved_breakdowns && v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_AIRCRAFT_EM_LANDING) { + /* Airplanes that are attempting an emergency landing have a 2% chance to crash */ + prob = 0x10000 / 50; } else { - if (_settings_game.vehicle.plane_crashes == 0) return; prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500; } diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp --- a/src/clear_cmd.cpp +++ b/src/clear_cmd.cpp @@ -34,7 +34,7 @@ static CommandCost ClearTile_Clear(TileI PR_CLEAR_ROUGH, PR_CLEAR_ROUGH, }; - CommandCost price(EXPENSES_CONSTRUCTION); + CommandCost price(EXPENSES_T_DEMOLITION); if (!IsClearGround(tile, CLEAR_GRASS) || GetClearDensity(tile) != 0) { price.AddCost(_price[clear_price_table[GetClearGround(tile)]]); diff --git a/src/command_type.h b/src/command_type.h --- a/src/command_type.h +++ b/src/command_type.h @@ -22,7 +22,7 @@ struct GRFFile; * a possible error message/state together. */ class CommandCost { - ExpensesType expense_type; ///< the type of expence as shown on the finances view +public: ExpensesType expense_type; ///< the type of expence as shown on the finances view Money cost; ///< The cost of this action StringID message; ///< Warning message for when success is unset bool success; ///< Whether the comment went fine up to this moment diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -580,6 +580,8 @@ Company *DoStartupNewCompany(bool is_ai, AI::BroadcastNewEvent(new ScriptEventCompanyNew(c->index), c->index); Game::NewEvent(new ScriptEventCompanyNew(c->index)); + if (!is_ai) UpdateAllTownVirtCoords(); + return c; } diff --git a/src/company_gui.cpp b/src/company_gui.cpp --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -70,6 +70,16 @@ static ExpensesType _expenses_list_1[] = EXPENSES_SHIP_INC, EXPENSES_LOAN_INT, EXPENSES_OTHER, + EXPENSES_T_TRAIN_CON, + EXPENSES_T_ROAD_CON, + EXPENSES_T_AIRCRAFT_CON, + EXPENSES_T_SHIP_CON, + EXPENSES_T_TREES_CON, + EXPENSES_T_SCENERY_CON, + EXPENSES_T_LANDSCAPING, + EXPENSES_T_DEMOLITION, + EXPENSES_T_REWARD_INC, + EXPENSES_T_DILAPIDATION, }; /** Grouped list of expenses. */ @@ -78,16 +88,26 @@ static ExpensesType _expenses_list_2[] = EXPENSES_ROADVEH_INC, EXPENSES_AIRCRAFT_INC, EXPENSES_SHIP_INC, + EXPENSES_T_REWARD_INC, INVALID_EXPENSES, EXPENSES_TRAIN_RUN, EXPENSES_ROADVEH_RUN, EXPENSES_AIRCRAFT_RUN, EXPENSES_SHIP_RUN, EXPENSES_PROPERTY, + EXPENSES_T_DILAPIDATION, EXPENSES_LOAN_INT, INVALID_EXPENSES, + EXPENSES_T_TRAIN_CON, + EXPENSES_T_ROAD_CON, + EXPENSES_T_AIRCRAFT_CON, + EXPENSES_T_SHIP_CON, + EXPENSES_T_TREES_CON, + EXPENSES_T_SCENERY_CON, EXPENSES_CONSTRUCTION, EXPENSES_NEW_VEHICLES, + EXPENSES_T_LANDSCAPING, + EXPENSES_T_DEMOLITION, EXPENSES_OTHER, INVALID_EXPENSES, }; diff --git a/src/company_type.h b/src/company_type.h --- a/src/company_type.h +++ b/src/company_type.h @@ -36,7 +36,7 @@ enum Owner : byte { }; DECLARE_POSTFIX_INCREMENT(Owner) -static const uint MAX_LENGTH_PRESIDENT_NAME_CHARS = 32; ///< The maximum length of a president name in characters including '\0' +static const uint MAX_LENGTH_PRESIDENT_NAME_CHARS = 48; ///< The maximum length of a president name in characters including '\0' static const uint MAX_LENGTH_COMPANY_NAME_CHARS = 32; ///< The maximum length of a company name in characters including '\0' static const uint MAX_HISTORY_QUARTERS = 24; ///< The maximum number of quarters kept as performance's history diff --git a/src/core/bitmath_func.hpp b/src/core/bitmath_func.hpp --- a/src/core/bitmath_func.hpp +++ b/src/core/bitmath_func.hpp @@ -375,14 +375,33 @@ private: * (since it will use hardware swapping if available). * Even though they should return uint16 and uint32, we get * warnings if we don't cast those (why?) */ +# define BSWAP64(x) (static_cast(CFSwapInt64(x))) # define BSWAP32(x) (static_cast(CFSwapInt32(x))) # define BSWAP16(x) (static_cast(CFSwapInt16(x))) #elif defined(_MSC_VER) /* MSVC has intrinsics for swapping, resulting in faster code */ +# define BSWAP64(x) (_byteswap_uint64(x)) # define BSWAP32(x) (_byteswap_ulong(x)) # define BSWAP16(x) (_byteswap_ushort(x)) #else /** + * Perform a 64 bits endianness bitswap on x. + * @param x the variable to bitswap + * @return the bitswapped value. + */ + static inline uint64 BSWAP64(uint64 x) + { +#if !defined(__ICC) && (defined(__GNUC__) || defined(__clang__)) + /* GCC >= 4.3 provides a builtin, resulting in faster code */ + return (uint64)__builtin_bswap64((uint64)x); +#else + return ((x >> 56) & 0xFFULL) | ((x >> 40) & 0xFF00ULL) | ((x >> 24) & 0xFF0000ULL) | ((x >> 8) & 0xFF000000ULL) | + ((x << 8) & 0xFF00000000ULL) | ((x << 24) & 0xFF0000000000ULL) | ((x << 40) & 0xFF000000000000ULL) | ((x << 56) & 0xFF00000000000000ULL); + ; +#endif /* __GNUC__ || __clang__ */ + } + + /** * Perform a 32 bits endianness bitswap on x. * @param x the variable to bitswap * @return the bitswapped value. diff --git a/src/core/endian_func.hpp b/src/core/endian_func.hpp --- a/src/core/endian_func.hpp +++ b/src/core/endian_func.hpp @@ -17,25 +17,33 @@ #if TTD_ENDIAN == TTD_BIG_ENDIAN # define FROM_BE16(x) (x) # define FROM_BE32(x) (x) +# define FROM_BE64(x) (x) # define TO_BE16(x) (x) # define TO_BE32(x) (x) # define TO_BE32X(x) (x) +# define TO_BE64(x) (x) # define FROM_LE16(x) BSWAP16(x) # define FROM_LE32(x) BSWAP32(x) +# define FROM_LE64(x) BSWAP64(x) # define TO_LE16(x) BSWAP16(x) # define TO_LE32(x) BSWAP32(x) # define TO_LE32X(x) BSWAP32(x) +# define TO_LE64(x) BSWAP64(x) #else # define FROM_BE16(x) BSWAP16(x) # define FROM_BE32(x) BSWAP32(x) +# define FROM_BE64(x) BSWAP64(x) # define TO_BE16(x) BSWAP16(x) # define TO_BE32(x) BSWAP32(x) # define TO_BE32X(x) BSWAP32(x) +# define TO_BE64(x) BSWAP64(x) # define FROM_LE16(x) (x) # define FROM_LE32(x) (x) +# define FROM_LE64(x) (x) # define TO_LE16(x) (x) # define TO_LE32(x) (x) # define TO_LE32X(x) (x) +# define TO_LE64(x) (x) #endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */ static inline uint16 ReadLE16Aligned(const void *x) diff --git a/src/date.cpp b/src/date.cpp --- a/src/date.cpp +++ b/src/date.cpp @@ -36,7 +36,8 @@ uint16 _tick_counter; ///< Ever increme */ void SetDate(Date date, DateFract fract) { - assert(fract < DAY_TICKS); + //Get rid of this insane assert + //assert(fract < DAY_TICKS); YearMonthDay ymd; diff --git a/src/date_type.h b/src/date_type.h --- a/src/date_type.h +++ b/src/date_type.h @@ -25,19 +25,26 @@ typedef uint8 Day; ///< Type for the * 1 tick is approximately 30 ms. * 1 day is thus about 2 seconds (74 * 30 = 2220) on a machine that can run OpenTTD normally */ -static const int DAY_TICKS = 74; ///< ticks per day +static const int ORIG_DAY_TICKS = 74; ///< unscaled ticks per day static const int DAYS_IN_YEAR = 365; ///< days per year static const int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more... static const int MONTHS_IN_YEAR = 12; ///< months per year +/** + * Day length in ticks, calculated using a configurable scale factor. + */ +#define DAY_TICKS (ORIG_DAY_TICKS * _settings_game.economy.daylength_multiplier) + static const int STATION_RATING_TICKS = 185; ///< cycle duration for updating station rating static const int STATION_ACCEPTANCE_TICKS = 250; ///< cycle duration for updating station acceptance static const int STATION_LINKGRAPH_TICKS = 504; ///< cycle duration for cleaning dead links static const int CARGO_AGING_TICKS = 185; ///< cycle duration for aging cargo static const int INDUSTRY_PRODUCE_TICKS = 256; ///< cycle duration for industry production -static const int TOWN_GROWTH_TICKS = 70; ///< cycle duration for towns trying to grow. (this originates from the size of the town array in TTD +static const int ORIG_TOWN_GROWTH_TICKS = 70; ///< cycle duration for towns trying to grow. (this originates from the size of the town array in TTD) static const int INDUSTRY_CUT_TREE_TICKS = INDUSTRY_PRODUCE_TICKS * 2; ///< cycle duration for lumber mill's extra action +#define TOWN_GROWTH_TICKS (ORIG_TOWN_GROWTH_TICKS * _settings_game.economy.town_growth_multiplier) + /* * ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are @@ -111,4 +118,7 @@ static const Year INVALID_YEAR = -1; / static const Date INVALID_DATE = -1; ///< Representation of an invalid date static const Ticks INVALID_TICKS = -1; ///< Representation of an invalid number of ticks +// daylength patch requirement +#include "settings_type.h" + #endif /* DATE_TYPE_H */ diff --git a/src/depot_type.h b/src/depot_type.h --- a/src/depot_type.h +++ b/src/depot_type.h @@ -13,6 +13,6 @@ typedef uint16 DepotID; ///< Type for the unique identifier of depots. struct Depot; -static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 32; ///< The maximum length of a depot name in characters including '\0' +static const uint MAX_LENGTH_DEPOT_NAME_CHARS = 128; ///< The maximum length of a depot name in characters including '\0' #endif /* DEPOT_TYPE_H */ diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -362,6 +362,7 @@ static bool DisasterTick_Ufo(DisasterVeh uint dist = Delta(v->x_pos, u->x_pos) + Delta(v->y_pos, u->y_pos); if (dist < TILE_SIZE && !(u->vehstatus & VS_HIDDEN) && u->breakdown_ctr == 0) { + u->breakdown_type = BREAKDOWN_CRITICAL; u->breakdown_ctr = 3; u->breakdown_delay = 140; } @@ -547,6 +548,7 @@ static bool DisasterTick_Big_Ufo(Disaste if (Delta(target->x_pos, v->x_pos) + Delta(target->y_pos, v->y_pos) <= 12 * (int)TILE_SIZE) { target->breakdown_ctr = 5; target->breakdown_delay = 0xF0; + target->breakdown_type = BREAKDOWN_CRITICAL; } } } diff --git a/src/economy.cpp b/src/economy.cpp --- a/src/economy.cpp +++ b/src/economy.cpp @@ -747,7 +747,32 @@ void RecomputePrices() /* Setup price bases */ for (Price i = PR_BEGIN; i < PR_END; i++) { - Money price = _price_base_specs[i].start_price; + Money price; + + if (i == PR_INFRASTRUCTURE_RAIL) + { + price = _settings_game.economy.infrastructure_base_cost_rail; + } + else if (i == PR_INFRASTRUCTURE_ROAD) + { + price = _settings_game.economy.infrastructure_base_cost_road; + } + else if (i == PR_INFRASTRUCTURE_WATER) + { + price = _settings_game.economy.infrastructure_base_cost_water; + } + else if (i == PR_INFRASTRUCTURE_AIRPORT) + { + price = _settings_game.economy.infrastructure_base_cost_air; + } + else if (i == PR_INFRASTRUCTURE_STATION) + { + price = _settings_game.economy.infrastructure_base_cost_station; + } + else + { + price = _price_base_specs[i].start_price; + } /* Apply difficulty settings */ uint mod = 1; diff --git a/src/economy_type.h b/src/economy_type.h --- a/src/economy_type.h +++ b/src/economy_type.h @@ -155,7 +155,7 @@ typedef int8 PriceMultipliers[PR_END]; /** Types of expenses. */ enum ExpensesType : byte { - EXPENSES_CONSTRUCTION = 0, ///< Construction costs. + EXPENSES_CONSTRUCTION = 0, ///< Construction costs. [miscellaneous/unsorted] EXPENSES_NEW_VEHICLES, ///< New vehicles. EXPENSES_TRAIN_RUN, ///< Running costs trains. EXPENSES_ROADVEH_RUN, ///< Running costs road vehicles. @@ -168,6 +168,16 @@ enum ExpensesType : byte { EXPENSES_SHIP_INC, ///< Income from ships. EXPENSES_LOAN_INT, ///< Interest payments over the loan. EXPENSES_OTHER, ///< Other expenses. + EXPENSES_T_TRAIN_CON, + EXPENSES_T_ROAD_CON, + EXPENSES_T_AIRCRAFT_CON, + EXPENSES_T_SHIP_CON, + EXPENSES_T_TREES_CON, + EXPENSES_T_SCENERY_CON, + EXPENSES_T_LANDSCAPING, + EXPENSES_T_DEMOLITION, + EXPENSES_T_REWARD_INC, + EXPENSES_T_DILAPIDATION, EXPENSES_END, ///< Number of expense types. INVALID_EXPENSES = 0xFF, ///< Invalid expense type. }; diff --git a/src/engine.cpp b/src/engine.cpp --- a/src/engine.cpp +++ b/src/engine.cpp @@ -276,22 +276,26 @@ Money Engine::GetRunningCost() const base_price = this->u.road.running_cost_class; if (base_price == INVALID_PRICE) return 0; cost_factor = GetEngineProperty(this->index, PROP_ROADVEH_RUNNING_COST_FACTOR, this->u.road.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_road; break; case VEH_TRAIN: base_price = this->u.rail.running_cost_class; if (base_price == INVALID_PRICE) return 0; cost_factor = GetEngineProperty(this->index, PROP_TRAIN_RUNNING_COST_FACTOR, this->u.rail.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_rail; break; case VEH_SHIP: base_price = PR_RUNNING_SHIP; cost_factor = GetEngineProperty(this->index, PROP_SHIP_RUNNING_COST_FACTOR, this->u.ship.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_water; break; case VEH_AIRCRAFT: base_price = PR_RUNNING_AIRCRAFT; cost_factor = GetEngineProperty(this->index, PROP_AIRCRAFT_RUNNING_COST_FACTOR, this->u.air.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_air; break; default: NOT_REACHED(); @@ -443,7 +447,7 @@ uint16 Engine::GetRange() const { switch (this->type) { case VEH_AIRCRAFT: - return GetEngineProperty(this->index, PROP_AIRCRAFT_RANGE, this->u.air.max_range); + return (GetEngineProperty(this->index, PROP_AIRCRAFT_RANGE, this->u.air.max_range) * _settings_game.vehicle.plane_range_multiplier); default: NOT_REACHED(); } diff --git a/src/engine_type.h b/src/engine_type.h --- a/src/engine_type.h +++ b/src/engine_type.h @@ -170,7 +170,7 @@ enum EngineFlags { ENGINE_EXCLUSIVE_PREVIEW = 2, ///< This vehicle is in the exclusive preview stage, either being used or being offered to a company. }; -static const uint MAX_LENGTH_ENGINE_NAME_CHARS = 32; ///< The maximum length of an engine name in characters including '\0' +static const uint MAX_LENGTH_ENGINE_NAME_CHARS = 128; ///< The maximum length of an engine name in characters including '\0' static const EngineID INVALID_ENGINE = 0xFFFF; ///< Constant denoting an invalid engine. diff --git a/src/fios.h b/src/fios.h --- a/src/fios.h +++ b/src/fios.h @@ -23,6 +23,12 @@ enum SaveLoadInvalidateWindowData { SLIWD_FILTER_CHANGES, ///< The filename filter has changed (via the editbox) }; +enum PatchpackSaveExtensionType { + PSXT_NONE, + PSXT_MODIFIED, + PSXT_EXTENDED +}; + typedef SmallMap CompanyPropertiesMap; /** @@ -45,6 +51,9 @@ struct LoadCheckData { struct LoggedAction *gamelog_action; ///< Gamelog actions uint gamelog_actions; ///< Number of gamelog actions + uint16 save_version; ///< Save version + PatchpackSaveExtensionType save_ext_type; ///< Extension type + std::string save_version_label; ///< Save version label LoadCheckData() : error_data(nullptr), grfconfig(nullptr), grf_compatibility(GLC_NOT_FOUND), gamelog_action(nullptr), gamelog_actions(0) diff --git a/src/fios_gui.cpp b/src/fios_gui.cpp --- a/src/fios_gui.cpp +++ b/src/fios_gui.cpp @@ -54,6 +54,9 @@ void LoadCheckData::Clear() this->map_size_x = this->map_size_y = 256; // Default for old savegames which do not store mapsize. this->current_date = 0; + this->save_version = SL_MIN_VERSION; + this->save_ext_type = PSXT_NONE; + this->save_version_label = ""; this->settings = {}; for (auto &pair : this->companies) { @@ -471,6 +474,19 @@ public: uint y_max = r.bottom - FONT_HEIGHT_NORMAL - WD_FRAMERECT_BOTTOM; if (y > y_max) break; + if (_load_check_data.checkable) { + SetDParam(0, _load_check_data.save_version); + SetDParamStr(1, _load_check_data.save_ext_type == PSXT_EXTENDED ? "X" : _load_check_data.save_ext_type == PSXT_MODIFIED ? "M" : ""); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_VERSION_WITH_EXTENSION); + y += FONT_HEIGHT_NORMAL; + if (_load_check_data.save_version_label.length() > 0) + { + SetDParamStr(0, _load_check_data.save_version_label.c_str()); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_VERSION_LABEL); + y += FONT_HEIGHT_NORMAL; + } + y += FONT_HEIGHT_NORMAL; + } if (!_load_check_data.checkable) { /* Old savegame, no information available */ DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_NOT_AVAILABLE); diff --git a/src/gamelog.cpp b/src/gamelog.cpp --- a/src/gamelog.cpp +++ b/src/gamelog.cpp @@ -51,8 +51,8 @@ static const char * GetGamelogRevisionSt return _openttd_revision; } else if (gamelog_revision[0] == 0) { /* Prefix character indication revision status */ - assert(_openttd_revision_modified < 3); - gamelog_revision[0] = "gum"[_openttd_revision_modified]; // g = "git", u = "unknown", m = "modified" + assert(_openttd_revision_modified < 5); + gamelog_revision[0] = "gumhh"[_openttd_revision_modified]; // g = "git", u = "unknown", m = "modified", "h" = "hg" /* Append the revision hash */ strecat(gamelog_revision, _openttd_revision_hash, lastof(gamelog_revision)); /* Truncate string to GAMELOG_REVISION_LENGTH bytes */ diff --git a/src/ground_vehicle.cpp b/src/ground_vehicle.cpp --- a/src/ground_vehicle.cpp +++ b/src/ground_vehicle.cpp @@ -28,12 +28,9 @@ void GroundVehicle::PowerChange uint32 number_of_parts = 0; uint16 max_track_speed = this->vcache.cached_max_speed; // Max track speed in internal units. + this->CalculatePower(total_power, max_te, false); + for (const T *u = v; u != nullptr; u = u->Next()) { - uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u); - total_power += current_power; - - /* Only powered parts add tractive effort. */ - if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort(); number_of_parts++; /* Get minimum max speed for this track. */ @@ -56,8 +53,6 @@ void GroundVehicle::PowerChange this->gcache.cached_air_drag = air_drag + 3 * air_drag * number_of_parts / 20; - max_te *= GROUND_ACCELERATION; // Tractive effort in (tonnes * 1000 * 9.8 =) N. - max_te /= 256; // Tractive effort is a [0-255] coefficient. if (this->gcache.cached_power != total_power || this->gcache.cached_max_te != max_te) { /* Stop the vehicle if it has no power. */ if (total_power == 0) this->vehstatus |= VS_STOPPED; @@ -71,6 +66,30 @@ void GroundVehicle::PowerChange this->gcache.cached_max_track_speed = max_track_speed; } +template +void GroundVehicle::CalculatePower(uint32& total_power, uint32& max_te, bool breakdowns) const { + + total_power = 0; + max_te = 0; + + const T *v = T::From(this); + + for (const T *u = v; u != NULL; u = u->Next()) { + uint32 current_power = u->GetPower() + u->GetPoweredPartPower(u); + total_power += current_power; + + /* Only powered parts add tractive effort. */ + if (current_power > 0) max_te += u->GetWeight() * u->GetTractiveEffort(); + + if (breakdowns && u->breakdown_ctr == 1 && u->breakdown_type == BREAKDOWN_LOW_POWER) { + total_power = total_power * u->breakdown_severity / 256; + } + } + + max_te *= GROUND_ACCELERATION; // Tractive effort in (tonnes * 1000 * 9.8 =) N. + max_te /= 256; // Tractive effort is a [0-255] coefficient. +} + /** * Recalculates the cached weight of a vehicle and its parts. Should be called each time the cargo on * the consist changes. @@ -102,7 +121,7 @@ void GroundVehicle::CargoChange * @return Current acceleration of the vehicle. */ template -int GroundVehicle::GetAcceleration() const +int GroundVehicle::GetAcceleration() { /* Templated class used for function calls for performance reasons. */ const T *v = T::From(this); @@ -117,6 +136,7 @@ int GroundVehicle::GetAccelerat * and km/h to m/s conversion below result in a maximum of * about 1.1E11, way more than 4.3E9 of int32. */ int64 power = this->gcache.cached_power * 746ll; + uint32 max_te = this->gcache.cached_max_te; // [N] /* This is constructed from: * - axle resistance: U16 power * 10 for 128 vehicles. @@ -148,7 +168,16 @@ int GroundVehicle::GetAccelerat /* This value allows to know if the vehicle is accelerating or braking. */ AccelStatus mode = v->GetAccelerationStatus(); - const int max_te = this->gcache.cached_max_te; // [N] + /* handle breakdown power reduction */ + //TODO + if( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) { + /* We'd like to cache this, but changing cached_power has too many unwanted side-effects */ + uint32 power_temp; + this->CalculatePower(power_temp, max_te, true); + power = power_temp * 74611; + } + + /* Constructued from power, with need to multiply by 18 and assuming * low speed, it needs to be a 64 bit integer too. */ int64 force; @@ -156,7 +185,7 @@ int GroundVehicle::GetAccelerat if (!maglev) { /* Conversion factor from km/h to m/s is 5/18 to get [N] in the end. */ force = power * 18 / (speed * 5); - if (mode == AS_ACCEL && force > max_te) force = max_te; + if (mode == AS_ACCEL && force > (int)max_te) force = max_te; } else { force = power / 25; } @@ -166,6 +195,34 @@ int GroundVehicle::GetAccelerat force = std::max(force, (mass * 8) + resistance); } + /* If power is 0 because of a breakdown, we make the force 0 if accelerating */ + if ( Type == VEH_TRAIN && mode == AS_ACCEL && HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER) && power == 0) { + force = 0; + } + + /* Calculate the breakdown chance */ + if (_settings_game.vehicle.improved_breakdowns) { + assert(this->gcache.cached_max_track_speed > 0); + /** First, calculate (resistance / force * current speed / max speed) << 16. + * This yields a number x on a 0-1 scale, but shifted 16 bits to the left. + * We then calculate 64 + 128x, clamped to 0-255, but still shifted 16 bits to the left. + * Then we apply a correction for multiengine trains, and in the end we shift it 16 bits to the right to get a 0-255 number. + * @note A seperate correction for multiheaded engines is done in CheckVehicleBreakdown. We can't do that here because it would affect the whole consist. + */ + uint64 breakdown_factor = (uint64)abs(resistance) * (uint64)(this->cur_speed << 16); + breakdown_factor /= (std::max(force, (int64)100) * this->gcache.cached_max_track_speed); + breakdown_factor = std::min((64 << 16) + (breakdown_factor * 128), 255 << 16); + if ( Type == VEH_TRAIN && Train::From(this)->tcache.cached_num_engines > 1) { + /* For multiengine trains, breakdown chance is multiplied by 3 / (num_engines + 2) */ + breakdown_factor *= 3; + breakdown_factor /= (Train::From(this)->tcache.cached_num_engines + 2); + } + /* breakdown_chance is at least 5 (5 / 128 = ~4% of the normal chance) */ + this->breakdown_chance = (uint8) std::max(breakdown_factor >> 16, (uint64)5); + } else { + this->breakdown_chance = 128; + } + if (mode == AS_ACCEL) { /* Easy way out when there is no acceleration. */ if (force == resistance) return 0; @@ -176,7 +233,27 @@ int GroundVehicle::GetAccelerat * a hill will never speed up enough to (eventually) get back to the * same (maximum) speed. */ int accel = ClampToI32((force - resistance) / (mass * 4)); - return force < resistance ? std::min(-1, accel) : std::max(1, accel); + accel = force < resistance ? std::min(-1, accel) : std::max(1, accel); + if (this->type == VEH_TRAIN ) { + if(_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL && + HasBit(Train::From(this)->flags, VRF_BREAKDOWN_POWER)) { + /* We need to apply the power reducation for non-realistic acceleration here */ + uint32 power; + CalculatePower(power, max_te, true); + accel = accel * power / this->gcache.cached_power; + accel -= this->acceleration >> 1; + } + + + if ( this->IsFrontEngine() && !(this->current_order_time & 0x1FF) && + !(this->current_order.IsType(OT_LOADING)) && + !(Train::From(this)->flags & (VRF_IS_BROKEN | (1 << VRF_TRAIN_STUCK))) && + this->cur_speed < 3 && accel < 5) { + SetBit(Train::From(this)->flags, VRF_TO_HEAVY); + } + } + + return accel; } else { return ClampToI32(std::min(-force - resistance, -10000) / mass); } diff --git a/src/ground_vehicle.hpp b/src/ground_vehicle.hpp --- a/src/ground_vehicle.hpp +++ b/src/ground_vehicle.hpp @@ -89,9 +89,11 @@ struct GroundVehicle : public Specialize void PowerChanged(); void CargoChanged(); - int GetAcceleration() const; + int GetAcceleration(); bool IsChainInDepot() const override; + void CalculatePower(uint32& power, uint32& max_te, bool breakdowns) const; + /** * Common code executed for crashed ground vehicles * @param flooded was this vehicle flooded? @@ -367,7 +369,22 @@ protected: /* When we are going faster than the maximum speed, reduce the speed * somewhat gradually. But never lower than the maximum speed. */ - int tempmax = max_speed; + int tempmax = ((this->breakdown_ctr == 1) ? this->cur_speed : max_speed); + + if (this->breakdown_ctr == 1) { + if (this->breakdown_type == BREAKDOWN_LOW_POWER) { + if((this->tick_counter & 0x7) == 0) { + if(this->cur_speed > (this->breakdown_severity * max_speed) >> 8) { + tempmax = this->cur_speed - (this->cur_speed / 10) - 1; + } else { + tempmax = (this->breakdown_severity * max_speed) >> 8; + } + } + } + if(this->breakdown_type == BREAKDOWN_LOW_SPEED) + tempmax = std::min(max_speed, this->breakdown_severity); + } + if (this->cur_speed > max_speed) { tempmax = std::max(this->cur_speed - (this->cur_speed / 10) - 1, max_speed); } diff --git a/src/group_gui.cpp b/src/group_gui.cpp --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -207,7 +207,7 @@ private: this->tiny_step_height = this->column_size[VGC_FOLD].height; this->column_size[VGC_NAME] = maxdim(GetStringBoundingBox(STR_GROUP_DEFAULT_TRAINS + this->vli.vtype), GetStringBoundingBox(STR_GROUP_ALL_TRAINS + this->vli.vtype)); - this->column_size[VGC_NAME].width = std::max(170u, this->column_size[VGC_NAME].width); + this->column_size[VGC_NAME].width = std::max(450u, this->column_size[VGC_NAME].width); this->tiny_step_height = std::max(this->tiny_step_height, this->column_size[VGC_NAME].height); this->column_size[VGC_PROTECT] = GetSpriteSize(SPR_GROUP_REPLACE_PROTECT); @@ -1084,14 +1084,14 @@ public: static WindowDesc _other_group_desc( - WDP_AUTO, "list_groups", 460, 246, + WDP_AUTO, "list_groups", 940, 246, WC_INVALID, WC_NONE, 0, _nested_group_widgets, lengthof(_nested_group_widgets) ); static WindowDesc _train_group_desc( - WDP_AUTO, "list_groups_train", 525, 246, + WDP_AUTO, "list_groups_train", 1005, 246, WC_TRAINS_LIST, WC_NONE, 0, _nested_group_widgets, lengthof(_nested_group_widgets) diff --git a/src/group_type.h b/src/group_type.h --- a/src/group_type.h +++ b/src/group_type.h @@ -17,7 +17,7 @@ static const GroupID ALL_GROUP = 0xF static const GroupID DEFAULT_GROUP = 0xFFFE; ///< Ungrouped vehicles are in this group. static const GroupID INVALID_GROUP = 0xFFFF; ///< Sentinel for invalid groups. -static const uint MAX_LENGTH_GROUP_NAME_CHARS = 32; ///< The maximum length of a group name in characters including '\0' +static const uint MAX_LENGTH_GROUP_NAME_CHARS = 128; ///< The maximum length of a group name in characters including '\0' struct Group; diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -520,7 +520,7 @@ static CommandCost ClearTile_Industry(Ti Game::NewEvent(new ScriptEventIndustryClose(i->index)); delete i; } - return CommandCost(EXPENSES_CONSTRUCTION, indspec->GetRemovalCost()); + return CommandCost(EXPENSES_T_DEMOLITION, indspec->GetRemovalCost()); } /** @@ -2054,7 +2054,7 @@ CommandCost CmdBuildIndustry(DoCommandFl AdvertiseIndustryOpening(ind); } - return CommandCost(EXPENSES_OTHER, indspec->GetConstructionCost()); + return CommandCost(EXPENSES_CONSTRUCTION, indspec->GetConstructionCost()); } /** diff --git a/src/landscape.cpp b/src/landscape.cpp --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -690,7 +690,7 @@ void ClearSnowLine() */ CommandCost CmdLandscapeClear(DoCommandFlag flags, TileIndex tile) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_DEMOLITION); bool do_clear = false; /* Test for stuff which results in water when cleared. Then add the cost to also clear the water. */ if ((flags & DC_FORCE_CLEAR_TILE) && HasTileWaterClass(tile) && IsTileOnWater(tile) && !IsWaterTile(tile) && !IsCoastTile(tile)) { @@ -741,7 +741,7 @@ std::tuple CmdClearA if (start_tile >= MapSize()) return { CMD_ERROR, 0 }; Money money = GetAvailableMoneyForCommand(); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_DEMOLITION); CommandCost last_error = CMD_ERROR; bool had_success = false; diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -304,6 +304,7 @@ STR_SORT_BY_WAITING_TOTAL STR_SORT_BY_WAITING_AVAILABLE :Available waiting cargo STR_SORT_BY_RATING_MAX :Highest cargo rating STR_SORT_BY_RATING_MIN :Lowest cargo rating +STR_SORT_BY_DILAPIDATION :Dilapidation STR_SORT_BY_ENGINE_ID :EngineID (classic sort) STR_SORT_BY_COST :Cost STR_SORT_BY_POWER :Power @@ -1233,6 +1234,9 @@ STR_CONFIG_SETTING_CONSTRUCTION_SPEED_HE STR_CONFIG_SETTING_VEHICLE_BREAKDOWNS :Vehicle breakdowns: {STRING2} STR_CONFIG_SETTING_VEHICLE_BREAKDOWNS_HELPTEXT :Control how often inadequately serviced vehicles may break down +STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS :Enable improved breakdowns: {STRING} +STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS_HELPTEXT :If enabled, different type of breakdowns can occur: emergency stop, temporary speed reduction, temporary power loss or a classic breakdown. Aircrafts may travel with reduced speed or have a mandatory service at the destination airport or have an emergency landing at the closest airport. Breakdown possibility is similar to classic breakdowns, but the impact to vehicles and the network is reduced. + STR_CONFIG_SETTING_SUBSIDY_MULTIPLIER :Subsidy multiplier: {STRING2} STR_CONFIG_SETTING_SUBSIDY_MULTIPLIER_HELPTEXT :Set how much is paid for subsidised connections @@ -1359,6 +1363,9 @@ STR_CONFIG_SETTING_AUTOSCROLL_EVERY_VIEW STR_CONFIG_SETTING_BRIBE :Allow bribing of the local authority: {STRING2} STR_CONFIG_SETTING_BRIBE_HELPTEXT :Allow companies to try bribing the local town authority. If the bribe is noticed by an inspector, the company will not be able to act in the town for six months +STR_CONFIG_SETTING_BRIBE_RISKY :Bribing can be caught by an inspector: {STRING2} +STR_CONFIG_SETTING_BRIBE_RISKY_HELPTEXT :Whether or not bribing towns can be noticed by an inspector + STR_CONFIG_SETTING_ALLOW_EXCLUSIVE :Allow buying exclusive transport rights: {STRING2} STR_CONFIG_SETTING_ALLOW_EXCLUSIVE_HELPTEXT :If a company buys exclusive transport rights for a town, opponents' stations (passenger and cargo) won't receive any cargo for a whole year @@ -1441,6 +1448,9 @@ STR_CONFIG_SETTING_HOVER_DELAY_DISABLED STR_CONFIG_SETTING_POPULATION_IN_LABEL :Show town population in the town name label: {STRING2} STR_CONFIG_SETTING_POPULATION_IN_LABEL_HELPTEXT :Display the population of towns in their label on the map +STR_CONFIG_SETTING_RATING_COLOUR_IN_LABEL :Colour town names based on company rating: {STRING2} +STR_CONFIG_SETTING_RATING_COLOUR_IN_LABEL_HELPTEXT :Colour towns based on company rating in their label on the map + STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS :Thickness of lines in graphs: {STRING2} STR_CONFIG_SETTING_GRAPH_LINE_THICKNESS_HELPTEXT :Width of the line in the graphs. A thin line is more precisely readable, a thicker line is easier to see and colours are easier to distinguish @@ -1862,6 +1872,63 @@ STR_CONFIG_SETTING_TOWN_CARGOGENMODE_HEL ###length 2 STR_CONFIG_SETTING_TOWN_CARGOGENMODE_ORIGINAL :Quadratic (original) STR_CONFIG_SETTING_TOWN_CARGOGENMODE_BITCOUNT :Linear +STR_CONFIG_SETTING_DAYLENGTH_FACTOR :Day length multiplier: {STRING2} +STR_CONFIG_SETTING_DAYLENGTH_FACTOR_HELPTEXT :Adjusts the length of a day in OpenTTD. A multiplier of 1 is equivalent to classic TTD (each day lasts 74 ticks or 2.22 seconds) +STR_CONFIG_SETTING_TOWN_GROWTH_FACTOR :Town growth interval multiplier: {STRING2} +STR_CONFIG_SETTING_TOWN_GROWTH_FACTOR_HELPTEXT :Adjusts the length of time between town growth ticks, intended to be used alongside day length multiplier. Larger values will result in town growth happening more slowly +STR_CONFIG_SETTING_PLANE_RANGE_MULTIPLIER :Aircraft range multiplier: {STRING2} +STR_CONFIG_SETTING_PLANE_RANGE_MULTIPLIER_HELPTEXT :Adjusts the maximum range of all aircraft +STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES :Station naming convention: {STRING2} +STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_HELPTEXT :Determines how stations will be named when placed; affects all players +STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_SELECTION_0 :Use classic station names only +STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_SELECTION_1 :Use NewGRF station names where appropriate +STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_SELECTION_2 :Name stations based on nearby industries +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_RAIL :Rail infrastructure base cost: {STRING2} +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_RAIL_HELPTEXT :Adjusts the base infrastructure maintenance cost of rail tiles +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_ROAD :Road infrastructure base cost: {STRING2} +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_ROAD_HELPTEXT :Adjusts the base infrastructure maintenance cost of road tiles +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_WATER :Water infrastructure base cost: {STRING2} +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_WATER_HELPTEXT :Adjusts the base infrastructure maintenance cost of water tiles +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_AIR :Air infrastructure base cost: {STRING2} +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_AIR_HELPTEXT :Adjusts the base infrastructure maintenance cost of airports +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_STATION :Station infrastructure base cost: {STRING2} +STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_STATION_HELPTEXT :Adjusts the base infrastructure maintenance cost of station tiles +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_RAIL :Rail running cost multiplier: {STRING2} +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_RAIL_HELPTEXT :Adjusts the running cost of rail vehicles, intended to be used alongside day length multiplier +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_ROAD :Road running cost multiplier: {STRING2} +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_ROAD_HELPTEXT :Adjusts the running cost of road vehicles, intended to be used alongside day length multiplier +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_WATER :Water running cost multiplier: {STRING2} +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_WATER_HELPTEXT :Adjusts the running cost of water vehicles, intended to be used alongside day length multiplier +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_AIR :Air running cost multiplier: {STRING2} +STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_AIR_HELPTEXT :Adjusts the running cost of air vehicles, intended to be used alongside day length multiplier +STR_CONFIG_SETTING_VEHICLE_BREAKDOWN_SCALER :Vehicle breakdown scaler: {STRING2} +STR_CONFIG_SETTING_VEHICLE_BREAKDOWN_SCALER_HELPTEXT :This is an additional breakdown chance scaler. Whenever a vehicle would normally break down in OTTD, a random 24-bit number will instead be generated (between 0 and 16777215). If it is greater than this scaler, the breakdown will be cancelled. (Setting to maximum value will behave like this scaler didn't exist) + +STR_CONFIG_SETTING_DILAPIDATION_MAX_AMOUNT :Maximum station dilapidation: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_MAX_AMOUNT_HELPTEXT :Every time the rating is updated (around once every 2.5 days) the amount of dilapidation at the station will be adjusted, being increased if there are excessive numbers of waiting passengers and being decreased otherwise. The more a station is dilapidated at the start of each month, the higher the amount your company will be fined. This setting controls the maximum amount of dilapidation that can be at a station. Setting to zero will disable this feature +STR_CONFIG_SETTING_DILAPIDATION_INCREASE :Dilapidation increase: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_INCREASE_HELPTEXT :This is the amount by which station dilapidation will be increased if there are excessive numbers of waiting passengers +STR_CONFIG_SETTING_DILAPIDATION_DECREASE :Dilapidation decrease: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_DECREASE_HELPTEXT :This is the amount by which station dilapidation will be decreased if there are not excessive numbers of waiting passengers +STR_CONFIG_SETTING_DILAPIDATION_POP_RAIL :Train station excessive passenger count: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_POP_RAIL_HELPTEXT :Train stations are allowed to have this many passengers waiting before the number is considered excessive. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_POP_ROAD :Road stop excessive passenger count: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_POP_ROAD_HELPTEXT :Road stops are allowed to have this many passengers waiting before the number is considered excessive. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_POP_WATER :Dock excessive passenger count: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_POP_WATER_HELPTEXT :Docks are allowed to have this many passengers waiting before the number is considered excessive. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_POP_AIR :Airport/helipad excessive passenger count: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_POP_AIR_HELPTEXT :Airports and helipads are allowed to have this many passengers waiting before the number is considered excessive. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_FINE_RAIL :Fine per train station dilapidation point: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_FINE_RAIL_HELPTEXT :You will be fined this amount of money each month for each dilapidation point at each train station. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_FINE_ROAD :Fine per road stop dilapidation point: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_FINE_ROAD_HELPTEXT :You will be fined this amount of money each month for each dilapidation point at each road stop. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_FINE_WATER :Fine per dock dilapidation point: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_FINE_WATER_HELPTEXT :You will be fined this amount of money each month for each dilapidation point at each docks. For multi-type stations the highest value is used +STR_CONFIG_SETTING_DILAPIDATION_FINE_AIR :Fine per airport/helipad dilapidation point: {STRING2} +STR_CONFIG_SETTING_DILAPIDATION_FINE_AIR_HELPTEXT :You will be fined this amount of money each month for each dilapidation point at each airport or helipad. For multi-type stations the highest value is used + +STR_CONFIG_SETTING_LOAD_LEGACY_PATCHPACK_DATA :Attempt to load legacy patch data: {STRING2} +STR_CONFIG_SETTING_LOAD_LEGACY_PATCHPACK_DATA_HELPTEXT :Causes the save loader to try to load legacy patch data for save files that might be from patchpack 5.x or earlier. Generally safe to leave enabled. Not all old saves are supported yet STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT :In game placement of trees: {STRING2} STR_CONFIG_SETTING_EXTRA_TREE_PLACEMENT_HELPTEXT :Control random appearance of trees during the game. This might affect industries which rely on tree growth, for example lumber mills @@ -2018,6 +2085,9 @@ STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST STR_CONFIG_SETTING_AI :{ORANGE}Competitors STR_CONFIG_SETTING_AI_NPC :{ORANGE}Computer players STR_CONFIG_SETTING_NETWORK :{ORANGE}Network +STR_CONFIG_SETTING_PATCHPACK :{ORANGE}Patchpack configuration +STR_CONFIG_SETTING_PATCHPACK_DAYLENGTH :{ORANGE}Economy multipliers +STR_CONFIG_SETTING_PATCHPACK_DILAPIDATION :{ORANGE}Station dilapidation STR_CONFIG_SETTING_PATHFINDER_FOR_TRAINS :Pathfinder for trains: {STRING2} STR_CONFIG_SETTING_PATHFINDER_FOR_TRAINS_HELPTEXT :Path finder to use for trains @@ -2058,7 +2128,7 @@ STR_VIDEO_DRIVER_ERROR STR_VIDEO_DRIVER_ERROR_NO_HARDWARE_ACCELERATION :{WHITE}... no compatible GPU found. Hardware acceleration disabled # Intro window -STR_INTRO_CAPTION :{WHITE}OpenTTD {REV} +STR_INTRO_CAPTION :{WHITE}Ginever.net OpenTTD Patchpack :: {REV} STR_INTRO_NEW_GAME :{BLACK}New Game STR_INTRO_LOAD_GAME :{BLACK}Load Game @@ -3033,7 +3103,7 @@ STR_LAI_OBJECT_DESCRIPTION_COMPANY_OWNED # About OpenTTD window STR_ABOUT_OPENTTD :{WHITE}About OpenTTD STR_ABOUT_ORIGINAL_COPYRIGHT :{BLACK}Original copyright {COPYRIGHT} 1995 Chris Sawyer, All rights reserved -STR_ABOUT_VERSION :{BLACK}OpenTTD version {REV} +STR_ABOUT_VERSION :{BLACK}Ginever.net OpenTTD Patchpack :: {REV} STR_ABOUT_COPYRIGHT_OPENTTD :{BLACK}OpenTTD {COPYRIGHT} 2002-{RAW_STRING} The OpenTTD team # Framerate display window @@ -3124,6 +3194,9 @@ STR_SAVELOAD_PARENT_DIRECTORY STR_SAVELOAD_OSKTITLE :{BLACK}Enter a name for the savegame +STR_SAVELOAD_VERSION_WITH_EXTENSION :{SILVER}Save format version: {WHITE}{NUM}{RAW_STRING} +STR_SAVELOAD_VERSION_LABEL :{SILVER}Saved by: {WHITE}{RAW_STRING} + # World generation STR_MAPGEN_WORLD_GENERATION_CAPTION :{WHITE}World Generation STR_MAPGEN_MAPSIZE :{BLACK}Map size: @@ -3529,6 +3602,7 @@ STR_STATION_LIST_TOOLTIP STR_STATION_LIST_USE_CTRL_TO_SELECT_MORE :{BLACK}Hold Ctrl to select more than one item STR_STATION_LIST_CAPTION :{WHITE}{COMPANY} - {COMMA} Station{P "" s} STR_STATION_LIST_STATION :{YELLOW}{STATION} {STATION_FEATURES} +STR_STATION_LIST_STATION_WITH_DILAPIDATION :{YELLOW}{STATION} {STATION_FEATURES} {WHITE}({NUM}) STR_STATION_LIST_WAYPOINT :{YELLOW}{WAYPOINT} STR_STATION_LIST_NONE :{YELLOW}- None - STR_STATION_LIST_SELECT_ALL_FACILITIES :{BLACK}Select all facilities @@ -3552,6 +3626,8 @@ STR_STATION_VIEW_RATINGS_TOOLTIP STR_STATION_VIEW_SUPPLY_RATINGS_TITLE :{BLACK}Monthly supply and local rating: STR_STATION_VIEW_CARGO_SUPPLY_RATING :{WHITE}{STRING}: {YELLOW}{COMMA} / {STRING} ({COMMA}%) +STR_STATION_VIEW_DILAPIDATION :{BLACK}Station dilapidation: {YELLOW}{NUM} / {NUM} + STR_STATION_VIEW_GROUP :{BLACK}Group by STR_STATION_VIEW_WAITING_STATION :Station: Waiting STR_STATION_VIEW_WAITING_AMOUNT :Amount: Waiting @@ -3612,8 +3688,8 @@ STR_FINANCES_CAPTION STR_FINANCES_EXPENDITURE_INCOME_TITLE :{WHITE}Expenditure/Income STR_FINANCES_YEAR :{WHITE}{NUM} -###length 13 -STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Construction +###length 23 +STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Misc Construction STR_FINANCES_SECTION_NEW_VEHICLES :{GOLD}New Vehicles STR_FINANCES_SECTION_TRAIN_RUNNING_COSTS :{GOLD}Train Running Costs STR_FINANCES_SECTION_ROAD_VEHICLE_RUNNING_COSTS :{GOLD}Road Vehicle Running Costs @@ -3626,6 +3702,16 @@ STR_FINANCES_SECTION_AIRCRAFT_INCOME STR_FINANCES_SECTION_SHIP_INCOME :{GOLD}Ship Income STR_FINANCES_SECTION_LOAN_INTEREST :{GOLD}Loan Interest STR_FINANCES_SECTION_OTHER :{GOLD}Other +STR_FINANCES_SECTION_T_TRAIN_CON :{GOLD}Railway Construction +STR_FINANCES_SECTION_T_ROAD_CON :{GOLD}Road Construction +STR_FINANCES_SECTION_T_AIRCRAFT_CON :{GOLD}Airport Construction +STR_FINANCES_SECTION_T_SHIP_CON :{GOLD}Waterways Construction +STR_FINANCES_SECTION_T_TREES_CON :{GOLD}Tree Construction +STR_FINANCES_SECTION_T_SCENERY_CON :{GOLD}Object Construction +STR_FINANCES_SECTION_T_LANDSCAPING :{GOLD}Landscaping +STR_FINANCES_SECTION_T_DEMOLITION :{GOLD}Demolition +STR_FINANCES_SECTION_T_REWARD_INC :{GOLD}Reward Income +STR_FINANCES_SECTION_T_DILAPIDATION :{GOLD}Station Dilapidation STR_FINANCES_NEGATIVE_INCOME :{BLACK}-{CURRENCY_LONG} STR_FINANCES_POSITIVE_INCOME :{BLACK}+{CURRENCY_LONG} @@ -4137,11 +4223,20 @@ STR_VEHICLE_STATUS_LEAVING STR_VEHICLE_STATUS_CRASHED :{RED}Crashed! STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down STR_VEHICLE_STATUS_STOPPED :{RED}Stopped +STR_VEHICLE_STATUS_BROKEN_DOWN_VEL :{RED}Broken down - {STRING1}, {LTBLUE} {VELOCITY} STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL :{RED}Stopping, {VELOCITY} STR_VEHICLE_STATUS_TRAIN_NO_POWER :{RED}No power STR_VEHICLE_STATUS_TRAIN_STUCK :{ORANGE}Waiting for free path STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR :{ORANGE}Too far to next destination +STR_BREAKDOWN_TYPE_CRITICAL :Mechanical failure +STR_BREAKDOWN_TYPE_EM_STOP :Emergency stop +STR_BREAKDOWN_TYPE_LOW_SPEED :Limited to {VELOCITY} +STR_BREAKDOWN_TYPE_LOW_POWER :{COMMA}% Power +STR_BREAKDOWN_TYPE_DEPOT :Heading to {STATION} Hangar for repairs +STR_BREAKDOWN_TYPE_LANDING :Heading to {STATION} for emergency landing +STR_ERROR_TRAIN_TOO_HEAVY :{WHITE}{VEHICLE} is too heavy + STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL :{LTBLUE}Heading for {STATION}, {VELOCITY} STR_VEHICLE_STATUS_NO_ORDERS_VEL :{LTBLUE}No orders, {VELOCITY} STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL :{LTBLUE}Heading for {WAYPOINT}, {VELOCITY} @@ -4172,6 +4267,11 @@ STR_VEHICLE_DETAILS_SHIP_RENAME STR_VEHICLE_DETAILS_AIRCRAFT_RENAME :{BLACK}Name aircraft STR_VEHICLE_INFO_AGE_RUNNING_COST_YR :{BLACK}Age: {LTBLUE}{STRING2}{BLACK} Running Cost: {LTBLUE}{CURRENCY_LONG}/yr + +STR_RUNNING :{LTBLUE}Running +STR_NEED_REPAIR :{ORANGE}Train needs repair - max speed reduced to {VELOCITY} +STR_CURRENT_STATUS :{BLACK}Current status: {STRING2} + STR_VEHICLE_INFO_AGE :{COMMA} year{P "" s} ({COMMA}) STR_VEHICLE_INFO_AGE_RED :{RED}{COMMA} year{P "" s} ({COMMA}) @@ -4210,7 +4310,9 @@ STR_QUERY_RENAME_AIRCRAFT_CAPTION # Extra buttons for train details windows STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} +STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Built: {LTBLUE}{NUM}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY} STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} +STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_AND_SPEED :{LTBLUE}{ENGINE}{BLACK} Value: {LTBLUE}{CURRENCY_LONG} {BLACK}Max. speed: {LTBLUE}{VELOCITY} STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT :{BLACK}Total cargo capacity of this train: STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY :{LTBLUE}- {CARGO_LONG} ({CARGO_SHORT}) @@ -4615,12 +4717,13 @@ STR_ERROR_GAME_SAVE_FAILED STR_ERROR_UNABLE_TO_DELETE_FILE :{WHITE}Unable to delete file STR_ERROR_GAME_LOAD_FAILED :{WHITE}Game Load Failed{}{STRING1} STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR :Internal error: {RAW_STRING} -STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME :Broken savegame - {RAW_STRING} -STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME :Savegame is made with newer version +STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME :Save file contains incompatible patch data ({RAW_STRING}) +STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME :Save file contains incompatible patch data STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE :File not readable STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE :File not writeable STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED :Data integrity check failed STR_GAME_SAVELOAD_ERROR_PATCHPACK :Savegame is made with a modified version +STR_GAME_SAVELOAD_ERROR_UNSUPPORTED_GPP :The legacy save loader doesn't currently support loading saves from this version STR_GAME_SAVELOAD_NOT_AVAILABLE : STR_WARNING_LOADGAME_REMOVED_TRAMS :{WHITE}Game was saved in version without tram support. All trams have been removed @@ -5467,9 +5570,24 @@ STR_SAVEGAME_NAME_DEFAULT STR_SAVEGAME_NAME_SPECTATOR :Spectator, {1:STRING1} # Viewport strings +STR_VIEWPORT_TOWN_POP_VERY_POOR_RATING :{RED}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_POP_MEDIOCRE_RATING :{ORANGE}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_POP_GOOD_RATING :{YELLOW}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_POP_NORMAL_RATING :{GREEN}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_POP_EXCELLENT_RATING :{LTBLUE}{TOWN} ({COMMA}) STR_VIEWPORT_TOWN_POP :{WHITE}{TOWN} ({COMMA}) +STR_VIEWPORT_TOWN_VERY_POOR_RATING :{RED}{TOWN} +STR_VIEWPORT_TOWN_MEDIOCRE_RATING :{ORANGE}{TOWN} +STR_VIEWPORT_TOWN_GOOD_RATING :{YELLOW}{TOWN} +STR_VIEWPORT_TOWN_NORMAL_RATING :{GREEN}{TOWN} +STR_VIEWPORT_TOWN_EXCELLENT_RATING :{LTBLUE}{TOWN} STR_VIEWPORT_TOWN :{WHITE}{TOWN} STR_VIEWPORT_TOWN_TINY_BLACK :{TINY_FONT}{BLACK}{TOWN} +STR_VIEWPORT_TOWN_TINY_VERY_POOR_RATING :{TINY_FONT}{RED}{TOWN} +STR_VIEWPORT_TOWN_TINY_MEDIOCRE_RATING :{TINY_FONT}{ORANGE}{TOWN} +STR_VIEWPORT_TOWN_TINY_GOOD_RATING :{TINY_FONT}{YELLOW}{TOWN} +STR_VIEWPORT_TOWN_TINY_NORMAL_RATING :{TINY_FONT}{GREEN}{TOWN} +STR_VIEWPORT_TOWN_TINY_EXCELLENT_RATING :{TINY_FONT}{LTBLUE}{TOWN} STR_VIEWPORT_TOWN_TINY_WHITE :{TINY_FONT}{WHITE}{TOWN} STR_VIEWPORT_SIGN_SMALL_BLACK :{TINY_FONT}{BLACK}{SIGN} diff --git a/src/lang/english_AU.txt b/src/lang/english_AU.txt --- a/src/lang/english_AU.txt +++ b/src/lang/english_AU.txt @@ -3247,7 +3247,7 @@ STR_FINANCES_EXPENDITURE_INCOME_TITLE STR_FINANCES_YEAR :{WHITE}{NUM} ###length 13 -STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Construction +STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Misc Construction STR_FINANCES_SECTION_NEW_VEHICLES :{GOLD}New Vehicles STR_FINANCES_SECTION_TRAIN_RUNNING_COSTS :{GOLD}Train Running Costs STR_FINANCES_SECTION_ROAD_VEHICLE_RUNNING_COSTS :{GOLD}Road Vehicle Running Costs diff --git a/src/lang/english_US.txt b/src/lang/english_US.txt --- a/src/lang/english_US.txt +++ b/src/lang/english_US.txt @@ -3613,7 +3613,7 @@ STR_FINANCES_EXPENDITURE_INCOME_TITLE STR_FINANCES_YEAR :{WHITE}{NUM} ###length 13 -STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Construction +STR_FINANCES_SECTION_CONSTRUCTION :{GOLD}Misc Construction STR_FINANCES_SECTION_NEW_VEHICLES :{GOLD}New Vehicles STR_FINANCES_SECTION_TRAIN_RUNNING_COSTS :{GOLD}Train Running Costs STR_FINANCES_SECTION_ROAD_VEHICLE_RUNNING_COSTS :{GOLD}Road Vehicle Running Costs diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -408,6 +408,15 @@ static const char * const _credits[] = { u8"Original design by Chris Sawyer", u8"Original graphics by Simon Foster", u8"", + u8"Ginever.net OpenTTD Patchpack team:", + u8" Ruby Dennington (Theleruby) - Patch implementation", + u8" Paul Dennington (Paulicus25) - Lead tester", + u8" Assorted testers from Ginever Entertainment", + u8"", + u8"The latest version of this patchpack can be obtained from:", + u8" https://hg.ginever.net/public/openttd (source)", + u8" https://hg.ginever.net/public/openttd-binaries (Windows binaries)", + u8"", u8"The OpenTTD team (in alphabetical order):", u8" Grzegorz Duczy\u0144ski (adf88) - General coding (since 1.7.2)", u8" Albert Hofkamp (Alberth) - GUI expert (since 0.7)", diff --git a/src/network/core/game_info.cpp b/src/network/core/game_info.cpp --- a/src/network/core/game_info.cpp +++ b/src/network/core/game_info.cpp @@ -46,7 +46,7 @@ std::string_view GetNetworkRevisionStrin if (network_revision.empty()) { network_revision = _openttd_revision; - if (_openttd_revision_tagged) { + if (_openttd_revision_tagged || _openttd_revision_modified == 3 || _openttd_revision_modified == 4) { /* Tagged; do not mangle further, though ensure it's not too long. */ if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1); } else { diff --git a/src/network/network_func.h b/src/network/network_func.h --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -74,6 +74,8 @@ bool NetworkServerStart(); void NetworkServerNewCompany(const Company *company, NetworkClientInfo *ci); bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_name); +void NetworkSavePassword(); +void NetworkLoadPassword(); void NetworkServerDoMove(ClientID client_id, CompanyID company_id); void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string); diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -34,6 +34,7 @@ #include #include +#include "../fileio_func.h" #include "../safeguards.h" @@ -1566,6 +1567,9 @@ static void NetworkAutoCleanCompanies() IConsolePrint(CC_INFO, "Auto-removed protection from company #{}.", c->index + 1); _network_company_states[c->index].months_empty = 0; NetworkServerUpdateCompanyPassworded(c->index, false); + if (_settings_client.network.save_password) { + NetworkSavePassword(); + } } /* Is the company empty for autoclean_novehicles-months, and has no vehicles? */ if (_settings_client.network.autoclean_novehicles != 0 && _network_company_states[c->index].months_empty > _settings_client.network.autoclean_novehicles && vehicles_in_company[c->index] == 0) { @@ -1659,6 +1663,10 @@ void NetworkServerSetCompanyPassword(Com } NetworkServerUpdateCompanyPassworded(company_id, !_network_company_states[company_id].password.empty()); + + if (_settings_client.network.save_password) { + NetworkSavePassword(); + } } /** @@ -2076,6 +2084,9 @@ void NetworkServerNewCompany(const Compa _network_company_states[c->index].months_empty = 0; _network_company_states[c->index].password.clear(); NetworkServerUpdateCompanyPassworded(c->index, false); + if (_settings_client.network.save_password) { + NetworkSavePassword(); + } if (ci != nullptr) { /* ci is nullptr when replaying, or for AIs. In neither case there is a client. */ @@ -2094,3 +2105,41 @@ void NetworkServerNewCompany(const Compa NetworkServerSendChat(NETWORK_ACTION_COMPANY_NEW, DESTTYPE_BROADCAST, 0, "", ci->client_id, c->index + 1); } } + +void NetworkSavePassword() +{ + std::string password_file_name = std::to_string(_settings_game.game_creation.generation_seed) + ".pwd"; + Debug(net, 0, "Saving company passwords to %s", password_file_name); + FILE* file_pointer = FioFOpenFile(password_file_name, "wb", SAVE_DIR); + if (file_pointer != nullptr) { + for (CompanyID l_company = (CompanyID)0; l_company < MAX_COMPANIES; l_company++) { + if (NetworkCompanyIsPassworded(l_company)) { + const char* password = _network_company_states[l_company].password.c_str(); + fwrite(password, strlen(password), 1, file_pointer); + } + fwrite("\n", 1, 1, file_pointer); + } + fclose(file_pointer); + } +} + +void NetworkLoadPassword() +{ + std::string password_file_name = std::to_string(_settings_game.game_creation.generation_seed) + ".pwd"; + FILE* file_pointer = FioFOpenFile(password_file_name, "rb", SAVE_DIR); + if (file_pointer != nullptr) { + Debug(net, 0, "Loading company passwords from %s", password_file_name); + for (CompanyID l_company = (CompanyID)0; l_company < MAX_COMPANIES; l_company++) { + char password[NETWORK_PASSWORD_LENGTH] = { '\0' }; + fgets(password, NETWORK_PASSWORD_LENGTH, file_pointer); + if (strlen(password) > 1) { + _network_company_states[l_company].password = password; + NetworkServerUpdateCompanyPassworded(l_company, !_network_company_states[l_company].password.empty()); + fseek(file_pointer, 1L, SEEK_CUR); + } + } + } + else { + Debug(net, 0, "Password file %s not found", password_file_name); + } +} diff --git a/src/object_cmd.cpp b/src/object_cmd.cpp --- a/src/object_cmd.cpp +++ b/src/object_cmd.cpp @@ -205,7 +205,7 @@ static CommandCost ClearTile_Object(Tile */ CommandCost CmdBuildObject(DoCommandFlag flags, TileIndex tile, ObjectType type, uint8 view) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_SCENERY_CON); if (type >= NUM_OBJECTS) return CMD_ERROR; const ObjectSpec *spec = ObjectSpec::Get(type); @@ -478,7 +478,7 @@ static CommandCost ClearTile_Object(Tile ObjectType type = o->type; const ObjectSpec *spec = ObjectSpec::Get(type); - CommandCost cost(EXPENSES_CONSTRUCTION, spec->GetClearCost() * ta.w * ta.h / 5); + CommandCost cost(EXPENSES_T_DEMOLITION, spec->GetClearCost() * ta.w * ta.h / 5); if (spec->flags & OBJECT_FLAG_CLEAR_INCOME) cost.MultiplyCost(-1); // They get an income! /* Towns can't remove any objects. */ @@ -525,7 +525,7 @@ static CommandCost ClearTile_Object(Tile } /* cost of relocating company is 1% of company value */ - cost = CommandCost(EXPENSES_CONSTRUCTION, CalculateCompanyValue(c) / 100); + cost = CommandCost(EXPENSES_T_SCENERY_CON, CalculateCompanyValue(c) / 100); break; } diff --git a/src/openttd.cpp b/src/openttd.cpp --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -254,7 +254,20 @@ static void WriteSavegameInfo(const char char buf[8192]; char *p = buf; p += seprintf(p, lastof(buf), "Name: %s\n", name); - p += seprintf(p, lastof(buf), "Savegame ver: %d\n", _sl_version); + const char *type = ""; + extern SaveLoadVersion _sl_is_faked_ext; + extern bool _sl_is_ext_version; + if (_sl_is_faked_ext != SL_MIN_VERSION) { + type = " (fake extended)"; + } else if (_sl_is_ext_version) { + type = " (extended)"; + } + p += seprintf(p, lastof(buf), "Savegame ver: %d%s\n", _sl_version, type); + for (size_t i = 0; i < XSLFI_SIZE; i++) { + if (_sl_xv_feature_versions[i] > 0) { + p += seprintf(p, lastof(buf), " Feature: %s = %d\n", SlXvGetFeatureName((SlXvFeatureIndex) i), _sl_xv_feature_versions[i]); + } + } p += seprintf(p, lastof(buf), "NewGRF ver: 0x%08X\n", last_ottd_rev); p += seprintf(p, lastof(buf), "Modified: %d\n", ever_modified); @@ -1047,6 +1060,9 @@ void SwitchToMode(SwitchMode new_mode) OnStartGame(_network_dedicated); /* Decrease pause counter (was increased from opening load dialog) */ Command::Post(PM_PAUSED_SAVELOAD, false); + if (_is_network_server && _settings_client.network.save_password) { + NetworkLoadPassword(); + } } break; } diff --git a/src/order_type.h b/src/order_type.h --- a/src/order_type.h +++ b/src/order_type.h @@ -94,6 +94,7 @@ enum OrderDepotTypeFlags { ODTF_MANUAL = 0, ///< Manually initiated order. ODTFB_SERVICE = 1 << 0, ///< This depot order is because of the servicing limit. ODTFB_PART_OF_ORDERS = 1 << 1, ///< This depot order is because of a regular order. + ODTFB_BREAKDOWN = 1 << 2, ///< This depot order is because of a breakdown. }; /** diff --git a/src/rail_cmd.cpp b/src/rail_cmd.cpp --- a/src/rail_cmd.cpp +++ b/src/rail_cmd.cpp @@ -415,7 +415,7 @@ static CommandCost CheckRailSlope(Slope } Foundation f_old = GetRailFoundation(tileh, existing); - return CommandCost(EXPENSES_CONSTRUCTION, f_new != f_old ? _price[PR_BUILD_FOUNDATION] : (Money)0); + return CommandCost(EXPENSES_T_TRAIN_CON, f_new != f_old ? _price[PR_BUILD_FOUNDATION] : (Money)0); } /* Validate functions for rail building */ @@ -435,7 +435,7 @@ static inline bool ValParamTrackOrientat */ CommandCost CmdBuildSingleRail(DoCommandFlag flags, TileIndex tile, RailType railtype, Track track, bool auto_remove_signals) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_TRAIN_CON); if (!ValParamRailtype(railtype) || !ValParamTrackOrientation(track)) return CMD_ERROR; @@ -618,7 +618,7 @@ CommandCost CmdBuildSingleRail(DoCommand */ CommandCost CmdRemoveSingleRail(DoCommandFlag flags, TileIndex tile, Track track) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_TRAIN_CON); bool crossing = false; if (!ValParamTrackOrientation(track)) return CMD_ERROR; @@ -877,7 +877,7 @@ static CommandCost ValidateAutoDrag(Trac */ static CommandCost CmdRailTrackHelper(DoCommandFlag flags, TileIndex tile, TileIndex end_tile, RailType railtype, Track track, bool remove, bool auto_remove_signals, bool fail_on_obstacle) { - CommandCost total_cost(EXPENSES_CONSTRUCTION); + CommandCost total_cost(EXPENSES_T_TRAIN_CON); if ((!remove && !ValParamRailtype(railtype)) || !ValParamTrackOrientation(track)) return CMD_ERROR; if (end_tile >= MapSize()) return CMD_ERROR; @@ -1061,17 +1061,17 @@ CommandCost CmdBuildSingleSignal(DoComma CommandCost cost; if (!HasSignalOnTrack(tile, track)) { /* build new signals */ - cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS]); + cost = CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_SIGNALS]); } else { if (signals_copy != 0 && sigvar != GetSignalVariant(tile, track)) { /* convert signals <-> semaphores */ - cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS] + _price[PR_CLEAR_SIGNALS]); + cost = CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_SIGNALS] + _price[PR_CLEAR_SIGNALS]); } else if (convert_signal) { /* convert button pressed */ if (ctrl_pressed || GetSignalVariant(tile, track) != sigvar) { /* convert electric <-> semaphore */ - cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_SIGNALS] + _price[PR_CLEAR_SIGNALS]); + cost = CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_SIGNALS] + _price[PR_CLEAR_SIGNALS]); } else { /* it is free to change signal type: normal-pre-exit-combo */ cost = CommandCost(); @@ -1239,7 +1239,7 @@ static bool AdvanceSignalAutoFill(TileIn */ static CommandCost CmdSignalTrackHelper(DoCommandFlag flags, TileIndex tile, TileIndex end_tile, Track track, SignalType sigtype, SignalVariant sigvar, bool mode, bool remove, bool autofill, bool minimise_gaps, int signal_density) { - CommandCost total_cost(EXPENSES_CONSTRUCTION); + CommandCost total_cost(EXPENSES_T_TRAIN_CON); if (end_tile >= MapSize() || !ValParamTrackOrientation(track)) return CMD_ERROR; if (signal_density == 0 || signal_density > 20) return CMD_ERROR; @@ -1492,7 +1492,7 @@ CommandCost CmdRemoveSingleSignal(DoComm MarkTileDirtyByTile(tile); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_SIGNALS]); + return CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_CLEAR_SIGNALS]); } /** @@ -1541,7 +1541,7 @@ CommandCost CmdConvertRail(DoCommandFlag TrainList affected_trains; - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_TRAIN_CON); CommandCost error = CommandCost(STR_ERROR_NO_SUITABLE_RAILROAD_TRACK); // by default, there is no track to convert. bool found_convertible_track = false; // whether we actually did convert some track (see bug #7633) @@ -1777,12 +1777,12 @@ static CommandCost RemoveTrainDepot(Tile if (v != nullptr) TryPathReserve(v, true); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_TRAIN]); + return CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_CLEAR_DEPOT_TRAIN]); } static CommandCost ClearTile_Track(TileIndex tile, DoCommandFlag flags) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_DEMOLITION); if (flags & DC_AUTO) { if (!IsTileOwner(tile, _current_company)) { @@ -2997,7 +2997,7 @@ static CommandCost TestAutoslopeOnRailTi /* Surface slope must not be changed */ default: if (z_old != z_new || tileh_old != tileh_new) return_cmd_error(STR_ERROR_MUST_REMOVE_RAILROAD_TRACK); - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + return CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_FOUNDATION]); } /* The height of the track_corner must not be changed. The rest ensures GetRailFoundation() already. */ @@ -3005,7 +3005,7 @@ static CommandCost TestAutoslopeOnRailTi z_new += GetSlopeZInCorner(RemoveHalftileSlope(tileh_new), track_corner); if (z_old != z_new) return_cmd_error(STR_ERROR_MUST_REMOVE_RAILROAD_TRACK); - CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + CommandCost cost = CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_FOUNDATION]); /* Make the ground dirty, if surface slope has changed */ if (tileh_old != tileh_new) { /* If there is flat water on the lower halftile add the cost for clearing it */ @@ -3063,10 +3063,10 @@ static CommandCost TerraformTile_Track(T if ((flags & DC_EXEC) != 0) SetRailGroundType(tile, RAIL_GROUND_BARREN); /* allow terraforming */ - return CommandCost(EXPENSES_CONSTRUCTION, was_water ? _price[PR_CLEAR_WATER] : (Money)0); + return CommandCost(EXPENSES_T_TRAIN_CON, was_water ? _price[PR_CLEAR_WATER] : (Money)0); } else if (_settings_game.construction.build_on_slopes && AutoslopeEnabled() && AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRailDepotDirection(tile))) { - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + return CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_FOUNDATION]); } return Command::Do(flags, tile); } diff --git a/src/rev.cpp.in b/src/rev.cpp.in --- a/src/rev.cpp.in +++ b/src/rev.cpp.in @@ -38,7 +38,15 @@ bool IsReleasedVersion() const char _openttd_revision[] = "${REV_VERSION}"; /** - * The text version of OpenTTD's build date. + * The text version of OpenTTD's build date in short format (without the time). + * Updating the build date in this file is the safest as it generally gets + * updated for each revision in contrary to most other files that only see + * updates when they are actually changed themselves. + */ +const char _openttd_build_date_short[] = __DATE__; + +/** + * The text version of OpenTTD's build date and time. * Updating the build date in this file is the safest as it generally gets * updated for each revision in contrary to most other files that only see * updates when they are actually changed themselves. @@ -62,6 +70,8 @@ const char _openttd_revision_year[] = "$ * Value 0 means no modification, 1 is for unknown state * (compiling from sources without any version control software) * and 2 is for modified revision. + * + * Ginever patchpack uses value 3 for hg unmodified and 4 for hg modified */ const byte _openttd_revision_modified = ${REV_MODIFIED}; diff --git a/src/rev.h b/src/rev.h --- a/src/rev.h +++ b/src/rev.h @@ -12,6 +12,7 @@ extern const char _openttd_revision[]; extern const char _openttd_build_date[]; +extern const char _openttd_build_date_short[]; extern const char _openttd_revision_hash[]; extern const char _openttd_revision_year[]; extern const byte _openttd_revision_modified; diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -370,7 +370,7 @@ static CommandCost RemoveRoad(TileIndex /* If it's the last roadtype, just clear the whole tile */ if (GetRoadType(tile, OtherRoadTramType(rtt)) == INVALID_ROADTYPE) return Command::Do(flags, tile); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); if (IsTileType(tile, MP_TUNNELBRIDGE)) { /* Removing any roadbit in the bridge axis removes the roadtype (that's the behaviour remove-long-roads needs) */ if ((AxisToRoadBits(DiagDirToAxis(GetTunnelBridgeDirection(tile))) & pieces) == ROAD_NONE) return_cmd_error((rtt == RTT_TRAM) ? STR_ERROR_THERE_IS_NO_TRAMWAY : STR_ERROR_THERE_IS_NO_ROAD); @@ -489,7 +489,7 @@ static CommandCost RemoveRoad(TileIndex } } - CommandCost cost(EXPENSES_CONSTRUCTION, CountBits(pieces) * RoadClearCost(existing_rt)); + CommandCost cost(EXPENSES_T_ROAD_CON, CountBits(pieces) * RoadClearCost(existing_rt)); /* If we build a foundation we have to pay for it. */ if (f == FOUNDATION_NONE && GetRoadFoundation(tileh, present) != FOUNDATION_NONE) cost.AddCost(_price[PR_BUILD_FOUNDATION]); @@ -525,7 +525,7 @@ static CommandCost RemoveRoad(TileIndex MarkTileDirtyByTile(tile); YapfNotifyTrackLayoutChange(tile, railtrack); } - return CommandCost(EXPENSES_CONSTRUCTION, RoadClearCost(existing_rt) * 2); + return CommandCost(EXPENSES_T_ROAD_CON, RoadClearCost(existing_rt) * 2); } default: @@ -569,7 +569,7 @@ static CommandCost CheckRoadSlope(Slope if (_settings_game.construction.build_on_slopes && (_invalid_tileh_slopes_road[0][tileh] & (other | type_bits)) == ROAD_NONE) { /* If we add leveling we've got to pay for it */ - if ((other | existing) == ROAD_NONE) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if ((other | existing) == ROAD_NONE) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); return CommandCost(); } @@ -589,12 +589,12 @@ static CommandCost CheckRoadSlope(Slope if (_settings_game.construction.build_on_slopes) { /* If we add foundation we've got to pay for it */ - if ((other | existing) == ROAD_NONE) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if ((other | existing) == ROAD_NONE) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); return CommandCost(); } } else { - if (HasExactlyOneBit(existing) && GetRoadFoundation(tileh, existing) == FOUNDATION_NONE) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if (HasExactlyOneBit(existing) && GetRoadFoundation(tileh, existing) == FOUNDATION_NONE) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); return CommandCost(); } } @@ -614,7 +614,7 @@ static CommandCost CheckRoadSlope(Slope CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, RoadType rt, DisallowedRoadDirections toggle_drd, TownID town_id) { CompanyID company = _current_company; - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); RoadBits existing = ROAD_NONE; RoadBits other_bits = ROAD_NONE; @@ -782,7 +782,7 @@ CommandCost CmdBuildRoad(DoCommandFlag f UpdateLevelCrossing(tile, false); MarkTileDirtyByTile(tile); } - return CommandCost(EXPENSES_CONSTRUCTION, 2 * RoadBuildCost(rt)); + return CommandCost(EXPENSES_T_ROAD_CON, 2 * RoadBuildCost(rt)); } case MP_STATION: { @@ -999,7 +999,7 @@ CommandCost CmdBuildLongRoad(DoCommandFl * when you just 'click' on one tile to build them. */ if ((drd == DRD_NORTHBOUND || drd == DRD_SOUTHBOUND) && (axis == AXIS_Y) == (start_tile == end_tile && start_half == end_half)) drd ^= DRD_BOTH; - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); CommandCost last_error = CMD_ERROR; TileIndex tile = start_tile; bool had_bridge = false; @@ -1072,7 +1072,7 @@ CommandCost CmdBuildLongRoad(DoCommandFl */ std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, bool start_half, bool end_half) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); if (end_tile >= MapSize()) return { CMD_ERROR, 0 }; if (!ValParamRoadType(rt) || !IsValidAxis(axis)) return { CMD_ERROR, 0 }; @@ -1204,7 +1204,7 @@ static CommandCost RemoveRoadDepot(TileI DoClearSquare(tile); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_ROAD]); + return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_CLEAR_DEPOT_ROAD]); } static CommandCost ClearTile_Road(TileIndex tile, DoCommandFlag flags) @@ -1215,7 +1215,7 @@ static CommandCost ClearTile_Road(TileIn /* Clear the road if only one piece is on the tile OR we are not using the DC_AUTO flag */ if ((HasExactlyOneBit(b) && GetRoadBits(tile, RTT_TRAM) == ROAD_NONE) || !(flags & DC_AUTO)) { - CommandCost ret(EXPENSES_CONSTRUCTION); + CommandCost ret(EXPENSES_T_DEMOLITION); for (RoadTramType rtt : _roadtramtypes) { if (!MayHaveRoad(tile) || GetRoadType(tile, rtt) == INVALID_ROADTYPE) continue; @@ -1229,7 +1229,7 @@ static CommandCost ClearTile_Road(TileIn } case ROAD_TILE_CROSSING: { - CommandCost ret(EXPENSES_CONSTRUCTION); + CommandCost ret(EXPENSES_T_DEMOLITION); if (flags & DC_AUTO) return_cmd_error(STR_ERROR_MUST_REMOVE_ROAD_FIRST); @@ -1650,7 +1650,6 @@ static void DrawTile_Road(TileInfo *ti) if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); Axis axis = GetCrossingRailAxis(ti->tile); - const RailtypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile)); RoadType road_rt = GetRoadTypeRoad(ti->tile); @@ -2226,11 +2225,11 @@ static CommandCost TerraformTile_Road(Ti if (_settings_game.construction.build_on_slopes && AutoslopeEnabled()) { switch (GetRoadTileType(tile)) { case ROAD_TILE_CROSSING: - if (!IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) && HasBit(VALID_LEVEL_CROSSING_SLOPES, tileh_new)) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if (!IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new)) && HasBit(VALID_LEVEL_CROSSING_SLOPES, tileh_new)) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); break; case ROAD_TILE_DEPOT: - if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile))) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if (AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, GetRoadDepotDirection(tile))) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); break; case ROAD_TILE_NORMAL: { @@ -2248,7 +2247,7 @@ static CommandCost TerraformTile_Road(Ti z_new += ApplyFoundationToSlope(GetRoadFoundation(tileh_new, bits), &tileh_new); /* The surface slope must not be changed */ - if ((z_old == z_new) && (tileh_old == tileh_new)) return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + if ((z_old == z_new) && (tileh_old == tileh_new)) return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); } } break; diff --git a/src/roadveh.h b/src/roadveh.h --- a/src/roadveh.h +++ b/src/roadveh.h @@ -184,6 +184,8 @@ protected: // These functions should not if (!this->IsArticulatedPart()) { /* Road vehicle weight is in units of 1/4 t. */ weight += GetVehicleProperty(this, PROP_ROADVEH_WEIGHT, RoadVehInfo(this->engine_type)->weight) / 4; + //DIRTY HACK + if ( !weight ) weight = 1; //at least 1 for realistic accelerate } return weight; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -294,6 +294,7 @@ CommandCost CmdBuildRoadVehicle(DoComman v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + v->breakdown_chance = 128; v->max_age = e->GetLifeLengthInDays(); v->SetServiceInterval(Company::Get(v->owner)->settings.vehicle.servint_roadveh); @@ -375,7 +376,6 @@ CommandCost CmdTurnRoadVeh(DoCommandFlag if ((v->vehstatus & VS_STOPPED) || (v->vehstatus & VS_CRASHED) || - v->breakdown_ctr != 0 || v->overtaking != 0 || v->state == RVSB_WORMHOLE || v->IsInDepot() || @@ -812,6 +812,9 @@ static void RoadVehCheckOvertake(RoadVeh /* For now, articulated road vehicles can't overtake anything. */ if (v->HasArticulatedPart()) return; + /* Don't overtake if the vehicle is broken or about to break down */ + if (v->breakdown_ctr != 0) return; + /* Vehicles are not driving in same direction || direction is not a diagonal direction */ if (v->direction != u->direction || !(v->direction & 1)) return; @@ -1573,8 +1576,7 @@ static bool RoadVehController(RoadVehicl } /* road vehicle has broken down? */ - if (v->HandleBreakdown()) return true; - if (v->vehstatus & VS_STOPPED) { + if (v->HandleBreakdown() || v->vehstatus & VS_STOPPED) { v->SetLastSpeed(); return true; } @@ -1634,6 +1636,7 @@ Money RoadVehicle::GetRunningCost() cons if (e->u.road.running_cost_class == INVALID_PRICE) return 0; uint cost_factor = GetVehicleProperty(this, PROP_ROADVEH_RUNNING_COST_FACTOR, e->u.road.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_road; if (cost_factor == 0) return 0; return GetPrice(e->u.road.running_cost_class, cost_factor, e->GetGRF()); diff --git a/src/saveload/CMakeLists.txt b/src/saveload/CMakeLists.txt --- a/src/saveload/CMakeLists.txt +++ b/src/saveload/CMakeLists.txt @@ -13,6 +13,8 @@ add_files( depot_sl.cpp economy_sl.cpp engine_sl.cpp + extended_ver_sl.cpp + extended_ver_sl.h game_sl.cpp gamelog_sl.cpp goal_sl.cpp @@ -31,6 +33,7 @@ add_files( order_sl.cpp saveload.cpp saveload.h + saveload_buffer.h saveload_filter.h saveload_internal.h settings_sl.cpp diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -59,6 +59,7 @@ #include "saveload_internal.h" +#include "extended_ver_sl.h" #include @@ -750,6 +751,9 @@ bool AfterLoadGame() _settings_game.construction.max_bridge_length = 64; _settings_game.construction.max_tunnel_length = 64; } + if (SlXvIsFeatureMissing(XSLFI_DAYLENGTH)) { + _settings_game.economy.daylength_multiplier = 1; + } if (IsSavegameVersionBefore(SLV_166)) _settings_game.economy.infrastructure_maintenance = false; if (IsSavegameVersionBefore(SLV_183)) { _settings_game.linkgraph.distribution_pax = DT_MANUAL; @@ -757,6 +761,9 @@ bool AfterLoadGame() _settings_game.linkgraph.distribution_armoured = DT_MANUAL; _settings_game.linkgraph.distribution_default = DT_MANUAL; } + if (SlXvIsFeatureMissing(XSLFI_TOWN_IMPROVEMENTS)) { + _settings_game.economy.town_growth_multiplier = _settings_game.economy.daylength_multiplier; + } if (IsSavegameVersionBefore(SLV_ENDING_YEAR)) { _settings_game.game_creation.ending_year = DEF_END_YEAR; @@ -2419,6 +2426,23 @@ bool AfterLoadGame() for (Depot *d : Depot::Iterate()) d->build_date = _date; } + if (!SlXvIsFeaturePresent(XSLFI_EXTRA_EXPENSES, 2, 2)) + { + // expenses size isn't currently 23. fix the array which now contains broken data + + bool first_extension_present = SlXvIsFeaturePresent(XSLFI_EXTRA_EXPENSES, 1, 1); + int old_expenses_size = first_extension_present ? 22 : 13; + int new_expenses_size = 23; + int second_expenses_offset = old_expenses_size - (new_expenses_size - old_expenses_size); + + for (Company* c : Company::Iterate()) { + std::copy_backward(&c->yearly_expenses[1][second_expenses_offset], &c->yearly_expenses[1][second_expenses_offset] + old_expenses_size, &c->yearly_expenses[2][0] + old_expenses_size); + std::copy_backward(&c->yearly_expenses[0][old_expenses_size], &c->yearly_expenses[0][old_expenses_size] + old_expenses_size, &c->yearly_expenses[1][0] + old_expenses_size); + std::fill_n(&c->yearly_expenses[0][old_expenses_size], new_expenses_size - old_expenses_size, 0); + std::fill_n(&c->yearly_expenses[1][old_expenses_size], new_expenses_size - old_expenses_size, 0); + } + } + /* In old versions it was possible to remove an airport while a plane was * taking off or landing. This gives all kind of problems when building * another airport in the same station so we don't allow that anymore. @@ -2822,6 +2846,37 @@ bool AfterLoadGame() * which is done by StartupEngines(). */ if (gcf_res != GLC_ALL_GOOD) StartupEngines(); + /* Set some breakdown-related variables to the correct values. */ + if (SlXvIsFeatureMissing(XSLFI_IMPROVED_BREAKDOWNS)) { + + for (Vehicle *v : Vehicle::Iterate()) { + switch(v->type) { + case VEH_TRAIN: { + if (Train::From(v)->IsFrontEngine()) { + if (v->breakdown_ctr == 1) SetBit(Train::From(v)->flags, VRF_BREAKDOWN_STOPPED); + } else if (Train::From(v)->IsEngine() || Train::From(v)->IsMultiheaded()) { + /** Non-front engines could have a reliability of 0. + * Set it to the reliability of the front engine or the maximum, whichever is lower. */ + const Engine *e = Engine::Get(v->engine_type); + v->reliability_spd_dec = e->reliability_spd_dec; + v->reliability = std::min(v->First()->reliability, e->reliability); + } + } + case VEH_ROAD: + v->breakdown_chance = 128; + break; + case VEH_SHIP: + v->breakdown_chance = 64; + break; + case VEH_AIRCRAFT: + v->breakdown_chance = Clamp(64 + (AircraftVehInfo(v->engine_type)->max_speed >> 3), 0, 255); + v->breakdown_severity = 40; + break; + default: break; + } + } + } + /* The road owner of standard road stops was not properly accounted for. */ if (IsSavegameVersionBefore(SLV_172)) { for (TileIndex t = 0; t < map_size; t++) { diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -19,6 +19,7 @@ #include "../tunnelbridge.h" #include "../station_base.h" #include "../strings_func.h" +#include "../settings_func.h" #include "table/strings.h" @@ -481,9 +482,11 @@ static const SaveLoad _company_desc[] = SLE_CONDVAR(CompanyProperties, bankrupt_value, SLE_VAR_I64 | SLE_FILE_I32, SL_MIN_VERSION, SLV_65), SLE_CONDVAR(CompanyProperties, bankrupt_value, SLE_INT64, SLV_65, SL_MAX_VERSION), - /* yearly expenses was changed to 64-bit in savegame version 2. */ + // please check afterload for XSLFI_EXTRA_EXPENSES before editing this again SLE_CONDARR(CompanyProperties, yearly_expenses, SLE_FILE_I32 | SLE_VAR_I64, 3 * 13, SL_MIN_VERSION, SLV_2), - SLE_CONDARR(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 13, SLV_2, SL_MAX_VERSION), + SLE_CONDARR_X(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 13, SLV_2, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_EXTRA_EXPENSES, 0, 0)), + SLE_CONDARR_X(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 22, SLV_2, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_EXTRA_EXPENSES, 1, 1)), + SLE_CONDARR_X(CompanyProperties, yearly_expenses, SLE_INT64, 3 * 23, SLV_2, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_EXTRA_EXPENSES, 2, 2)), SLE_CONDVAR(CompanyProperties, is_ai, SLE_BOOL, SLV_2, SL_MAX_VERSION), diff --git a/src/saveload/compat/industry_sl_compat.h b/src/saveload/compat/industry_sl_compat.h --- a/src/saveload/compat/industry_sl_compat.h +++ b/src/saveload/compat/industry_sl_compat.h @@ -49,7 +49,7 @@ const SaveLoadCompat _industry_sl_compat SLC_VAR("exclusive_consumer"), SLC_VAR("storage"), SLC_VAR("psa"), - SLC_NULL(1, SLV_82, SLV_197), + SLC_NULL_X(1, SLV_82, SLV_197, SlXvFeatureTest(XSLFTO_AND, XSLFI_EXTRA_EXPENSES, 0, 0)), SLC_VAR("random"), SLC_VAR("text"), SLC_NULL(32, SLV_2, SLV_144), diff --git a/src/saveload/compat/settings_sl_compat.h b/src/saveload/compat/settings_sl_compat.h --- a/src/saveload/compat/settings_sl_compat.h +++ b/src/saveload/compat/settings_sl_compat.h @@ -38,6 +38,7 @@ const SaveLoadCompat _settings_sl_compat SLC_VAR("difficulty.competitor_speed"), SLC_NULL(1, SLV_97, SLV_110), SLC_VAR("difficulty.vehicle_breakdowns"), + SLC_VAR("difficulty.vehicle_breakdown_scaler"), SLC_VAR("difficulty.subsidy_multiplier"), SLC_VAR("difficulty.subsidy_duration"), SLC_VAR("difficulty.construction_cost"), @@ -69,6 +70,7 @@ const SaveLoadCompat _settings_sl_compat SLC_VAR("construction.max_tunnel_length"), SLC_NULL(1, SL_MIN_VERSION, SLV_159), SLC_VAR("construction.train_signal_side"), + SLC_VAR("construction.name_stations_based_on_industries"), SLC_VAR("station.never_expire_airports"), SLC_VAR("economy.town_layout"), SLC_VAR("economy.allow_town_roads"), @@ -118,8 +120,10 @@ const SaveLoadCompat _settings_sl_compat SLC_VAR("vehicle.freight_trains"), SLC_NULL(1, SLV_67, SLV_159), SLC_VAR("vehicle.plane_speed"), + SLC_VAR("vehicle.plane_range_multiplier"), SLC_VAR("vehicle.dynamic_engines"), SLC_VAR("vehicle.plane_crashes"), + SLC_VAR("vehicle.improved_breakdowns"), SLC_NULL(1, SL_MIN_VERSION, SLV_159), SLC_VAR("gui.sg_full_load_any"), SLC_VAR("order.improved_load"), @@ -142,6 +146,7 @@ const SaveLoadCompat _settings_sl_compat SLC_VAR("economy.multiple_industry_per_town"), SLC_NULL(1, SL_MIN_VERSION, SLV_141), SLC_VAR("economy.bribe"), + SLC_VAR("economy.bribe_risky"), SLC_VAR("economy.exclusive_rights"), SLC_VAR("economy.fund_buildings"), SLC_VAR("economy.fund_roads"), @@ -154,6 +159,8 @@ const SaveLoadCompat _settings_sl_compat SLC_NULL(4, SL_MIN_VERSION, SLV_105), SLC_VAR("game_creation.ending_year"), SLC_VAR("economy.type"), + SLC_VAR("economy.daylength_multiplier"), + SLC_VAR("economy.town_growth_multiplier"), SLC_VAR("economy.allow_shares"), SLC_VAR("economy.min_years_for_shares"), SLC_VAR("economy.feeder_payment_share"), @@ -179,6 +186,26 @@ const SaveLoadCompat _settings_sl_compat SLC_VAR("economy.town_noise_population[1]"), SLC_VAR("economy.town_noise_population[2]"), SLC_VAR("economy.infrastructure_maintenance"), + SLC_VAR("economy.infrastructure_base_cost_rail"), + SLC_VAR("economy.infrastructure_base_cost_road"), + SLC_VAR("economy.infrastructure_base_cost_water"), + SLC_VAR("economy.infrastructure_base_cost_air"), + SLC_VAR("economy.infrastructure_base_cost_station"), + SLC_VAR("economy.running_cost_multiplier_rail"), + SLC_VAR("economy.running_cost_multiplier_road"), + SLC_VAR("economy.running_cost_multiplier_water"), + SLC_VAR("economy.running_cost_multiplier_air"), + SLC_VAR("economy.dilapidation_max_amount"), + SLC_VAR("economy.dilapidation_increase"), + SLC_VAR("economy.dilapidation_decrease"), + SLC_VAR("economy.dilapidation_pop_rail"), + SLC_VAR("economy.dilapidation_pop_road"), + SLC_VAR("economy.dilapidation_pop_water"), + SLC_VAR("economy.dilapidation_pop_air"), + SLC_VAR("economy.dilapidation_fine_rail"), + SLC_VAR("economy.dilapidation_fine_road"), + SLC_VAR("economy.dilapidation_fine_water"), + SLC_VAR("economy.dilapidation_fine_air"), SLC_VAR("pf.wait_for_pbs_path"), SLC_VAR("pf.reserve_paths"), SLC_VAR("pf.path_backoff_interval"), diff --git a/src/saveload/compat/station_sl_compat.h b/src/saveload/compat/station_sl_compat.h --- a/src/saveload/compat/station_sl_compat.h +++ b/src/saveload/compat/station_sl_compat.h @@ -117,6 +117,7 @@ const SaveLoadCompat _station_normal_sl_ SLC_VAR("had_vehicle_of_type"), SLC_VAR("loading_vehicles"), SLC_VAR("always_accepted"), + SLC_VAR("dilapidation"), SLC_VAR("goods"), }; diff --git a/src/saveload/compat/vehicle_sl_compat.h b/src/saveload/compat/vehicle_sl_compat.h --- a/src/saveload/compat/vehicle_sl_compat.h +++ b/src/saveload/compat/vehicle_sl_compat.h @@ -75,6 +75,8 @@ const SaveLoadCompat _vehicle_common_sl_ SLC_VAR("breakdown_delay"), SLC_VAR("breakdowns_since_last_service"), SLC_VAR("breakdown_chance"), + SLC_VAR("breakdown_type"), + SLC_VAR("breakdown_severity"), SLC_VAR("build_year"), SLC_VAR("load_unload_ticks"), SLC_VAR("cargo_paid_for"), diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp new file mode 100644 --- /dev/null +++ b/src/saveload/extended_ver_sl.cpp @@ -0,0 +1,651 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file extended_ver_sl.cpp Functions related to handling save/load extended version info. + * + * Known extended features are stored in _sl_xv_feature_versions, features which are currently enabled/in use and their versions are stored in the savegame. + * On load, the list of features and their versions are loaded from the savegame. If the savegame contains a feature which is either unknown, or has too high a version, + * loading can be either aborted, or the feature can be ignored if the the feature flags in the savegame indicate that it can be ignored. The savegame may also list any additional + * chunk IDs which are associated with an extended feature, these can be discarded if the feature is discarded. + * This information is stored in the SLXI chunk, the contents of which has the following format: + * + * uint32 chunk version + * uint32 chunk flags + * uint32 number of sub chunks/features + * For each of N sub chunk/feature: + * uint32 feature flags (SlxiSubChunkFlags) + * uint16 feature version + * SLE_STR feature name + * uint32* extra data length [only present iff feature flags & XSCF_EXTRA_DATA_PRESENT] + * N bytes extra data + * uint32* chunk ID list count [only present iff feature flags & XSCF_CHUNK_ID_LIST_PRESENT] + * N x uint32 chunk ID list + * + * Extended features as recorded in the SLXI chunk, above, MAY add, remove, change, or otherwise modify fields in chunks + * not owned by the feature and therefore not listed in the sub chunk/feature information in the SLXI chunk. + * In this case the XSCF_IGNORABLE_UNKNOWN flag SHOULD NOT be set, as it is not possible to correctly load the modified chunk without + * knowledge of the feature. + * In the case where the modifications to other chunks vary with respect to lower feature versions, the XSCF_IGNORABLE_VERSION flag + * also SHOULD NOT be set. + * Use of the XSCF_IGNORABLE_UNKNOWN and XSCF_IGNORABLE_VERSION flags MUST ONLY be used in the cases where the feature and any + * associated chunks can be cleanly dropped, and the savegame can be correctly loaded by a client with no knowledge of the feature. + */ + +#include "../stdafx.h" +#include "../debug.h" +#include "saveload.h" +#include "saveload_buffer.h" +#include "extended_ver_sl.h" +#include "../rev.h" + +#include +#include + +#include "../safeguards.h" + +uint16 _sl_xv_feature_versions[XSLFI_SIZE]; ///< array of all known feature types and their current versions +bool _sl_is_ext_version; ///< is this an extended savegame version, with more info in the SLXI chunk? +bool _sl_might_be_legacy_patchpack_save; ///< might this be a save from an earlier version of the patchpack? +SaveLoadVersion _sl_is_faked_ext; ///< is this a faked extended savegame version, with no SLXI chunk? See: SlXvCheckSpecialSavegameVersions. +std::vector _sl_xv_discardable_chunk_ids; ///< list of chunks IDs which we can discard if no chunk loader exists +std::string _sl_xv_version_label; ///< optional SLXI version label + +static const uint32 _sl_xv_slxi_chunk_version = 0; ///< current version of SLXI chunk + +static void loadVL(const SlxiSubChunkInfo* info, uint32 length); +static uint32 saveVL(const SlxiSubChunkInfo* info, bool dry_run); + +const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { + { XSLFI_VERSION_LABEL, XSCF_IGNORABLE_ALL, 1, 1, "version_label", saveVL, loadVL, nullptr }, + { XSLFI_PATCHPACK_MAJOR_VERSION, XSCF_NULL, 6, 6, "ginever_patchpack_version", nullptr, nullptr, nullptr }, + { XSLFI_DAYLENGTH, XSCF_NULL, 1, 1, "ginever_daylength", nullptr, nullptr, nullptr }, + { XSLFI_STATION_NEWGRF, XSCF_NULL, 1, 1, "ginever_station_newgrf", nullptr, nullptr, nullptr }, + { XSLFI_INFRASTRUCTURE, XSCF_NULL, 1, 1, "ginever_infrastructure", nullptr, nullptr, nullptr }, + { XSLFI_RUNNING_COST_MULT, XSCF_NULL, 1, 1, "ginever_running_cost_mult", nullptr, nullptr, nullptr }, + { XSLFI_PLANE_RANGE_MULT, XSCF_NULL, 1, 1, "ginever_plane_range_mult", nullptr, nullptr, nullptr }, + { XSLFI_EXTRA_EXPENSES, XSCF_NULL, 2, 2, "ginever_extra_expenses", nullptr, nullptr, nullptr }, + { XSLFI_IMPROVED_BREAKDOWNS, XSCF_NULL, 1, 1, "ginever_improved_breakdowns", nullptr, nullptr, nullptr }, + { XSLFI_BREAKDOWN_SCALER, XSCF_NULL, 1, 1, "ginever_breakdown_scaler", nullptr, nullptr, nullptr }, + { XSLFI_TOWN_IMPROVEMENTS, XSCF_NULL, 1, 1, "ginever_town_improvements", nullptr, nullptr, nullptr }, + { XSLFI_DILAPIDATION, XSCF_NULL, 1, 1, "ginever_dilapidation", nullptr, nullptr, nullptr }, + + { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker +}; + +/** + * Extended save/load feature test + * + * First performs a tradional check on the provided @p savegame_version against @p savegame_version_from and @p savegame_version_to. + * Then, if the feature set in the constructor is not XSLFI_NULL, also check than the feature version is inclusively bounded by @p min_version and @p max_version, + * and return the combination of the two tests using the operator defined in the constructor. + * Otherwise just returns the result of the savegame version test + */ +bool SlXvFeatureTest::IsFeaturePresent(SaveLoadVersion savegame_version, SaveLoadVersion savegame_version_from, SaveLoadVersion savegame_version_to) const +{ + bool savegame_version_ok = savegame_version >= savegame_version_from && savegame_version < savegame_version_to; + + if (this->functor) return (*this->functor)(savegame_version, savegame_version_ok); + + if (this->feature == XSLFI_NULL) return savegame_version_ok; + + bool feature_ok = SlXvIsFeaturePresent(this->feature, this->min_version, this->max_version); + + switch (op) { + case XSLFTO_OR: + return savegame_version_ok || feature_ok; + + case XSLFTO_AND: + return savegame_version_ok && feature_ok; + + default: + NOT_REACHED(); + return false; + } +} + +/** + * Returns true if @p feature is present and has a version inclusively bounded by @p min_version and @p max_version + */ +bool SlXvIsFeaturePresent(SlXvFeatureIndex feature, uint16 min_version, uint16 max_version) +{ + assert(feature < XSLFI_SIZE); + return _sl_xv_feature_versions[feature] >= min_version && _sl_xv_feature_versions[feature] <= max_version; +} + +/** + * Returns true if @p feature is present and has a version inclusively bounded by @p min_version and @p max_version + */ +const char *SlXvGetFeatureName(SlXvFeatureIndex feature) +{ + const SlxiSubChunkInfo *info = _sl_xv_sub_chunk_infos; + for (; info->index != XSLFI_NULL; ++info) { + if (info->index == feature) { + return info->name; + } + } + return "(unknown feature)"; +} + +/** + * Resets all extended feature versions to 0 + */ +void SlXvResetState() +{ + _sl_is_ext_version = false; + _sl_might_be_legacy_patchpack_save = false; + _sl_is_faked_ext = SL_MIN_VERSION; + _sl_xv_version_label = ""; + _sl_xv_discardable_chunk_ids.clear(); + memset(_sl_xv_feature_versions, 0, sizeof(_sl_xv_feature_versions)); +} + +/** + * Resets all extended feature versions to their currently enabled versions, i.e. versions suitable for saving + */ +void SlXvSetCurrentState() +{ + SlXvResetState(); + _sl_is_ext_version = true; + _sl_might_be_legacy_patchpack_save = false; + + const SlxiSubChunkInfo *info = _sl_xv_sub_chunk_infos; + for (; info->index != XSLFI_NULL; ++info) { + _sl_xv_feature_versions[info->index] = info->save_version; + } +} + +/** + * Check for "special" savegame versions (i.e. known patchpacks) and set correct savegame version, settings, etc. + * Only called if load_legacy_patchpack_savedata is enabled in settings. + */ +void SlXvCheckSpecialSavegameVersionsA() +{ + extern SaveLoadVersion _sl_version; + if (_sl_version == SLV_PP1X + || _sl_version == SLV_PP2X + || _sl_version == SLV_PP3X + || _sl_version == SLV_PP4X + || _sl_version == SLV_PP5X_DAYLENGTH + || _sl_version == SLV_PP5X_HOTFIX_174 + || _sl_version == SLV_PP5X_HOTFIX_175 + || _sl_version == SLV_PP5X_HOTFIX_177 + || _sl_version == SLV_PP5X_HOTFIX_187 + || _sl_version == SLV_PP5X_STATION_NEWGRF + || _sl_version == SLV_PP5X_INFRASTRUCTURE + || _sl_version == SLV_PP5X_RUNNING_COST_MULT + || _sl_version == SLV_PP5X_PLANE_RANGE_MULT + || _sl_version == SLV_PP5X_HOTFIX_196 + || _sl_version == SLV_PP5X_EXTRA_EXPENSES + || _sl_version == SLV_PP5X_HOTFIX_198 + || _sl_version == SLV_PP5X_IMPROVED_BREAKDOWNS + || _sl_version == SLV_PP5X_BREAKDOWN_SCALER + || _sl_version == SLV_PP5X_TOWN_IMPROVEMENTS + || _sl_version == SLV_PP5X_DILAPIDATION + || _sl_version == SLV_PP5X_HOTFIX_222) + { + Debug(sl, 1, "Save version {} might be a patchpack save, flagging for save version checks", _sl_version); + _sl_might_be_legacy_patchpack_save = true; + } +} + +/** + * Check for "special" savegame versions (i.e. known patchpacks) and set correct savegame version, settings, etc. + * Only called if load_legacy_patchpack_savedata is enabled in settings. + */ +void SlXvCheckSpecialSavegameVersionsB() +{ + if (!_sl_might_be_legacy_patchpack_save) + { + _load_check_data.save_version_label = _sl_xv_version_label; + return; + } + _sl_might_be_legacy_patchpack_save = false; + + Debug(sl, 1, "Checking potential patchpack save against gamelog revision '{}'", _sl_xv_version_label); + + extern SaveLoadVersion _sl_version; + SaveLoadVersion original_version = _sl_version; + + if (original_version == SLV_PP1X) + { + // the gamelog revision should be in the format "rXXXXXM-TTR Alp" + if (_sl_xv_version_label.find("M-TTR") == 6) + { + _sl_is_faked_ext = original_version; + _sl_xv_version_label = "ginever-1.x"; + _sl_version = SLV_100; + _sl_xv_feature_versions[XSLFI_PATCHPACK_MAJOR_VERSION] = 1; + //_sl_xv_feature_versions[XSLFI_LEGACY_DAYLENGTH] = 1; + + Debug(sl, 1, "Loading a patchpack 1.x savegame version {} as version {} with extensions", original_version, _sl_version); + _load_check_data.save_ext_type = PSXT_MODIFIED; + _load_check_data.save_version_label = _sl_xv_version_label; + SlError(STR_GAME_SAVELOAD_ERROR_UNSUPPORTED_GPP); // temporary + return; + } + } + if (original_version == SLV_PP2X) + { + // the gamelog revision should be in the format "v2.XXXXX.YYY" or "Rev XXXXX-YYY" + if (_sl_xv_version_label.find("v2.") == 0 || _sl_xv_version_label.find("-") == 9) + { + _sl_is_faked_ext = original_version; + _sl_xv_version_label = "ginever-2.x"; + _sl_version = SLV_125; + _sl_xv_feature_versions[XSLFI_PATCHPACK_MAJOR_VERSION] = 2; + //_sl_xv_feature_versions[XSLFI_LEGACY_DAYLENGTH] = 2; + + Debug(sl, 1, "Loading a patchpack 2.x savegame version {} as version {} with extensions", original_version, _sl_version); + _load_check_data.save_ext_type = PSXT_MODIFIED; + _load_check_data.save_version_label = _sl_xv_version_label; + SlError(STR_GAME_SAVELOAD_ERROR_UNSUPPORTED_GPP); // temporary + return; + } + } + if (original_version == SLV_PP3X) + { + // the gamelog revision should be in the format "v3.XXXXX.YYY" + if (_sl_xv_version_label.find("v3.") == 0) + { + _sl_is_faked_ext = original_version; + _sl_xv_version_label = "ginever-3.x"; + _sl_version = SLV_141; + _sl_xv_feature_versions[XSLFI_PATCHPACK_MAJOR_VERSION] = 3; + //_sl_xv_feature_versions[XSLFI_DAYLENGTH] = 1; + //_sl_xv_feature_versions[XSLFI_LEGACY_CARGODIST] = 1; + + Debug(sl, 1, "Loading a patchpack 3.x savegame version {} as version {} with extensions", original_version, _sl_version); + _load_check_data.save_ext_type = PSXT_MODIFIED; + _load_check_data.save_version_label = _sl_xv_version_label; + SlError(STR_GAME_SAVELOAD_ERROR_UNSUPPORTED_GPP); // temporary + return; + } + } + if (original_version == SLV_PP4X) + { + // the gamelog revision should be in the format "v4.XXXXX.YYY" + if (_sl_xv_version_label.find("v4.") == 0) + { + _sl_is_faked_ext = original_version; + _sl_xv_version_label = "ginever-4.x"; + _sl_version = SLV_142; + _sl_xv_feature_versions[XSLFI_PATCHPACK_MAJOR_VERSION] = 4; + //_sl_xv_feature_versions[XSLFI_DAYLENGTH] = 1; + //_sl_xv_feature_versions[XSLFI_LEGACY_CARGODIST] = 2; + + Debug(sl, 1, "Loading a patchpack 4.x savegame version {} as version {} with extensions", original_version, _sl_version); + _load_check_data.save_ext_type = PSXT_MODIFIED; + _load_check_data.save_version_label = _sl_xv_version_label; + SlError(STR_GAME_SAVELOAD_ERROR_UNSUPPORTED_GPP); // temporary + return; + } + } + if (original_version == SLV_PP5X_DAYLENGTH + || original_version == SLV_PP5X_HOTFIX_174 + || original_version == SLV_PP5X_HOTFIX_175 + || original_version == SLV_PP5X_HOTFIX_177 + || original_version == SLV_PP5X_HOTFIX_187 + || original_version == SLV_PP5X_STATION_NEWGRF + || original_version == SLV_PP5X_INFRASTRUCTURE + || original_version == SLV_PP5X_RUNNING_COST_MULT + || original_version == SLV_PP5X_PLANE_RANGE_MULT + || original_version == SLV_PP5X_HOTFIX_196 + || original_version == SLV_PP5X_EXTRA_EXPENSES + || original_version == SLV_PP5X_HOTFIX_198 + || original_version == SLV_PP5X_IMPROVED_BREAKDOWNS + || original_version == SLV_PP5X_BREAKDOWN_SCALER + || original_version == SLV_PP5X_TOWN_IMPROVEMENTS + || original_version == SLV_PP5X_DILAPIDATION + || original_version == SLV_PP5X_HOTFIX_222 + ) + { + // the gamelog revision should be in the format "v5.XXXXX.YYY", but some of the newer versions have broken log messages and just printed "u" + if (_sl_xv_version_label.find("v5.") == 0 || _sl_xv_version_label == "u") + { + _sl_xv_feature_versions[XSLFI_PATCHPACK_MAJOR_VERSION] = 5; + + if (original_version >= SLV_PP5X_DAYLENGTH) + { + _sl_is_faked_ext = original_version; + _sl_xv_version_label = "ginever-5.x"; + _sl_version = SLV_160; + _sl_xv_feature_versions[XSLFI_DAYLENGTH] = 1; + } + if (original_version >= SLV_PP5X_HOTFIX_174) + { + _sl_version = SLV_174; + } + if (original_version >= SLV_PP5X_HOTFIX_175) + { + _sl_version = SLV_175; + } + if (original_version >= SLV_PP5X_HOTFIX_177) + { + _sl_version = SLV_177; + } + if (original_version >= SLV_PP5X_HOTFIX_187) + { + _sl_version = SLV_187; + } + if (original_version >= SLV_PP5X_STATION_NEWGRF) + { + _sl_version = SLV_187; + _sl_xv_feature_versions[XSLFI_STATION_NEWGRF] = 1; + } + if (original_version >= SLV_PP5X_INFRASTRUCTURE) + { + _sl_xv_feature_versions[XSLFI_INFRASTRUCTURE] = 1; + } + if (original_version >= SLV_PP5X_RUNNING_COST_MULT) + { + _sl_version = SLV_194; + _sl_xv_feature_versions[XSLFI_RUNNING_COST_MULT] = 1; + } + if (original_version >= SLV_PP5X_PLANE_RANGE_MULT) + { + _sl_xv_feature_versions[XSLFI_PLANE_RANGE_MULT] = 1; + } + if (original_version >= SLV_PP5X_HOTFIX_196) + { + _sl_version = SLV_196; + } + if (original_version >= SLV_PP5X_EXTRA_EXPENSES) + { + _sl_xv_feature_versions[XSLFI_EXTRA_EXPENSES] = 1; + } + if (original_version >= SLV_PP5X_HOTFIX_198) + { + _sl_version = SLV_198; + } + if (original_version >= SLV_PP5X_IMPROVED_BREAKDOWNS) + { + _sl_version = SLV_MULTITILE_DOCKS; + _sl_xv_feature_versions[XSLFI_IMPROVED_BREAKDOWNS] = 1; + } + if (original_version >= SLV_PP5X_BREAKDOWN_SCALER) + { + _sl_xv_feature_versions[XSLFI_BREAKDOWN_SCALER] = 1; + } + if (original_version >= SLV_PP5X_TOWN_IMPROVEMENTS) + { + _sl_xv_feature_versions[XSLFI_TOWN_IMPROVEMENTS] = 1; + } + if (original_version >= SLV_PP5X_DILAPIDATION) + { + _sl_xv_feature_versions[XSLFI_DILAPIDATION] = 1; + _sl_xv_feature_versions[XSLFI_EXTRA_EXPENSES] = 2; + } + if (original_version >= SLV_PP5X_HOTFIX_222) + { + _sl_version = SLV_ENDING_YEAR; + } + + Debug(sl, 1, "Loading a patchpack 5.x savegame version {} as version {} with extensions", original_version, _sl_version); + _load_check_data.save_ext_type = PSXT_MODIFIED; + _load_check_data.save_version_label = _sl_xv_version_label; + return; + } + } + + Debug(sl, 1, "This doesn't appear to be a patchpack save"); + _load_check_data.save_version_label = _sl_xv_version_label; + + // now we have to restore the original OTTD invalid save version check for non-Ginever patchpacks since we had to previously disable it + if (original_version >= SLV_START_PATCHPACKS && original_version < SLV_END_PATCHPACKS) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK); +} + +/** + * Return true if this chunk has been marked as discardable + */ +bool SlXvIsChunkDiscardable(uint32 id) +{ + for (size_t i = 0; i < _sl_xv_discardable_chunk_ids.size(); i++) { + if (_sl_xv_discardable_chunk_ids[i] == id) { + return true; + } + } + return false; +} + +/** + * Writes a chunk ID list string to the savegame, returns the number of chunks written + * In dry run mode, only returns the number of chunk which would have been written + */ +static uint32 WriteChunkIdList(const char *chunk_list, bool dry_run) +{ + unsigned int chunk_count = 0; // number of chunks output + unsigned int id_offset = 0; // how far are we into the ID + for (; *chunk_list != 0; chunk_list++) { + if (id_offset == 4) { + assert(*chunk_list == ','); + id_offset = 0; + } else { + if (!dry_run) { + SlWriteByte(*chunk_list); + } + if (id_offset == 3) { + chunk_count++; + } + id_offset++; + } + } + assert(id_offset == 4); + return chunk_count; +} + +char name_buffer[256]; + +static void loadVL(const SlxiSubChunkInfo* info, uint32 length) +{ + _sl_xv_version_label.resize(length); + ReadBuffer::GetCurrent()->CopyBytes(reinterpret_cast(_sl_xv_version_label.data()), length); + Debug(sl, 2, "SLXI version label: %s", _sl_xv_version_label.c_str()); + _load_check_data.save_version_label = _sl_xv_version_label; // allows early print of version if extension is detected +} + +static uint32 saveVL(const SlxiSubChunkInfo* info, bool dry_run) +{ + const size_t length = strlen(_openttd_revision); + if (!dry_run) MemoryDumper::GetCurrent()->CopyBytes(const_cast(reinterpret_cast(_openttd_revision)), length); + return static_cast(length); +} + +struct SLXIChunkHandler : ChunkHandler { + SLXIChunkHandler() : ChunkHandler('SLXI', CH_RIFF) {} + + void LoadCheck(size_t len = 0) const override + { + Load(); // required to be able to correctly interpret other check data + } + + void Load() const override + { + if (_sl_is_faked_ext != SL_MIN_VERSION || !_sl_is_ext_version || _sl_might_be_legacy_patchpack_save) { + SlErrorCorrupt("SLXI chunk is unexpectedly present"); + } + + SlXvResetState(); + _sl_is_ext_version = true; + + uint32 version = SlReadUint32(); + if (version > _sl_xv_slxi_chunk_version) SlErrorCorruptFmt("SLXI chunk: version: %u is too new (expected max: %u)", version, _sl_xv_slxi_chunk_version); + + uint32 chunk_flags = SlReadUint32(); + // flags are not in use yet, reserve for future expansion + if (chunk_flags != 0) SlErrorCorruptFmt("SLXI chunk: unknown chunk header flags: 0x%X", chunk_flags); + + const SaveLoad xlsi_sub_chunk_name_desc[] = { + SLEG_STR("feature", name_buffer, SLE_STRB) + }; + + uint32 item_count = SlReadUint32(); + for (uint32 i = 0; i < item_count; i++) { + SlxiSubChunkFlags flags = static_cast(SlReadUint32()); + uint16 version = SlReadUint16(); + SlGlobList(xlsi_sub_chunk_name_desc); + + // linearly scan through feature list until found name match + bool found = false; + const SlxiSubChunkInfo* info = _sl_xv_sub_chunk_infos; + for (; info->index != XSLFI_NULL; ++info) { + if (strcmp(name_buffer, info->name) == 0) { + found = true; + break; + } + } + + bool discard_chunks = false; + if (found) { + if (version > info->max_version) { + if (flags & XSCF_IGNORABLE_VERSION) { + // version too large but carry on regardless + discard_chunks = true; + if (flags & XSCF_EXTRA_DATA_PRESENT) { + SlSkipBytes(SlReadUint32()); // skip extra data field + } + Debug(sl, 1, "SLXI chunk: too large version for feature: '{}', version: {}, max version: {}, ignoring", name_buffer, version, info->max_version); + } + else { + SlErrorCorruptFmt("SLXI chunk: too large version for feature: '%s', version: %d, max version: %d", name_buffer, version, info->max_version); + } + } + else { + // success path :) + + _sl_xv_feature_versions[info->index] = version; + if (flags & XSCF_EXTRA_DATA_PRESENT) { + uint32 extra_data_size = SlReadUint32(); + if (extra_data_size) { + if (info->load_proc) { + size_t read = SlGetBytesRead(); + info->load_proc(info, extra_data_size); + if (SlGetBytesRead() != read + extra_data_size) { + SlErrorCorruptFmt("SLXI chunk: feature: %s, version: %d, extra data length mismatch", name_buffer, version); + } + } + else { + SlErrorCorruptFmt("SLXI chunk: feature: %s, version: %d, unexpectedly includes extra data", name_buffer, version); + } + } + } + + Debug(sl, 1, "SLXI chunk: found known feature: '{}', version: {}, max version: {}", name_buffer, version, info->max_version); + } + } + else { + if (flags & XSCF_IGNORABLE_UNKNOWN) { + // not found but carry on regardless + discard_chunks = true; + if (flags & XSCF_EXTRA_DATA_PRESENT) { + SlSkipBytes(SlReadUint32()); // skip extra data field + } + Debug(sl, 1, "SLXI chunk: unknown feature: '{}', version: {}, ignoring", name_buffer, version); + } + else { + SlErrorCorruptFmt("SLXI chunk: unknown feature: %s, version: %d", name_buffer, version); + } + } + + // at this point the extra data field should have been consumed + // handle chunk ID list field + if (flags & XSCF_CHUNK_ID_LIST_PRESENT) { + uint32 chunk_count = SlReadUint32(); + for (uint32 j = 0; j < chunk_count; j++) { + uint32 chunk_id = SlReadUint32(); + if (discard_chunks) { + _sl_xv_discardable_chunk_ids.push_back(chunk_id); + Debug(sl, 2, "SLXI chunk: unknown feature: '{}', discarding chunk: {:c}{:c}{:c}{:c}", name_buffer, chunk_id >> 24, chunk_id >> 16, chunk_id >> 8, chunk_id); + } + } + } + } + } + + void Save() const override + { + SlXvSetCurrentState(); + + static const SaveLoad _xlsi_sub_chunk_desc[] = { + SLE_STR(SlxiSubChunkInfo, name, SLE_STR, 0) + }; + + // calculate lengths + uint32 item_count = 0; + uint32 length = 12; + std::vector extra_data_lengths; + std::vector chunk_counts; + extra_data_lengths.resize(XSLFI_SIZE); + chunk_counts.resize(XSLFI_SIZE); + const SlxiSubChunkInfo* info = _sl_xv_sub_chunk_infos; + for (; info->index != XSLFI_NULL; ++info) { + if (_sl_xv_feature_versions[info->index] > 0) { + item_count++; + length += 6; + length += (uint32)SlCalcObjLength(info, _xlsi_sub_chunk_desc); + if (info->save_proc) { + uint32 extra_data_length = info->save_proc(info, true); + if (extra_data_length) { + extra_data_lengths[info->index] = extra_data_length; + length += 4 + extra_data_length; + } + } + if (info->chunk_list) { + uint32 chunk_count = WriteChunkIdList(info->chunk_list, true); + if (chunk_count) { + chunk_counts[info->index] = chunk_count; + length += 4 * (1 + chunk_count); + } + } + } + } + + // write header + SlSetLength(length); + SlWriteUint32(_sl_xv_slxi_chunk_version); // chunk version + SlWriteUint32(0); // flags + SlWriteUint32(item_count); // item count + + // write data + info = _sl_xv_sub_chunk_infos; + for (; info->index != XSLFI_NULL; ++info) { + uint16 save_version = _sl_xv_feature_versions[info->index]; + if (save_version > 0) { + SlxiSubChunkFlags flags = info->flags; + assert(!(flags & (XSCF_EXTRA_DATA_PRESENT | XSCF_CHUNK_ID_LIST_PRESENT))); + uint32 extra_data_length = extra_data_lengths[info->index]; + uint32 chunk_count = chunk_counts[info->index]; + if (extra_data_length > 0) flags |= XSCF_EXTRA_DATA_PRESENT; + if (chunk_count > 0) flags |= XSCF_CHUNK_ID_LIST_PRESENT; + SlWriteUint32(flags); + SlWriteUint16(save_version); + SlObject(const_cast(info), _xlsi_sub_chunk_desc); + + if (extra_data_length > 0) { + SlWriteUint32(extra_data_length); + size_t written = SlGetBytesWritten(); + info->save_proc(info, false); + assert(SlGetBytesWritten() == written + extra_data_length); + } + if (chunk_count > 0) { + SlWriteUint32(chunk_count); + size_t written = SlGetBytesWritten(); + WriteChunkIdList(info->chunk_list, false); + assert(SlGetBytesWritten() == written + (chunk_count * 4)); + } + } + } + } +}; + +static const SLXIChunkHandler SLXI; +static const ChunkHandlerRef version_ext_chunk_handlers[] = { + SLXI, +}; + +extern const ChunkHandlerTable _version_ext_chunk_handlers(version_ext_chunk_handlers); diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h new file mode 100644 --- /dev/null +++ b/src/saveload/extended_ver_sl.h @@ -0,0 +1,138 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file extended_ver_sl.h Functions/types related to handling save/load extended version info. */ + +#ifndef EXTENDED_VER_SL_H +#define EXTENDED_VER_SL_H + +#include "../core/bitmath_func.hpp" + +#include + +enum SaveLoadVersion : uint16; + +/** + * List of extended features, each feature has its own (16 bit) version + */ +enum SlXvFeatureIndex { + XSLFI_NULL = 0, ///< Unused value, to indicate that no extended feature test is in use + //=================================== + // COMMON EXTENDED FEATURES + //=================================== + XSLFI_VERSION_LABEL, ///< Version label + XSLFI_PATCHPACK_MAJOR_VERSION, ///< Patchpack major version + //=================================== + // PATCHPACK 5.X FEATURES + //=================================== + XSLFI_DAYLENGTH, ///< Variable day length + XSLFI_STATION_NEWGRF, ///< Station names based on industries + XSLFI_INFRASTRUCTURE, ///< Custom infrastructure base costs + XSLFI_RUNNING_COST_MULT, ///< Running cost multiplier + XSLFI_PLANE_RANGE_MULT, ///< Plane range multiplier + XSLFI_EXTRA_EXPENSES, ///< Additional expense types in the expenses window + XSLFI_IMPROVED_BREAKDOWNS, ///< Improved breakdowns patch + XSLFI_BREAKDOWN_SCALER, ///< Additional breakdown chance scaler + XSLFI_TOWN_IMPROVEMENTS, ///< Configurable bribe risk and town growth factor + XSLFI_DILAPIDATION, ///< Station dilapidation + //=================================== + XSLFI_SIZE, ///< Total count of features, including null feature +}; + +extern uint16 _sl_xv_feature_versions[XSLFI_SIZE]; + +/** + * Operator to use when combining traditional savegame number test with an extended feature version test + */ +enum SlXvFeatureTestOperator { + XSLFTO_OR = 0, ///< Test if traditional savegame version is in bounds OR extended feature is in version bounds + XSLFTO_AND ///< Test if traditional savegame version is in bounds AND extended feature is in version bounds +}; + +/** + * Structure to describe an extended feature version test, and how it combines with a traditional savegame version test + */ +struct SlXvFeatureTest { + using TestFunctorPtr = bool (*)(uint16, bool); ///< Return true if feature present, first parameter is standard savegame version, second is whether standard savegame version is within bounds + + private: + uint16 min_version; + uint16 max_version; + SlXvFeatureIndex feature; + SlXvFeatureTestOperator op; + TestFunctorPtr functor = nullptr; + + public: + SlXvFeatureTest() + : min_version(0), max_version(0), feature(XSLFI_NULL), op(XSLFTO_OR) { } + + SlXvFeatureTest(SlXvFeatureTestOperator op_, SlXvFeatureIndex feature_, uint16 min_version_ = 1, uint16 max_version_ = 0xFFFF) + : min_version(min_version_), max_version(max_version_), feature(feature_), op(op_) { } + + SlXvFeatureTest(TestFunctorPtr functor_) + : min_version(0), max_version(0), feature(XSLFI_NULL), op(XSLFTO_OR), functor(functor_) { } + + bool IsFeaturePresent(SaveLoadVersion savegame_version, SaveLoadVersion savegame_version_from, SaveLoadVersion savegame_version_to) const; +}; + +bool SlXvIsFeaturePresent(SlXvFeatureIndex feature, uint16 min_version = 1, uint16 max_version = 0xFFFF); + +/** + * Returns true if @p feature is missing (i.e. has a version of 0) + */ +inline bool SlXvIsFeatureMissing(SlXvFeatureIndex feature) +{ + return !SlXvIsFeaturePresent(feature); +} + +const char *SlXvGetFeatureName(SlXvFeatureIndex feature); + +/** + * sub chunk flags, this is saved as-is + * (XSCF_EXTRA_DATA_PRESENT and XSCF_CHUNK_ID_LIST_PRESENT must only be set by the save code, and read by the load code) + */ +enum SlxiSubChunkFlags { + XSCF_NULL = 0, ///< zero value + XSCF_IGNORABLE_UNKNOWN = 1 << 0, ///< the loader is free to ignore this without aborting the load if it doesn't know what it is at all + XSCF_IGNORABLE_VERSION = 1 << 1, ///< the loader is free to ignore this without aborting the load if the version is greater than the maximum that can be loaded + XSCF_EXTRA_DATA_PRESENT = 1 << 2, ///< extra data field is present, extra data in some sub-chunk/feature specific format, not used for anything yet + XSCF_CHUNK_ID_LIST_PRESENT = 1 << 3, ///< chunk ID list field is present, list of chunks which this sub-chunk/feature adds to the save game, this can be used to discard the chunks if the feature is unknown + + XSCF_IGNORABLE_ALL = XSCF_IGNORABLE_UNKNOWN | XSCF_IGNORABLE_VERSION, ///< all "ignorable" flags +}; +DECLARE_ENUM_AS_BIT_SET(SlxiSubChunkFlags) + +struct SlxiSubChunkInfo; + +typedef uint32 SlxiSubChunkSaveProc(const SlxiSubChunkInfo *info, bool dry_run); ///< sub chunk save procedure type, must return length and write no data when dry_run is true +typedef void SlxiSubChunkLoadProc(const SlxiSubChunkInfo *info, uint32 length); ///< sub chunk load procedure, must consume length bytes + +/** Handlers and description of chunk. */ +struct SlxiSubChunkInfo { + SlXvFeatureIndex index; ///< feature index, this is saved + SlxiSubChunkFlags flags; ///< flags, this is saved + uint16 save_version; ///< version to save + uint16 max_version; ///< maximum version to accept on load + const char *name; ///< feature name, this *IS* saved, so must be globally unique + SlxiSubChunkSaveProc *save_proc; ///< save procedure of the sub chunk, this may be nullptr in which case no extra chunk data is saved + SlxiSubChunkLoadProc *load_proc; ///< load procedure of the sub chunk, this may be nullptr in which case the extra chunk data must be missing or of 0 length + const char *chunk_list; ///< this is a list of chunks that this feature uses, which should be written to the savegame, this must be a comma-seperated list of 4-character IDs, with no spaces, or nullptr +}; + +void SlXvResetState(); + +void SlXvSetCurrentState(); + +void SlXvCheckSpecialSavegameVersionsA(); + +void SlXvCheckSpecialSavegameVersionsB(); + +bool SlXvIsChunkDiscardable(uint32 id); + +#endif /* EXTENDED_VER_SL_H */ diff --git a/src/saveload/gamelog_sl.cpp b/src/saveload/gamelog_sl.cpp --- a/src/saveload/gamelog_sl.cpp +++ b/src/saveload/gamelog_sl.cpp @@ -11,12 +11,15 @@ #include "saveload.h" #include "compat/gamelog_sl_compat.h" +#include "extended_ver_sl.h" #include "../gamelog_internal.h" #include "../fios.h" #include "../safeguards.h" +extern bool _sl_is_ext_version; +extern std::string _sl_xv_version_label; class SlGamelogMode : public DefaultSaveLoadHandler { public: @@ -61,6 +64,11 @@ public: { if (lc->ct != GLCT_REVISION) return; SlObject(lc, this->GetLoadDescription()); + // update revision + if (!_sl_is_ext_version) + { + _sl_xv_version_label = lc->revision.text; + } } void LoadCheck(LoggedChange *lc) const override { this->Load(lc); } @@ -398,11 +406,13 @@ struct GLOGChunkHandler : ChunkHandler { void Load() const override { this->LoadCommon(_gamelog_action, _gamelog_actions); + SlXvCheckSpecialSavegameVersionsB(); } void LoadCheck(size_t) const override { this->LoadCommon(_load_check_data.gamelog_action, _load_check_data.gamelog_actions); + SlXvCheckSpecialSavegameVersionsB(); } }; diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -52,6 +52,7 @@ #include "table/strings.h" +#include "saveload_buffer.h" #include "saveload_internal.h" #include "saveload_filter.h" @@ -59,6 +60,8 @@ extern const SaveLoadVersion SAVEGAME_VERSION = (SaveLoadVersion)(SL_MAX_VERSION - 1); ///< Current savegame version of OpenTTD. +const SaveLoadVersion SAVEGAME_VERSION_EXT = (SaveLoadVersion)(0x8000); ///< Savegame extension indicator mask + SavegameType _savegame_type; ///< type of savegame we are loading FileToSaveLoad _file_to_saveload; ///< File to save or load in the openttd loop. @@ -68,6 +71,11 @@ byte _sl_minor_version; ///< the m std::string _savegame_format; ///< how to compress savegames bool _do_autosave; ///< are we doing an autosave at the moment? +extern bool _sl_is_ext_version; +extern bool _sl_might_be_legacy_patchpack_save; +extern SaveLoadVersion _sl_is_faked_ext; +extern std::string _sl_xv_version_label; + /** What are we currently doing? */ enum SaveLoadAction { SLA_LOAD, ///< loading @@ -83,113 +91,6 @@ enum NeedLength { NL_CALCLENGTH = 2, ///< need to calculate the length }; -/** Save in chunks of 128 KiB. */ -static const size_t MEMORY_CHUNK_SIZE = 128 * 1024; - -/** A buffer for reading (and buffering) savegame data. */ -struct ReadBuffer { - byte buf[MEMORY_CHUNK_SIZE]; ///< Buffer we're going to read from. - byte *bufp; ///< Location we're at reading the buffer. - byte *bufe; ///< End of the buffer we can read from. - LoadFilter *reader; ///< The filter used to actually read. - size_t read; ///< The amount of read bytes so far from the filter. - - /** - * Initialise our variables. - * @param reader The filter to actually read data. - */ - ReadBuffer(LoadFilter *reader) : bufp(nullptr), bufe(nullptr), reader(reader), read(0) - { - } - - inline byte ReadByte() - { - if (this->bufp == this->bufe) { - size_t len = this->reader->Read(this->buf, lengthof(this->buf)); - if (len == 0) SlErrorCorrupt("Unexpected end of chunk"); - - this->read += len; - this->bufp = this->buf; - this->bufe = this->buf + len; - } - - return *this->bufp++; - } - - /** - * Get the size of the memory dump made so far. - * @return The size. - */ - size_t GetSize() const - { - return this->read - (this->bufe - this->bufp); - } -}; - - -/** Container for dumping the savegame (quickly) to memory. */ -struct MemoryDumper { - std::vector blocks; ///< Buffer with blocks of allocated memory. - byte *buf; ///< Buffer we're going to write to. - byte *bufe; ///< End of the buffer we write to. - - /** Initialise our variables. */ - MemoryDumper() : buf(nullptr), bufe(nullptr) - { - } - - ~MemoryDumper() - { - for (auto p : this->blocks) { - free(p); - } - } - - /** - * Write a single byte into the dumper. - * @param b The byte to write. - */ - inline void WriteByte(byte b) - { - /* Are we at the end of this chunk? */ - if (this->buf == this->bufe) { - this->buf = CallocT(MEMORY_CHUNK_SIZE); - this->blocks.push_back(this->buf); - this->bufe = this->buf + MEMORY_CHUNK_SIZE; - } - - *this->buf++ = b; - } - - /** - * Flush this dumper into a writer. - * @param writer The filter we want to use. - */ - void Flush(SaveFilter *writer) - { - uint i = 0; - size_t t = this->GetSize(); - - while (t > 0) { - size_t to_write = std::min(MEMORY_CHUNK_SIZE, t); - - writer->Write(this->blocks[i++], to_write); - t -= to_write; - } - - writer->Finish(); - } - - /** - * Get the size of the memory dump made so far. - * @return The size. - */ - size_t GetSize() const - { - return this->blocks.size() * MEMORY_CHUNK_SIZE - (this->bufe - this->buf); - } -}; - /** The saveload struct, containing reader-writer functions, buffer, version, etc. */ struct SaveLoadParams { SaveLoadAction action; ///< are we doing a save or a load atm. @@ -216,9 +117,127 @@ struct SaveLoadParams { static SaveLoadParams _sl; ///< Parameters used for/at saveload. +void ReadBuffer::SkipBytesSlowPath(size_t bytes) +{ + bytes -= (this->bufe - this->bufp); + while (true) { + size_t len = this->reader->Read(this->buf, lengthof(this->buf)); + if (len == 0) SlErrorCorrupt("Unexpected end of chunk"); + this->read += len; + if (len >= bytes) { + this->bufp = this->buf + bytes; + this->bufe = this->buf + len; + return; + } + else { + bytes -= len; + } + } +} + +void ReadBuffer::AcquireBytes() +{ + size_t remainder = this->bufe - this->bufp; + if (remainder) { + memmove(this->buf, this->bufp, remainder); + } + size_t len = this->reader->Read(this->buf + remainder, lengthof(this->buf) - remainder); + if (len == 0) SlErrorCorrupt("Unexpected end of chunk"); + + this->read += len; + this->bufp = this->buf; + this->bufe = this->buf + remainder + len; +} + +void MemoryDumper::FinaliseBlock() +{ + assert(this->saved_buf == nullptr); + if (!this->blocks.empty()) { + size_t s = MEMORY_CHUNK_SIZE - (this->bufe - this->buf); + this->blocks.back().size = s; + this->completed_block_bytes += s; + } + this->buf = this->bufe = nullptr; +} + +void MemoryDumper::AllocateBuffer() +{ + if (this->saved_buf) { + const size_t offset = this->buf - this->autolen_buf; + const size_t size = (this->autolen_buf_end - this->autolen_buf) * 2; + this->autolen_buf = ReallocT(this->autolen_buf, size); + this->autolen_buf_end = this->autolen_buf + size; + this->buf = this->autolen_buf + offset; + this->bufe = this->autolen_buf_end; + return; + } + this->FinaliseBlock(); + this->buf = MallocT(MEMORY_CHUNK_SIZE); + this->blocks.emplace_back(this->buf); + this->bufe = this->buf + MEMORY_CHUNK_SIZE; +} + +/** + * Flush this dumper into a writer. + * @param writer The filter we want to use. + */ +void MemoryDumper::Flush(SaveFilter* writer) +{ + this->FinaliseBlock(); + + size_t block_count = this->blocks.size(); + for (size_t i = 0; i < block_count; i++) { + writer->Write(this->blocks[i].data, this->blocks[i].size); + } + + writer->Finish(); +} + +void MemoryDumper::StartAutoLength() +{ + assert(this->saved_buf == nullptr); + + this->saved_buf = this->buf; + this->saved_bufe = this->bufe; + this->buf = this->autolen_buf; + this->bufe = this->autolen_buf_end; +} + +std::pair MemoryDumper::StopAutoLength() +{ + assert(this->saved_buf != nullptr); + auto res = std::make_pair(this->autolen_buf, this->buf - this->autolen_buf); + + this->buf = this->saved_buf; + this->bufe = this->saved_bufe; + this->saved_buf = this->saved_bufe = nullptr; + return res; +} + +/** + * Get the size of the memory dump made so far. + * @return The size. + */ +size_t MemoryDumper::GetSize() const +{ + assert(this->saved_buf == nullptr); + return this->completed_block_bytes + (this->bufe ? (MEMORY_CHUNK_SIZE - (this->bufe - this->buf)) : 0); +} + +ReadBuffer* ReadBuffer::GetCurrent() +{ + return _sl.reader; +} + +MemoryDumper* MemoryDumper::GetCurrent() +{ + return _sl.dumper; +} + static const std::vector &ChunkHandlers() { /* These define the chunks */ + extern const ChunkHandlerTable _version_ext_chunk_handlers; extern const ChunkHandlerTable _gamelog_chunk_handlers; extern const ChunkHandlerTable _map_chunk_handlers; extern const ChunkHandlerTable _misc_chunk_handlers; @@ -255,6 +274,7 @@ static const std::vector> 24, ch.id >> 16, ch.id >> 8, ch.id); @@ -435,41 +456,60 @@ void SlWriteByte(byte b) _sl.dumper->WriteByte(b); } -static inline int SlReadUint16() +int SlReadUint16() { - int x = SlReadByte() << 8; - return x | SlReadByte(); + _sl.reader->CheckBytes(2); + return _sl.reader->RawReadUint16(); } -static inline uint32 SlReadUint32() +uint32 SlReadUint32() { - uint32 x = SlReadUint16() << 16; - return x | SlReadUint16(); + _sl.reader->CheckBytes(4); + return _sl.reader->RawReadUint32(); } -static inline uint64 SlReadUint64() +uint64 SlReadUint64() { - uint32 x = SlReadUint32(); - uint32 y = SlReadUint32(); - return (uint64)x << 32 | y; + _sl.reader->CheckBytes(8); + return _sl.reader->RawReadUint64(); +} + +void SlWriteUint16(uint16 v) +{ + _sl.dumper->CheckBytes(2); + _sl.dumper->RawWriteUint16(v); } -static inline void SlWriteUint16(uint16 v) +void SlWriteUint32(uint32 v) { - SlWriteByte(GB(v, 8, 8)); - SlWriteByte(GB(v, 0, 8)); + _sl.dumper->CheckBytes(4); + _sl.dumper->RawWriteUint32(v); } -static inline void SlWriteUint32(uint32 v) +void SlWriteUint64(uint64 x) { - SlWriteUint16(GB(v, 16, 16)); - SlWriteUint16(GB(v, 0, 16)); + _sl.dumper->CheckBytes(8); + _sl.dumper->RawWriteUint64(x); } -static inline void SlWriteUint64(uint64 x) +/** + * Returns number of bytes read so far + * May only be called during a load/load check action + */ +size_t SlGetBytesRead() { - SlWriteUint32((uint32)(x >> 32)); - SlWriteUint32((uint32)x); + assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK); + return _sl.reader->GetSize(); +} + +/** + * Returns number of bytes written so far + * May only be called during a save action + */ +size_t SlGetBytesWritten() +{ + assert(_sl.action == SLA_SAVE); + return _sl.dumper->GetSize(); } /** @@ -1060,6 +1100,9 @@ static void SlStdString(void *ptr, VarTy case SLA_LOAD_CHECK: case SLA_LOAD: { size_t len = SlReadArrayLength(); + if (len > 65535) { + SlErrorCorrupt("String too long"); + } if (GetVarMemType(conv) == SLE_VAR_NULL) { SlSkipBytes(len); return; @@ -1554,7 +1597,7 @@ static void SlVector(void *vector, VarTy /** Are we going to save this object or not? */ static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld) { - return (_sl_version >= sld.version_from && _sl_version < sld.version_to); + return sld.ext_feature_test.IsFeaturePresent(_sl_version, sld.version_from, sld.version_to); } /** @@ -2048,7 +2091,7 @@ std::vector SlCompatTableHeade /* In old savegames there can be data we no longer care for. We * skip this by simply reading the amount of bytes indicated and * send those to /dev/null. */ - saveloads.push_back({"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, slc.length, slc.version_from, slc.version_to, 0, nullptr, 0, nullptr}); + saveloads.push_back({"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, slc.length, slc.version_from, slc.version_to, 0, nullptr, 0, nullptr, slc.ext_feature_test}); } else { auto sld_it = key_lookup.find(slc.name); /* If this branch triggers, it means that an entry in the @@ -2061,6 +2104,7 @@ std::vector SlCompatTableHeade SlErrorCorrupt("Internal error with savegame compatibility"); } for (auto &sld : sld_it->second) { + if (!slc.ext_feature_test.IsFeaturePresent(_sl_version, SL_MIN_VERSION, SL_MAX_VERSION)) continue; saveloads.push_back(*sld); } } @@ -2186,7 +2230,7 @@ static void SlLoadChunk(const ChunkHandl * If the chunkhandler is nullptr, the chunk is skipped. * @param ch The chunkhandler that will be used for the operation */ -static void SlLoadCheckChunk(const ChunkHandler &ch) +static void SlLoadCheckChunk(const ChunkHandler* ch) { byte m = SlReadByte(); size_t len; @@ -2206,11 +2250,25 @@ static void SlLoadCheckChunk(const Chunk case CH_TABLE: case CH_ARRAY: _sl.array_index = 0; - ch.LoadCheck(); + if (ch) + { + ch->LoadCheck(); + } + else + { + SlSkipArray(); + } break; case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: - ch.LoadCheck(); + if (ch) + { + ch->LoadCheck(); + } + else + { + SlSkipArray(); + } break; case CH_RIFF: /* Read length */ @@ -2218,7 +2276,13 @@ static void SlLoadCheckChunk(const Chunk len += SlReadUint16(); _sl.obj_len = len; endoffs = _sl.reader->GetSize() + len; - ch.LoadCheck(len); + if (ch) { + ch->LoadCheck(len); + } + else + { + SlSkipBytes(len); + } if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size"); break; default: @@ -2230,6 +2294,25 @@ static void SlLoadCheckChunk(const Chunk } /** + * Load a chunk of data for checking savegames. + * @param ch The chunkhandler that will be used for the operation + */ +static void SlLoadCheckChunk(const ChunkHandler &ch) +{ + SlLoadCheckChunk(&ch); +} + +/** + * Skip unwanted chunk + * @param id Chunk ID + */ +static void SlLoadSkipChunk(uint32 id) +{ + Debug(sl, 1, "Discarding chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id); + SlLoadCheckChunk(nullptr); +} + +/** * Save a chunk of data (eg. vehicles, stations, etc.). Each chunk is * prefixed by an ID identifying it, followed by data, and terminator where appropriate * @param ch The chunkhandler that will be used for the operation @@ -2302,8 +2385,17 @@ static void SlLoadChunks() Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id); ch = SlFindChunkHandler(id); - if (ch == nullptr) SlErrorCorrupt("Unknown chunk type"); - SlLoadChunk(*ch); + if (ch == nullptr) { + if (SlXvIsChunkDiscardable(id)) { + SlLoadSkipChunk(id); + } + else { + SlErrorCorrupt("Unknown chunk type"); + } + } + else { + SlLoadChunk(*ch); + } } } @@ -2317,8 +2409,17 @@ static void SlLoadCheckChunks() Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id); ch = SlFindChunkHandler(id); - if (ch == nullptr) SlErrorCorrupt("Unknown chunk type"); - SlLoadCheckChunk(*ch); + if (ch == nullptr) { + if (SlXvIsChunkDiscardable(id)) { + SlLoadSkipChunk(id); + } + else { + SlErrorCorruptFmt("Unknown chunk type %c%c%c%c", id >> 24, id >> 16, id >> 8, id); + } + } + else { + SlLoadCheckChunk(*ch); + } } } @@ -2976,7 +3077,7 @@ static SaveOrLoadResult SaveFileToDisk(b const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression); /* We have written our stuff to memory, now write it to file! */ - uint32 hdr[2] = { fmt->tag, TO_BE32(SAVEGAME_VERSION << 16) }; + uint32 hdr[2] = { fmt->tag, TO_BE32((uint32)(SAVEGAME_VERSION | SAVEGAME_VERSION_EXT) << 16) }; _sl.sf->Write((byte*)hdr, sizeof(hdr)); _sl.sf = fmt->init_write(_sl.sf, compression); @@ -3035,6 +3136,7 @@ static SaveOrLoadResult DoSave(SaveFilte _sl.sf = writer; _sl_version = SAVEGAME_VERSION; + SlXvSetCurrentState(); SaveViewportBeforeSaveGame(); SlSaveChunks(); @@ -3087,6 +3189,8 @@ static SaveOrLoadResult DoLoad(LoadFilte _load_check_data.checkable = true; } + SlXvResetState(); + uint32 hdr[2]; if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); @@ -3099,6 +3203,7 @@ static SaveOrLoadResult DoLoad(LoadFilte _sl.lf->Reset(); _sl_version = SL_MIN_VERSION; _sl_minor_version = 0; + SlXvResetState(); /* Try to find the LZO savegame format; it uses 'OTTD' as tag. */ fmt = _saveload_formats; @@ -3121,11 +3226,18 @@ static SaveOrLoadResult DoLoad(LoadFilte * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */ _sl_minor_version = (TO_BE32(hdr[1]) >> 8) & 0xFF; - Debug(sl, 1, "Loading savegame version {}", _sl_version); + if (_sl_version & SAVEGAME_VERSION_EXT) { + _sl_version = (SaveLoadVersion)(_sl_version & ~SAVEGAME_VERSION_EXT); + _sl_is_ext_version = true; + } else if (_settings_client.gui.load_legacy_patchpack_savedata ) { + SlXvCheckSpecialSavegameVersionsA(); + } + + Debug(sl, 1, "Loading savegame version {}{}", _sl_version, _sl_is_ext_version ? " (extended)" : ""); /* Is the version higher than the current? */ if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME); - if (_sl_version >= SLV_START_PATCHPACKS && _sl_version <= SLV_END_PATCHPACKS) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK); + if (_sl_version >= SLV_START_PATCHPACKS && _sl_version < SLV_END_PATCHPACKS && !_sl_might_be_legacy_patchpack_save) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK); break; } @@ -3182,6 +3294,8 @@ static SaveOrLoadResult DoLoad(LoadFilte if (load_check) { /* Load chunks into _load_check_data. * No pools are loaded. References are not possible, and thus do not need resolving. */ + _load_check_data.save_version = _sl_is_faked_ext != SL_MIN_VERSION ? _sl_is_faked_ext : _sl_version; + _load_check_data.save_ext_type = _sl_is_ext_version ? PSXT_EXTENDED : PSXT_NONE; SlLoadCheckChunks(); } else { /* Load chunks and resolve references */ @@ -3207,6 +3321,7 @@ static SaveOrLoadResult DoLoad(LoadFilte } GamelogStopAction(); + SlXvSetCurrentState(); } return SL_OK; @@ -3263,12 +3378,14 @@ SaveOrLoadResult SaveOrLoad(const std::s if (!LoadOldSaveGame(filename)) return SL_REINIT; _sl_version = SL_MIN_VERSION; _sl_minor_version = 0; + SlXvResetState(); GamelogStartAction(GLAT_LOAD); if (!AfterLoadGame()) { GamelogStopAction(); return SL_REINIT; } GamelogStopAction(); + SlXvSetCurrentState(); return SL_OK; } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -18,6 +18,8 @@ #include #include +#include "extended_ver_sl.h" + /** SaveLoad versions * Previous savegame versions, the trunk revision where they were * introduced and the released version that had that particular @@ -323,6 +325,30 @@ enum SaveLoadVersion : uint16 { * its own chunk with feature toggles. */ SLV_START_PATCHPACKS, ///< 220 First known patchpack to use a version just above ours. + + // legacy patchpack version codes + SLV_PP1X = 100, ///< 1.x + SLV_PP2X = 126, ///< 2.x + SLV_PP3X = 186, ///< 3.x + SLV_PP4X = 189, ///< 4.20033.004 (conflicting with 5.x, because FGS) + SLV_PP5X_DAYLENGTH = 160, ///< 5.22434.001 + SLV_PP5X_HOTFIX_174 = 174, ///< 5.23975.005b + SLV_PP5X_HOTFIX_175 = 175, ///< 5.24187.007 + SLV_PP5X_HOTFIX_177 = 177, ///< 5.24771.008b + SLV_PP5X_HOTFIX_187 = 187, ///< 5.25942.009 + SLV_PP5X_STATION_NEWGRF = 188, ///< 5.26167.011 + SLV_PP5X_INFRASTRUCTURE = 189, ///< 5.26167.012 + SLV_PP5X_RUNNING_COST_MULT = 194, ///< 5.27099.013 + SLV_PP5X_PLANE_RANGE_MULT = 195, ///< 5.27500.016 + SLV_PP5X_HOTFIX_196 = 196, ///< 5.27930.017 + SLV_PP5X_EXTRA_EXPENSES = 197, ///< 5.27930.018 + SLV_PP5X_HOTFIX_198 = 198, ///< 5.020 + SLV_PP5X_IMPROVED_BREAKDOWNS = 217, ///< 5.022 dev + SLV_PP5X_BREAKDOWN_SCALER = 218, ///< 5.022 dev + SLV_PP5X_TOWN_IMPROVEMENTS = 219, ///< 5.022 dev + SLV_PP5X_DILAPIDATION = 220, ///< 5.022 final + SLV_PP5X_HOTFIX_222 = 222, ///< 5.024 + SLV_END_PATCHPACKS = 286, ///< 286 Last known patchpack to use a version just above ours. SLV_GS_INDUSTRY_CONTROL, ///< 287 PR#7912 and PR#8115 GS industry control. @@ -663,6 +689,7 @@ struct SaveLoad { SaveLoadAddrProc *address_proc; ///< Callback proc the get the actual variable address in memory. size_t extra_data; ///< Extra data for the callback proc. std::shared_ptr handler; ///< Custom handler for Save/Load procs. + SlXvFeatureTest ext_feature_test; ///< extended feature test }; /** @@ -678,6 +705,7 @@ struct SaveLoadCompat { uint16 length; ///< Length of the NULL field. SaveLoadVersion version_from; ///< Save/load the variable starting from this savegame version. SaveLoadVersion version_to; ///< Save/load the variable before this savegame version. + SlXvFeatureTest ext_feature_test; ///< extended feature test }; /** @@ -689,9 +717,11 @@ struct SaveLoadCompat { * @param from First savegame version that has the field. * @param to Last savegame version that has the field. * @param extra Extra data to pass to the address callback function. + * @param extver Extended feature test * @note In general, it is better to use one of the SLE_* macros below. */ -#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) SaveLoad {#variable, cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra, nullptr} +#define SLE_GENERAL_X(cmd, base, variable, type, length, from, to, extra, extver) SaveLoad {#variable, cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra, nullptr, extver} +#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) SLE_GENERAL_X(cmd, base, variable, type, length, from, to, extra, SlXvFeatureTest()) /** * Storage of a variable in some savegame versions. @@ -700,8 +730,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extver Extended feature test */ -#define SLE_CONDVAR(base, variable, type, from, to) SLE_GENERAL(SL_VAR, base, variable, type, 0, from, to, 0) +#define SLE_CONDVAR_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_VAR, base, variable, type, 0, from, to, 0, extver) +#define SLE_CONDVAR(base, variable, type, from, to) SLE_CONDVAR_X(base, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a reference in some savegame versions. @@ -710,8 +742,10 @@ struct SaveLoadCompat { * @param type Type of the reference, a value from #SLRefType. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extver Extended feature test */ -#define SLE_CONDREF(base, variable, type, from, to) SLE_GENERAL(SL_REF, base, variable, type, 0, from, to, 0) +#define SLE_CONDREF_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_REF, base, variable, type, 0, from, to, 0, extver) +#define SLE_CONDREF(base, variable, type, from, to) SLE_CONDREF_X(base, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a fixed-size array of #SL_VAR elements in some savegame versions. @@ -721,8 +755,10 @@ struct SaveLoadCompat { * @param length Number of elements in the array. * @param from First savegame version that has the array. * @param to Last savegame version that has the array. + * @param extver Extended feature test */ -#define SLE_CONDARR(base, variable, type, length, from, to) SLE_GENERAL(SL_ARR, base, variable, type, length, from, to, 0) +#define SLE_CONDARR_X(base, variable, type, length, from, to, extver) SLE_GENERAL_X(SL_ARR, base, variable, type, length, from, to, 0, extver) +#define SLE_CONDARR(base, variable, type, length, from, to) SLE_CONDARR_X(base, variable, type, length, from, to, SlXvFeatureTest()) /** * Storage of a string in some savegame versions. @@ -732,8 +768,10 @@ struct SaveLoadCompat { * @param length Number of elements in the string (only used for fixed size buffers). * @param from First savegame version that has the string. * @param to Last savegame version that has the string. + * @param extver Extended feature test */ -#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_GENERAL(SL_STR, base, variable, type, length, from, to, 0) +#define SLE_CONDSTR_X(base, variable, type, length, from, to, extver) SLE_GENERAL_X(SL_STR, base, variable, type, length, from, to, 0, extver) +#define SLE_CONDSTR(base, variable, type, length, from, to) SLE_CONDSTR_X(base, variable, type, length, from, to, SlXvFeatureTest()) /** * Storage of a \c std::string in some savegame versions. @@ -742,8 +780,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the string. * @param to Last savegame version that has the string. + * @param extver Extended feature test */ -#define SLE_CONDSSTR(base, variable, type, from, to) SLE_GENERAL(SL_STDSTR, base, variable, type, 0, from, to, 0) +#define SLE_CONDSSTR_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_STDSTR, base, variable, type, 0, from, to, 0, extver) +#define SLE_CONDSSTR(base, variable, type, from, to) SLE_CONDSSTR_X(base, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a list of #SL_REF elements in some savegame versions. @@ -752,8 +792,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. + * @param extver Extended feature test */ -#define SLE_CONDREFLIST(base, variable, type, from, to) SLE_GENERAL(SL_REFLIST, base, variable, type, 0, from, to, 0) +#define SLE_CONDREFLIST_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_REFLIST, base, variable, type, 0, from, to, 0, extver) +#define SLE_CONDREFLIST(base, variable, type, from, to) SLE_CONDREFLIST_X(base, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a deque of #SL_VAR elements in some savegame versions. @@ -762,8 +804,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. + * @param extver Extended feature test */ -#define SLE_CONDDEQUE(base, variable, type, from, to) SLE_GENERAL(SL_DEQUE, base, variable, type, 0, from, to, 0) +#define SLE_CONDDEQUE_X(base, variable, type, from, to, extver) SLE_GENERAL_X(SL_DEQUE, base, variable, type, 0, from, to, 0, extver) +#define SLE_CONDDEQUE(base, variable, type, from, to) SLE_CONDDEQUE_X(base, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a variable in every version of a savegame. @@ -837,8 +881,10 @@ struct SaveLoadCompat { * @param to Last savegame version that has the field. * @param extra Extra data to pass to the address callback function. * @note In general, it is better to use one of the SLEG_* macros below. + * @param extver Extended feature test */ -#define SLEG_GENERAL(name, cmd, variable, type, length, from, to, extra) SaveLoad {name, cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra, nullptr} +#define SLEG_GENERAL_X(name, cmd, variable, type, length, from, to, extra, extver) SaveLoad {name, cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra, nullptr, extver} +#define SLEG_GENERAL(name, cmd, variable, type, length, from, to, extra) SLEG_GENERAL_X(name, cmd, variable, type, length, from, to, extra, SlXvFeatureTest()) /** * Storage of a global variable in some savegame versions. @@ -847,8 +893,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extver Extended feature test */ -#define SLEG_CONDVAR(name, variable, type, from, to) SLEG_GENERAL(name, SL_VAR, variable, type, 0, from, to, 0) +#define SLEG_CONDVAR_X(name, variable, type, from, to, extver) SLEG_GENERAL_X(name, SL_VAR, variable, type, 0, from, to, 0, extver) +#define SLEG_CONDVAR(name, variable, type, from, to) SLEG_CONDVAR_X(name, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a global reference in some savegame versions. @@ -857,8 +905,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. + * @param extver Extended feature test */ -#define SLEG_CONDREF(name, variable, type, from, to) SLEG_GENERAL(name, SL_REF, variable, type, 0, from, to, 0) +#define SLEG_CONDREF_X(name, variable, type, from, to, extver) SLEG_GENERAL_X(name, SL_REF, variable, type, 0, from, to, 0, extver) +#define SLEG_CONDREF(name, variable, type, from, to) SLEG_CONDREF_X(name, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a global fixed-size array of #SL_VAR elements in some savegame versions. @@ -868,8 +918,10 @@ struct SaveLoadCompat { * @param length Number of elements in the array. * @param from First savegame version that has the array. * @param to Last savegame version that has the array. + * @param extver Extended feature test */ -#define SLEG_CONDARR(name, variable, type, length, from, to) SLEG_GENERAL(name, SL_ARR, variable, type, length, from, to, 0) +#define SLEG_CONDARR_X(name, variable, type, length, from, to, extver) SLEG_GENERAL_X(name, SL_ARR, variable, type, length, from, to, 0, extver) +#define SLEG_CONDARR(name, variable, type, length, from, to) SLEG_CONDARR_X(name, variable, type, length, from, to, SlXvFeatureTest()) /** * Storage of a global string in some savegame versions. @@ -879,8 +931,10 @@ struct SaveLoadCompat { * @param length Number of elements in the string (only used for fixed size buffers). * @param from First savegame version that has the string. * @param to Last savegame version that has the string. + * @param extver Extended feature test */ -#define SLEG_CONDSTR(name, variable, type, length, from, to) SLEG_GENERAL(name, SL_STR, variable, type, length, from, to, 0) +#define SLEG_CONDSTR_X(name, variable, type, length, from, to, extver) SLEG_GENERAL_X(name, SL_STR, variable, type, length, from, to, 0, extver) +#define SLEG_CONDSTR(name, variable, type, length, from, to) SLEG_CONDSTR_X(name, variable, type, length, from, to, SlXvFeatureTest()) /** * Storage of a global \c std::string in some savegame versions. @@ -889,8 +943,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the string. * @param to Last savegame version that has the string. + * @param extver Extended feature test */ -#define SLEG_CONDSSTR(name, variable, type, from, to) SLEG_GENERAL(name, SL_STDSTR, variable, type, 0, from, to, 0) +#define SLEG_CONDSSTR_X(name, variable, type, from, to, extver) SLEG_GENERAL_X(name, SL_STDSTR, variable, type, 0, from, to, 0, extver) +#define SLEG_CONDSSTR(name, variable, type, from, to) SLEG_CONDSSTR_X(name, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a structs in some savegame versions. @@ -898,8 +954,10 @@ struct SaveLoadCompat { * @param handler SaveLoadHandler for the structs. * @param from First savegame version that has the struct. * @param to Last savegame version that has the struct. + * @param extver Extended feature test */ -#define SLEG_CONDSTRUCT(name, handler, from, to) SaveLoad {name, SL_STRUCT, 0, 0, from, to, 0, nullptr, 0, std::make_shared()} +#define SLEG_CONDSTRUCT_X(name, handler, from, to, extver) SaveLoad {name, SL_STRUCT, 0, 0, from, to, 0, nullptr, 0, std::make_shared(), extver} +#define SLEG_CONDSTRUCT(name, handler, from, to) SLEG_CONDSTRUCT_X(name, handler, from, to, SlXvFeatureTest()) /** * Storage of a global reference list in some savegame versions. @@ -908,8 +966,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. + * @param extver Extended feature test */ -#define SLEG_CONDREFLIST(name, variable, type, from, to) SLEG_GENERAL(name, SL_REFLIST, variable, type, 0, from, to, 0) +#define SLEG_CONDREFLIST_X(name, variable, type, from, to, extver) SLEG_GENERAL_X(name, SL_REFLIST, variable, type, 0, from, to, 0, extver) +#define SLEG_CONDREFLIST(name, variable, type, from, to) SLEG_CONDREFLIST_X(name, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a global vector of #SL_VAR elements in some savegame versions. @@ -918,8 +978,10 @@ struct SaveLoadCompat { * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. + * @param extver Extended feature test */ -#define SLEG_CONDVECTOR(name, variable, type, from, to) SLEG_GENERAL(name, SL_VECTOR, variable, type, 0, from, to, 0) +#define SLEG_CONDVECTOR_X(name, variable, type, from, to, extver) SLEG_GENERAL_X(name, SL_VECTOR, variable, type, 0, from, to, 0, extver) +#define SLEG_CONDVECTOR(name, variable, type, from, to) SLEG_CONDVECTOR_X(name, variable, type, from, to, SlXvFeatureTest()) /** * Storage of a list of structs in some savegame versions. @@ -927,8 +989,10 @@ struct SaveLoadCompat { * @param handler SaveLoadHandler for the list of structs. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. + * @param extver Extended feature test */ -#define SLEG_CONDSTRUCTLIST(name, handler, from, to) SaveLoad {name, SL_STRUCTLIST, 0, 0, from, to, 0, nullptr, 0, std::make_shared()} +#define SLEG_CONDSTRUCTLIST_X(name, handler, from, to, extver) SaveLoad {name, SL_STRUCTLIST, 0, 0, from, to, 0, nullptr, 0, std::make_shared(), extver} +#define SLEG_CONDSTRUCTLIST(name, handler, from, to) SLEG_CONDSTRUCTLIST_X(name, handler, from, to, SlXvFeatureTest()) /** * Storage of a global variable in every savegame version. @@ -1004,7 +1068,8 @@ struct SaveLoadCompat { * Field name where the real SaveLoad can be located. * @param name The name of the field. */ -#define SLC_VAR(name) {name, 0, SL_MIN_VERSION, SL_MAX_VERSION} +#define SLC_VAR_X(name, extver) {name, 0, SL_MIN_VERSION, SL_MAX_VERSION, extver} +#define SLC_VAR(name) {name, 0, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest()} /** * Empty space in every savegame version. @@ -1012,10 +1077,11 @@ struct SaveLoadCompat { * @param from First savegame version that has the empty space. * @param to Last savegame version that has the empty space. */ -#define SLC_NULL(length, from, to) {{}, length, from, to} +#define SLC_NULL_X(length, from, to, extver) {{}, length, from, to, extver} +#define SLC_NULL(length, from, to) {{}, length, from, to, SlXvFeatureTest()} /** End marker of compat variables save or load. */ -#define SLC_END() {{}, 0, SL_MIN_VERSION, SL_MIN_VERSION} +#define SLC_END() {{}, 0, SL_MIN_VERSION, SL_MIN_VERSION, SlXvFeatureTest()} /** * Checks whether the savegame is below \a major.\a minor. @@ -1124,6 +1190,17 @@ size_t SlCalcObjLength(const void *objec byte SlReadByte(); void SlWriteByte(byte b); +int SlReadUint16(); +uint32 SlReadUint32(); +uint64 SlReadUint64(); + +void SlWriteUint16(uint16 v); +void SlWriteUint32(uint32 v); +void SlWriteUint64(uint64 v); + +size_t SlGetBytesRead(); +size_t SlGetBytesWritten(); + void SlGlobList(const SaveLoadTable &slt); void SlCopy(void *object, size_t length, VarType conv); std::vector SlTableHeader(const SaveLoadTable &slt); diff --git a/src/saveload/saveload_buffer.h b/src/saveload/saveload_buffer.h new file mode 100644 --- /dev/null +++ b/src/saveload/saveload_buffer.h @@ -0,0 +1,266 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file saveload_buffer.h Functions/types related to buffers used for saving and loading games. */ + +#ifndef SAVELOAD_BUFFER_H +#define SAVELOAD_BUFFER_H + +#include "../core/alloc_func.hpp" +#include "../core/endian_type.hpp" +#include "../core/endian_func.hpp" +#include "../core/math_func.hpp" + +#include +#include + +struct LoadFilter; +struct SaveFilter; + +/** Save in chunks of 128 KiB. */ +static const size_t MEMORY_CHUNK_SIZE = 128 * 1024; + +/** A buffer for reading (and buffering) savegame data. */ +struct ReadBuffer { + byte buf[MEMORY_CHUNK_SIZE]; ///< Buffer we're going to read from. + byte *bufp; ///< Location we're at reading the buffer. + byte *bufe; ///< End of the buffer we can read from. + LoadFilter *reader; ///< The filter used to actually read. + size_t read; ///< The amount of read bytes so far from the filter. + + /** + * Initialise our variables. + * @param reader The filter to actually read data. + */ + ReadBuffer(LoadFilter *reader) : bufp(nullptr), bufe(nullptr), reader(reader), read(0) + { + } + + static ReadBuffer *GetCurrent(); + + void SkipBytesSlowPath(size_t bytes); + void AcquireBytes(); + + inline void SkipBytes(size_t bytes) + { + byte *b = this->bufp + bytes; + if (likely(b <= this->bufe)) { + this->bufp = b; + } else { + SkipBytesSlowPath(bytes); + } + } + + inline byte RawReadByte() + { + return *this->bufp++; + } + + inline byte ReadByte() + { + if (unlikely(this->bufp == this->bufe)) { + this->AcquireBytes(); + } + + return RawReadByte(); + } + + inline void CheckBytes(size_t bytes) + { + while (unlikely(this->bufp + bytes > this->bufe)) this->AcquireBytes(); + } + + inline int RawReadUint16() + { +#if OTTD_ALIGNMENT == 0 + int x = FROM_BE16(*((const unaligned_uint16*) this->bufp)); + this->bufp += 2; + return x; +#else + int x = this->RawReadByte() << 8; + return x | this->RawReadByte(); +#endif + } + + inline uint32 RawReadUint32() + { +#if OTTD_ALIGNMENT == 0 + uint32 x = FROM_BE32(*((const unaligned_uint32*) this->bufp)); + this->bufp += 4; + return x; +#else + uint32 x = this->RawReadUint16() << 16; + return x | this->RawReadUint16(); +#endif + } + + inline uint64 RawReadUint64() + { +#if OTTD_ALIGNMENT == 0 + uint64 x = FROM_BE64(*((const unaligned_uint64*) this->bufp)); + this->bufp += 8; + return x; +#else + uint32 x = this->RawReadUint32(); + uint32 y = this->RawReadUint32(); + return (uint64)x << 32 | y; +#endif + } + + inline void CopyBytes(byte *ptr, size_t length) + { + while (length) { + if (unlikely(this->bufp == this->bufe)) { + this->AcquireBytes(); + } + size_t to_copy = std::min(this->bufe - this->bufp, length); + memcpy(ptr, this->bufp, to_copy); + this->bufp += to_copy; + ptr += to_copy; + length -= to_copy; + } + } + + /** + * Get the size of the memory dump made so far. + * @return The size. + */ + inline size_t GetSize() const + { + return this->read - (this->bufe - this->bufp); + } +}; + + +/** Container for dumping the savegame (quickly) to memory. */ +struct MemoryDumper { + struct BufferInfo { + byte *data; + size_t size = 0; + + BufferInfo(byte *d) : data(d) {} + ~BufferInfo() { free(this->data); } + + BufferInfo(const BufferInfo &) = delete; + BufferInfo(BufferInfo &&other) : data(other.data), size(other.size) { other.data = nullptr; }; + }; + + std::vector blocks; ///< Buffer with blocks of allocated memory. + byte *buf = nullptr; ///< Buffer we're going to write to. + byte *bufe = nullptr; ///< End of the buffer we write to. + size_t completed_block_bytes = 0; ///< Total byte count of completed blocks. + + byte *autolen_buf = nullptr; + byte *autolen_buf_end = nullptr; + byte *saved_buf = nullptr; + byte *saved_bufe = nullptr; + + MemoryDumper() + { + const size_t size = 8192; + this->autolen_buf = CallocT(size); + this->autolen_buf_end = this->autolen_buf + size; + } + + ~MemoryDumper() + { + free(this->autolen_buf); + } + + static MemoryDumper *GetCurrent(); + + void FinaliseBlock(); + void AllocateBuffer(); + + inline void CheckBytes(size_t bytes) + { + if (unlikely(this->buf + bytes > this->bufe)) this->AllocateBuffer(); + } + + /** + * Write a single byte into the dumper. + * @param b The byte to write. + */ + inline void WriteByte(byte b) + { + /* Are we at the end of this chunk? */ + if (unlikely(this->buf == this->bufe)) { + this->AllocateBuffer(); + } + + *this->buf++ = b; + } + + inline void CopyBytes(byte *ptr, size_t length) + { + while (length) { + if (unlikely(this->buf == this->bufe)) { + this->AllocateBuffer(); + } + size_t to_copy = std::min(this->bufe - this->buf, length); + memcpy(this->buf, ptr, to_copy); + this->buf += to_copy; + ptr += to_copy; + length -= to_copy; + } + } + + inline void RawWriteByte(byte b) + { + *this->buf++ = b; + } + + inline void RawWriteUint16(uint16 v) + { +#if OTTD_ALIGNMENT == 0 + *((unaligned_uint16 *) this->buf) = TO_BE16(v); +#else + this->buf[0] = GB(v, 8, 8); + this->buf[1] = GB(v, 0, 8); +#endif + this->buf += 2; + } + + inline void RawWriteUint32(uint32 v) + { +#if OTTD_ALIGNMENT == 0 + *((unaligned_uint32 *) this->buf) = TO_BE32(v); +#else + this->buf[0] = GB(v, 24, 8); + this->buf[1] = GB(v, 16, 8); + this->buf[2] = GB(v, 8, 8); + this->buf[3] = GB(v, 0, 8); +#endif + this->buf += 4; + } + + inline void RawWriteUint64(uint64 v) + { +#if OTTD_ALIGNMENT == 0 + *((unaligned_uint64 *) this->buf) = TO_BE64(v); +#else + this->buf[0] = GB(v, 56, 8); + this->buf[1] = GB(v, 48, 8); + this->buf[2] = GB(v, 40, 8); + this->buf[3] = GB(v, 32, 8); + this->buf[4] = GB(v, 24, 8); + this->buf[5] = GB(v, 16, 8); + this->buf[6] = GB(v, 8, 8); + this->buf[7] = GB(v, 0, 8); +#endif + this->buf += 8; + } + + void Flush(SaveFilter *writer); + size_t GetSize() const; + void StartAutoLength(); + std::pair StopAutoLength(); +}; + +#endif diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -594,6 +594,7 @@ public: SLE_REFLIST(Station, loading_vehicles, REF_VEHICLE), SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), + SLE_CONDVAR_X(Station, dilapidation, SLE_UINT16, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION)), SLEG_STRUCTLIST("goods", SlStationGoods), }; inline const static SaveLoadCompatTable compat_description = _station_normal_sl_compat; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -680,6 +680,8 @@ public: SLE_VAR(Vehicle, breakdown_delay, SLE_UINT8), SLE_VAR(Vehicle, breakdowns_since_last_service, SLE_UINT8), SLE_VAR(Vehicle, breakdown_chance, SLE_UINT8), + SLE_CONDVAR_X(Vehicle, breakdown_type, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS)), + SLE_CONDVAR_X(Vehicle, breakdown_severity, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS)), SLE_CONDVAR(Vehicle, build_year, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_31), SLE_CONDVAR(Vehicle, build_year, SLE_INT32, SLV_31, SL_MAX_VERSION), diff --git a/src/script/api/script_company.hpp b/src/script/api/script_company.hpp --- a/src/script/api/script_company.hpp +++ b/src/script/api/script_company.hpp @@ -113,6 +113,15 @@ public: EXPENSES_SHIP_INC = ::EXPENSES_SHIP_INC, ///< Income from ships. EXPENSES_LOAN_INT = ::EXPENSES_LOAN_INT, ///< Interest payments over the loan. EXPENSES_OTHER = ::EXPENSES_OTHER, ///< Other expenses. + EXPENSES_T_TRAIN_CON = ::EXPENSES_T_TRAIN_CON, + EXPENSES_T_ROAD_CON = ::EXPENSES_T_ROAD_CON, + EXPENSES_T_AIRCRAFT_CON = ::EXPENSES_T_AIRCRAFT_CON, + EXPENSES_T_SHIP_CON = ::EXPENSES_T_SHIP_CON, + EXPENSES_T_TREES_CON = ::EXPENSES_T_TREES_CON, + EXPENSES_T_SCENERY_CON = ::EXPENSES_T_SCENERY_CON, + EXPENSES_T_LANDSCAPING = ::EXPENSES_T_LANDSCAPING, + EXPENSES_T_DEMOLITION = ::EXPENSES_T_DEMOLITION, + EXPENSES_T_REWARD_INC = ::EXPENSES_T_REWARD_INC, EXPENSES_INVALID = ::INVALID_EXPENSES, ///< Invalid expense type. }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1854,6 +1854,47 @@ static SettingsContainer &GetSettingsTre network->Add(new SettingEntry("network.use_relay_service")); } + SettingsPage *patchpack = main->Add(new SettingsPage(STR_CONFIG_SETTING_PATCHPACK)); + { + SettingsPage *patchpack_daylength = patchpack->Add(new SettingsPage(STR_CONFIG_SETTING_PATCHPACK_DAYLENGTH)); + { + patchpack_daylength->Add(new SettingEntry("economy.daylength_multiplier")); + patchpack_daylength->Add(new SettingEntry("economy.town_growth_multiplier")); + patchpack_daylength->Add(new SettingEntry("economy.running_cost_multiplier_rail")); + patchpack_daylength->Add(new SettingEntry("economy.running_cost_multiplier_road")); + patchpack_daylength->Add(new SettingEntry("economy.running_cost_multiplier_water")); + patchpack_daylength->Add(new SettingEntry("economy.running_cost_multiplier_air")); + patchpack_daylength->Add(new SettingEntry("economy.infrastructure_base_cost_rail")); + patchpack_daylength->Add(new SettingEntry("economy.infrastructure_base_cost_road")); + patchpack_daylength->Add(new SettingEntry("economy.infrastructure_base_cost_water")); + patchpack_daylength->Add(new SettingEntry("economy.infrastructure_base_cost_air")); + patchpack_daylength->Add(new SettingEntry("economy.infrastructure_base_cost_station")); + } + + SettingsPage *patchpack_dilapidation = patchpack->Add(new SettingsPage(STR_CONFIG_SETTING_PATCHPACK_DILAPIDATION)); + { + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_max_amount")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_increase")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_decrease")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_pop_rail")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_pop_road")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_pop_water")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_pop_air")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_fine_rail")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_fine_road")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_fine_water")); + patchpack_dilapidation->Add(new SettingEntry("economy.dilapidation_fine_air")); + } + + patchpack->Add(new SettingEntry("gui.load_legacy_patchpack_savedata")); + patchpack->Add(new SettingEntry("gui.colour_based_on_town_rating")); + patchpack->Add(new SettingEntry("economy.bribe_risky")); + patchpack->Add(new SettingEntry("construction.name_stations_based_on_industries")); + patchpack->Add(new SettingEntry("vehicle.plane_range_multiplier")); + patchpack->Add(new SettingEntry("vehicle.improved_breakdowns")); + patchpack->Add(new SettingEntry("difficulty.vehicle_breakdown_scaler")); + } + main->Init(); } return *main; diff --git a/src/settings_table.cpp b/src/settings_table.cpp --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -288,6 +288,22 @@ static void InvalidateCompanyLiveryWindo ResetVehicleColourMap(); } +static void PatchpackInvalidateRunningCostsOrElse(int32 new_value) +{ + InvalidateWindowClassesData(WC_AIRCRAFT_LIST); + InvalidateWindowClassesData(WC_BUILD_VEHICLE); + InvalidateWindowClassesData(WC_ENGINE_PREVIEW); + InvalidateWindowClassesData(WC_FINANCES); + InvalidateWindowClassesData(WC_PERFORMANCE_DETAIL); + InvalidateWindowClassesData(WC_PERFORMANCE_HISTORY); + InvalidateWindowClassesData(WC_REPLACE_VEHICLE); + InvalidateWindowClassesData(WC_ROADVEH_LIST); + InvalidateWindowClassesData(WC_SHIPS_LIST); + InvalidateWindowClassesData(WC_TRAINS_LIST); + InvalidateWindowClassesData(WC_VEHICLE_DETAILS); + InvalidateWindowClassesData(WC_VEHICLE_VIEW); +} + static void DifficultyNoiseChange(int32 new_value) { if (_game_mode == GM_NORMAL) { diff --git a/src/settings_type.h b/src/settings_type.h --- a/src/settings_type.h +++ b/src/settings_type.h @@ -81,6 +81,7 @@ struct DifficultySettings { byte vehicle_costs; ///< amount of money spent on vehicle running cost byte competitor_speed; ///< the speed at which the AI builds byte vehicle_breakdowns; ///< likelihood of vehicles breaking down + uint32 vehicle_breakdown_scaler; ///< likelihood of vehicles breaking down REDUX byte subsidy_multiplier; ///< payment multiplier for subsidized deliveries uint16 subsidy_duration; ///< duration of subsidies byte construction_cost; ///< how expensive is building @@ -138,6 +139,7 @@ struct GUISettings { bool autosave_on_network_disconnect; ///< save an autosave when you get disconnected from a network game with an error? uint8 date_format_in_default_names; ///< should the default savegame/screenshot name use long dates (31th Dec 2008), short dates (31-12-2008) or ISO dates (2008-12-31) byte max_num_autosaves; ///< controls how many autosavegames are made before the game starts to overwrite (names them 0 to max_num_autosaves - 1) + bool colour_based_on_town_rating; ///< colour code the town name based on rating? bool population_in_label; ///< show the population of a town in its label? uint8 right_mouse_btn_emulation; ///< should we emulate right mouse clicking? uint8 scrollwheel_scrolling; ///< scrolling using the scroll wheel? @@ -192,6 +194,8 @@ struct GUISettings { bool newgrf_show_old_versions; ///< whether to show old versions in the NewGRF list uint8 newgrf_default_palette; ///< default palette to use for NewGRFs without action 14 palette information + bool load_legacy_patchpack_savedata; ///< whether or not to handle legacy patchpack savedata from version 5.x + /** * Returns true when the user has sufficient privileges to edit newgrfs on a running game * @return whether the user has sufficient privileges to edit newgrfs in an existing game @@ -298,6 +302,7 @@ struct NetworkSettings { bool reload_cfg; ///< reload the config file before restarting std::string last_joined; ///< Last joined server bool no_http_content_downloads; ///< do not do content downloads over HTTP + bool save_password; ///< If password file is used UseRelayService use_relay_service; ///< Use relay service? }; @@ -354,6 +359,8 @@ struct ConstructionSettings { uint16 clear_frame_burst; ///< how many tiles may, over a short period, be cleared? uint32 tree_per_64k_frames; ///< how many trees may, over a long period, be planted per 65536 frames? uint16 tree_frame_burst; ///< how many trees may, over a short period, be planted? + + uint8 name_stations_based_on_industries; ///< ttr patchpack: name stations based on nearby industries? }; /** Settings related to the AI. */ @@ -494,12 +501,15 @@ struct VehicleSettings { byte extend_vehicle_life; ///< extend vehicle life by this many years byte road_side; ///< the side of the road vehicles drive on uint8 plane_crashes; ///< number of plane crashes, 0 = none, 1 = reduced, 2 = normal + uint8 plane_range_multiplier; ///< ttr patchpack: range multiplier + bool improved_breakdowns; ///< different types, chances and serverities of breakdowns }; /** Settings related to the economy. */ struct EconomySettings { bool inflation; ///< disable inflation bool bribe; ///< enable bribing the local authority + bool bribe_risky; ///< ttr patchpack: whether or not there is a risk of being caught while bribing EconomyType type; ///< economy type (original/smooth/frozen) bool allow_shares; ///< allow the buying/selling of shares uint8 min_years_for_shares; ///< minimum age of a company for it to trade shares @@ -522,6 +532,28 @@ struct EconomySettings { uint16 town_noise_population[3]; ///< population to base decision on noise evaluation (@see town_council_tolerance) bool allow_town_level_crossings; ///< towns are allowed to build level crossings bool infrastructure_maintenance; ///< enable monthly maintenance fee for owner infrastructure + uint16 infrastructure_base_cost_rail; ///< ttr patchpack: rail infrastructure cost base price + uint16 infrastructure_base_cost_road; ///< ttr patchpack: road infrastructure cost base price + uint16 infrastructure_base_cost_water; ///< ttr patchpack: water infrastructure cost base price + uint16 infrastructure_base_cost_air; ///< ttr patchpack: air infrastructure cost base price + uint16 infrastructure_base_cost_station; ///< ttr patchpack: station infrastructure cost base price + uint8 daylength_multiplier; ///< ttr patchpack: day length multiplier + uint8 town_growth_multiplier; ///< ttr patchpack: town growth multiplier + uint16 running_cost_multiplier_rail; ///< ttr patchpack: rail running cost multiplier + uint16 running_cost_multiplier_road; ///< ttr patchpack: road running cost multiplier + uint16 running_cost_multiplier_water; ///< ttr patchpack: water running cost multiplier + uint16 running_cost_multiplier_air; ///< ttr patchpack: air running cost multiplier + uint16 dilapidation_max_amount; ///< ttr patchpack: maximum amount of dilapidation allowed at station + uint16 dilapidation_increase; ///< ttr patchpack: amount of dilapidation to add on ratings update if station is overcrowded + uint16 dilapidation_decrease; ///< ttr patchpack: amount of dilapidation to remove on ratings update if station is not overcrowded + uint16 dilapidation_pop_rail; ///< ttr patchpack: amount of pop allowed at rail station before it is considered dilapidated + uint16 dilapidation_pop_road; ///< ttr patchpack: amount of pop allowed at road station before it is considered dilapidated + uint16 dilapidation_pop_water; ///< ttr patchpack: amount of pop allowed at water station before it is considered dilapidated + uint16 dilapidation_pop_air; ///< ttr patchpack: amount of pop allowed at air station before it is considered dilapidated + uint32 dilapidation_fine_rail; ///< ttr patchpack: monthly fine per dilapidation point at rail stations + uint32 dilapidation_fine_road; ///< ttr patchpack: monthly fine per dilapidation point at road stations + uint32 dilapidation_fine_water; ///< ttr patchpack: monthly fine per dilapidation point at water stations + uint32 dilapidation_fine_air; ///< ttr patchpack: monthly fine per dilapidation point at air stations }; struct LinkGraphSettings { diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -219,6 +219,7 @@ Money Ship::GetRunningCost() const { const Engine *e = this->GetEngine(); uint cost_factor = GetVehicleProperty(this, PROP_SHIP_RUNNING_COST_FACTOR, e->u.ship.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_water; return GetPrice(PR_RUNNING_SHIP, cost_factor, e->GetGRF()); } @@ -414,6 +415,18 @@ static bool ShipAccelerate(Vehicle *v) spd = std::min(v->cur_speed + 1, v->vcache.cached_max_speed); spd = std::min(spd, v->current_order.GetMaxSpeed() * 2); + if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_POWER && v->cur_speed > (v->breakdown_severity * ShipVehInfo(v->engine_type)->max_speed) >> 8) { + if((v->tick_counter & 0x7) == 0 && v->cur_speed > 0) { + spd = v->cur_speed - 1; + } else { + spd = v->cur_speed; + } + } + + if(v->breakdown_ctr == 1 && v->breakdown_type == BREAKDOWN_LOW_SPEED) { + spd = std::min(spd, v->breakdown_severity); + } + /* updates statusbar only if speed have changed to save CPU time */ if (spd != v->cur_speed) { v->cur_speed = spd; @@ -878,6 +891,7 @@ CommandCost CmdBuildShip(DoCommandFlag f v->reliability = e->reliability; v->reliability_spd_dec = e->reliability_spd_dec; + v->breakdown_chance = 64; // ships have a 50% lower breakdown chance than normal v->max_age = e->GetLifeLengthInDays(); v->state = TRACK_BIT_DEPOT; diff --git a/src/signs_type.h b/src/signs_type.h --- a/src/signs_type.h +++ b/src/signs_type.h @@ -16,6 +16,6 @@ struct Sign; static const SignID INVALID_SIGN = 0xFFFF; ///< Sentinel for an invalid sign. -static const uint MAX_LENGTH_SIGN_NAME_CHARS = 32; ///< The maximum length of a sign name in characters including '\0' +static const uint MAX_LENGTH_SIGN_NAME_CHARS = 128; ///< The maximum length of a sign name in characters including '\0' #endif /* SIGNS_TYPE_H */ diff --git a/src/station_base.h b/src/station_base.h --- a/src/station_base.h +++ b/src/station_base.h @@ -19,7 +19,6 @@ #include "newgrf_storage.h" #include "bitmap_type.h" #include -#include static const byte INITIAL_STATION_RATING = 175; @@ -486,6 +485,8 @@ public: IndustryList industries_near; ///< Cached list of industries near the station that can accept cargo, @see DeliverGoodsToIndustry() Industry *industry; ///< NOSAVE: Associated industry for neutral stations. (Rebuilt on load from Industry->st) + uint16 dilapidation; ///< Current amount of dilapidation at the station. Increases if too many passengers at station. Decreases if not too many. Player will be charged a fine based on how dilapidated the station is + Station(TileIndex tile = INVALID_TILE); ~Station(); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -64,6 +64,8 @@ #include "safeguards.h" +#include "company_func.h" + /** * Static instance of FlowStat::SharesMap. * Note: This instance is created on task start. @@ -238,23 +240,9 @@ static bool FindNearIndustryName(TileInd return !sni->indtypes[indtype]; } -static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) +/** Fetch the available station names. Patchpack moves this code from GenerateStationName. Should be easy to see what was moved if you do a diff :) */ +static void FetchAvailableStationNames(uint32& free_names, bool* indtypes, Station* st, const Town* t) { - static const uint32 _gen_station_name_bits[] = { - 0, // STATIONNAMING_RAIL - 0, // STATIONNAMING_ROAD - 1U << M(STR_SV_STNAME_AIRPORT), // STATIONNAMING_AIRPORT - 1U << M(STR_SV_STNAME_OILFIELD), // STATIONNAMING_OILRIG - 1U << M(STR_SV_STNAME_DOCKS), // STATIONNAMING_DOCK - 1U << M(STR_SV_STNAME_HELIPORT), // STATIONNAMING_HELIPORT - }; - - const Town *t = st->town; - uint32 free_names = UINT32_MAX; - - bool indtypes[NUM_INDUSTRYTYPES]; - memset(indtypes, 0, sizeof(indtypes)); - for (const Station *s : Station::Iterate()) { if (s != st && s->town == t) { if (s->indtype != IT_INVALID) { @@ -278,6 +266,56 @@ static StringID GenerateStationName(Stat } } } +} + +/** Callback proc for CircularTileSearch */ +static bool FindNearPatchpackIndustryName(TileIndex tile, void *user_data) +{ + /* Is this an industry tile? If not, skip it */ + if (!IsTileType(tile, MP_INDUSTRY)) return false; + + uint32 free_names = UINT32_MAX; + bool indtypes[NUM_INDUSTRYTYPES]; + memset(indtypes, 0, sizeof(indtypes)); + FetchAvailableStationNames(free_names, indtypes, (Station*)user_data, Industry::GetByTile(tile)->town); + + IndustryType indtype = GetIndustryType(tile); + + return !indtypes[indtype]; +} + +/** Generates station names */ +static StringID GenerateStationName(Station *st, TileIndex tile, StationNaming name_class) +{ + if (_settings_game.construction.name_stations_based_on_industries == 2) + { + /** I love having this as an in-game patch setting. DEM SETTINGS */ + TileIndex indtile = tile; + if (CircularTileSearch(&indtile, 7, FindNearPatchpackIndustryName, (void*)st)) { + IndustryType indtype = GetIndustryType(indtile); + const IndustrySpec *indsp = GetIndustrySpec(indtype); + st->indtype = indtype; + st->town = Industry::GetByTile(indtile)->town; + return STR_SV_STNAME_FALLBACK; + } + } + + static const uint32 _gen_station_name_bits[] = { + 0, // STATIONNAMING_RAIL + 0, // STATIONNAMING_ROAD + 1U << M(STR_SV_STNAME_AIRPORT), // STATIONNAMING_AIRPORT + 1U << M(STR_SV_STNAME_OILFIELD), // STATIONNAMING_OILRIG + 1U << M(STR_SV_STNAME_DOCKS), // STATIONNAMING_DOCK + 1U << M(STR_SV_STNAME_HELIPORT), // STATIONNAMING_HELIPORT + }; + + const Town *t = st->town; + + /* Fetch a list of available station names so we know which ones have already been used and are unavailable (including industry newGRF names) */ + uint32 free_names = UINT32_MAX; + bool indtypes[NUM_INDUSTRYTYPES]; + memset(indtypes, 0, sizeof(indtypes)); + FetchAvailableStationNames(free_names, indtypes, st, t); TileIndex indtile = tile; StationNameInformation sni = { free_names, indtypes }; @@ -286,7 +324,7 @@ static StringID GenerateStationName(Stat IndustryType indtype = GetIndustryType(indtile); const IndustrySpec *indsp = GetIndustrySpec(indtype); /* STR_NULL means it only disables oil rig/mines */ - if (indsp->station_name != STR_NULL) { + if (indsp->station_name != STR_NULL && (_settings_game.construction.name_stations_based_on_industries == 1)) { st->indtype = indtype; return STR_SV_STNAME_FALLBACK; } @@ -843,7 +881,7 @@ CommandCost CheckBuildableTile(TileIndex */ static CommandCost CheckFlatLandAirport(AirportTileTableIterator tile_iter, DoCommandFlag flags) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_AIRCRAFT_CON); int allowed_z = -1; for (; tile_iter != INVALID_TILE; ++tile_iter) { @@ -875,7 +913,7 @@ static CommandCost CheckFlatLandAirport( */ static CommandCost CheckFlatLandRailStation(TileArea tile_area, DoCommandFlag flags, Axis axis, StationID *station, RailType rt, std::vector &affected_vehicles, StationClassID spec_class, byte spec_index, byte plat_len, byte numtracks) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_TRAIN_CON); int allowed_z = -1; uint invalid_dirs = 5 << axis; @@ -961,7 +999,7 @@ static CommandCost CheckFlatLandRailStat */ static CommandCost CheckFlatLandRoadStop(TileArea tile_area, DoCommandFlag flags, uint invalid_dirs, bool is_drive_through, bool is_truck_stop, Axis axis, StationID *station, RoadType rt) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); int allowed_z = -1; for (TileIndex cur_tile : tile_area) { @@ -1552,7 +1590,7 @@ CommandCost RemoveFromRailBaseStation(Ti { /* Count of the number of tiles removed */ int quantity = 0; - CommandCost total_cost(EXPENSES_CONSTRUCTION); + CommandCost total_cost(EXPENSES_T_TRAIN_CON); /* Accumulator for the errors seen during clearing. If no errors happen, * and the quantity is 0 there is no station. Otherwise it will be one * of the other error that got accumulated. */ @@ -1721,7 +1759,7 @@ CommandCost RemoveRailStation(T *st, DoC assert(ta.w != 0 && ta.h != 0); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_TRAIN_CON); /* clear all areas of the station */ for (TileIndex tile : ta) { /* only remove tiles that are actually train station tiles */ @@ -1853,7 +1891,7 @@ CommandCost CmdBuildRoadStop(DoCommandFl bool is_truck_stop = stop_type != ROADSTOP_BUS; /* Total road stop cost. */ - CommandCost cost(EXPENSES_CONSTRUCTION, roadstop_area.w * roadstop_area.h * _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]); + CommandCost cost(EXPENSES_T_ROAD_CON, roadstop_area.w * roadstop_area.h * _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]); StationID est = INVALID_STATION; ret = CheckFlatLandRoadStop(roadstop_area, flags, is_drive_through ? 5 << axis : 1 << ddir, is_drive_through, is_truck_stop, axis, &est, rt); if (ret.Failed()) return ret; @@ -2045,7 +2083,7 @@ static CommandCost RemoveRoadStop(TileIn } } - return CommandCost(EXPENSES_CONSTRUCTION, _price[is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS]); + return CommandCost(EXPENSES_T_ROAD_CON, _price[is_truck ? PR_CLEAR_STATION_TRUCK : PR_CLEAR_STATION_BUS]); } /** @@ -2070,7 +2108,7 @@ CommandCost CmdRemoveRoadStop(DoCommandF TileArea roadstop_area(tile, width, height); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_ROAD_CON); CommandCost last_error(STR_ERROR_THERE_IS_NO_STATION); bool had_success = false; @@ -2350,7 +2388,7 @@ static CommandCost RemoveAirport(TileInd tile = st->airport.tile; - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_AIRCRAFT_CON); for (const Aircraft *a : Aircraft::Iterate()) { if (!a->IsNormalAircraft()) continue; @@ -2496,7 +2534,7 @@ CommandCost CmdBuildDock(DoCommandFlag f if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); - CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_STATION_DOCK]); + CommandCost cost(EXPENSES_T_SHIP_CON, _price[PR_BUILD_STATION_DOCK]); ret = Command::Do(flags, tile); if (ret.Failed()) return ret; cost.AddCost(ret); @@ -2696,7 +2734,7 @@ static CommandCost RemoveDock(TileIndex } } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_STATION_DOCK]); + return CommandCost(EXPENSES_T_SHIP_CON, _price[PR_CLEAR_STATION_DOCK]); } #include "table/station_land.h" @@ -2994,7 +3032,6 @@ draw_default_foundation: } if (HasStationRail(ti->tile) && HasRailCatenaryDrawn(GetRailType(ti->tile))) DrawRailCatenary(ti); - if (IsRoadStop(ti->tile)) { RoadType road_rt = GetRoadTypeRoad(ti->tile); RoadType tram_rt = GetRoadTypeTram(ti->tile); @@ -3565,6 +3602,64 @@ static void UpdateStationRating(Station } } + // station dilapidation + { + if (_settings_game.economy.dilapidation_max_amount > 0) { + bool dilapidation_calc = false; + uint16 dilapidation_pop = 0; + uint pop_count = st->goods[CT_PASSENGERS].cargo.AvailableCount(); + if (Company::IsValidID(st->owner)) { + if ((st->facilities & FACIL_TRAIN) != 0) { + dilapidation_calc = true; + if (_settings_game.economy.dilapidation_pop_rail > dilapidation_pop) { + dilapidation_pop = _settings_game.economy.dilapidation_pop_rail; + } + } + if (((st->facilities & FACIL_BUS_STOP) != 0) || ((st->facilities & FACIL_TRUCK_STOP) != 0)) { + dilapidation_calc = true; + if (_settings_game.economy.dilapidation_pop_road > dilapidation_pop) { + dilapidation_pop = _settings_game.economy.dilapidation_pop_road; + } + } + if ((st->facilities & FACIL_DOCK) != 0) { + dilapidation_calc = true; + if (_settings_game.economy.dilapidation_pop_water > dilapidation_pop) { + dilapidation_pop = _settings_game.economy.dilapidation_pop_water; + } + } + if ((st->facilities & FACIL_AIRPORT) != 0) { + dilapidation_calc = true; + if (_settings_game.economy.dilapidation_pop_air > dilapidation_pop) { + dilapidation_pop = _settings_game.economy.dilapidation_pop_air; + } + } + } + if (dilapidation_calc && pop_count > (uint)dilapidation_pop) { + // increase the dilapidation, preventing overflow + uint16 top_boundary = 65535 - _settings_game.economy.dilapidation_increase; + if (st->dilapidation >= top_boundary) { + st->dilapidation = 65535; + } + else { + st->dilapidation += _settings_game.economy.dilapidation_increase; + } + } + else + { + // decrease the dilapidation, preventing underflow + if (st->dilapidation <= _settings_game.economy.dilapidation_decrease) { + st->dilapidation = 0; + } + else { + st->dilapidation -= _settings_game.economy.dilapidation_decrease; + } + } + } + if (st->dilapidation > _settings_game.economy.dilapidation_max_amount) { + st->dilapidation = _settings_game.economy.dilapidation_max_amount; + } + } + StationID index = st->index; if (waiting_changed) { SetWindowDirty(WC_STATION_VIEW, index); // update whole window @@ -3803,6 +3898,40 @@ void OnTick_Station() } } +/** Charges player for dilapidation at station */ +void ChargePlayerForDilapidation(Station *st) +{ + if (st->dilapidation > 0 && Company::IsValidID(st->owner)) { + uint32 dilapidation_fine = 0; + if ((st->facilities & FACIL_TRAIN) != 0) { + if (_settings_game.economy.dilapidation_fine_rail > dilapidation_fine) { + dilapidation_fine = _settings_game.economy.dilapidation_fine_rail; + } + } + if (((st->facilities & FACIL_BUS_STOP) != 0) || ((st->facilities & FACIL_TRUCK_STOP) != 0)) { + if (_settings_game.economy.dilapidation_fine_road > dilapidation_fine) { + dilapidation_fine = _settings_game.economy.dilapidation_fine_road; + } + } + if ((st->facilities & FACIL_DOCK) != 0) { + if (_settings_game.economy.dilapidation_fine_water > dilapidation_fine) { + dilapidation_fine = _settings_game.economy.dilapidation_fine_water; + } + } + if ((st->facilities & FACIL_AIRPORT) != 0) { + if (_settings_game.economy.dilapidation_fine_air > dilapidation_fine) { + dilapidation_fine = _settings_game.economy.dilapidation_fine_air; + } + } + Money actual_fine = dilapidation_fine; + actual_fine *= st->dilapidation; + actual_fine *= (_economy.inflation_prices >> 16); + if (actual_fine > 0) { + SubtractMoneyFromCompanyFract(st->owner, CommandCost(EXPENSES_T_DILAPIDATION, actual_fine << 8)); + } + } +} + /** Monthly loop for stations. */ void StationMonthlyLoop() { @@ -3812,6 +3941,7 @@ void StationMonthlyLoop() SB(ge->status, GoodsEntry::GES_LAST_MONTH, 1, GB(ge->status, GoodsEntry::GES_CURRENT_MONTH, 1)); ClrBit(ge->status, GoodsEntry::GES_CURRENT_MONTH); } + ChargePlayerForDilapidation(st); } } @@ -4302,11 +4432,11 @@ static CommandCost TerraformTile_Station DiagDirection direction = AxisToDiagDir(GetRailStationAxis(tile)); if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, direction)) break; if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + return CommandCost(EXPENSES_T_TRAIN_CON, _price[PR_BUILD_FOUNDATION]); } case STATION_AIRPORT: - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + return CommandCost(EXPENSES_T_AIRCRAFT_CON, _price[PR_BUILD_FOUNDATION]); case STATION_TRUCK: case STATION_BUS: { @@ -4315,7 +4445,7 @@ static CommandCost TerraformTile_Station if (IsDriveThroughStopTile(tile)) { if (!AutoslopeCheckForEntranceEdge(tile, z_new, tileh_new, ReverseDiagDir(direction))) break; } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_FOUNDATION]); + return CommandCost(EXPENSES_T_ROAD_CON, _price[PR_BUILD_FOUNDATION]); } default: break; diff --git a/src/station_gui.cpp b/src/station_gui.cpp --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -322,6 +322,13 @@ protected: return minr1 > minr2; } + /** Sort stations by their dilapidation */ + static bool StationDilapidationSorter(const Station* const& a, const Station* const& b) + { + if (a->dilapidation == b->dilapidation) return StationNameSorter(a, b); // fallback + return a->dilapidation < b->dilapidation; + } + /** Sort the stations list */ void SortStationsList() { @@ -460,7 +467,17 @@ public: SetDParam(0, st->index); SetDParam(1, st->facilities); - int x = DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y + (line_height - FONT_HEIGHT_NORMAL) / 2, STR_STATION_LIST_STATION); + int x; + + if (_settings_game.economy.dilapidation_max_amount > 0 && st->dilapidation > 0) + { + SetDParam(2, st->dilapidation); + x = DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y + (line_height - FONT_HEIGHT_NORMAL) / 2, STR_STATION_LIST_STATION_WITH_DILAPIDATION); + } + else + { + x = DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y + (line_height - FONT_HEIGHT_NORMAL) / 2, STR_STATION_LIST_STATION); + } x += rtl ? -text_spacing : text_spacing; /* show cargo waiting and station ratings */ @@ -699,7 +716,8 @@ GUIStationList::SortFunction * const Com &StationWaitingTotalSorter, &StationWaitingAvailableSorter, &StationRatingMaxSorter, - &StationRatingMinSorter + &StationRatingMinSorter, + &StationDilapidationSorter }; /* Names of the sorting functions */ @@ -710,6 +728,7 @@ const StringID CompanyStationsWindow::so STR_SORT_BY_WAITING_AVAILABLE, STR_SORT_BY_RATING_MAX, STR_SORT_BY_RATING_MIN, + STR_SORT_BY_DILAPIDATION, INVALID_STRING_ID }; @@ -1854,6 +1873,13 @@ struct StationViewWindow : public Window y += WD_PAR_VSEP_WIDE; } + if (_settings_game.economy.dilapidation_max_amount > 0) { + SetDParam(0, st->dilapidation); + SetDParam(1, _settings_game.economy.dilapidation_max_amount); + y = DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, r.bottom, STR_STATION_VIEW_DILAPIDATION); + y += WD_PAR_VSEP_WIDE; + } + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_STATION_VIEW_SUPPLY_RATINGS_TITLE); y += FONT_HEIGHT_NORMAL; diff --git a/src/station_type.h b/src/station_type.h --- a/src/station_type.h +++ b/src/station_type.h @@ -85,7 +85,7 @@ enum CatchmentArea { MAX_CATCHMENT = 10, ///< Maximum catchment for airports with "modified catchment" enabled }; -static const uint MAX_LENGTH_STATION_NAME_CHARS = 32; ///< The maximum length of a station name in characters including '\0' +static const uint MAX_LENGTH_STATION_NAME_CHARS = 128; ///< The maximum length of a station name in characters including '\0' struct StationCompare { bool operator() (const Station *lhs, const Station *rhs) const; diff --git a/src/stdafx.h b/src/stdafx.h --- a/src/stdafx.h +++ b/src/stdafx.h @@ -428,6 +428,16 @@ static_assert(SIZE_MAX >= UINT32_MAX); /* For the FMT library we only want to use the headers, not link to some library. */ #define FMT_HEADER_ONLY +#if defined(__GNUC__) || defined(__clang__) +__attribute__((aligned(1))) typedef uint16 unaligned_uint16; +__attribute__((aligned(1))) typedef uint32 unaligned_uint32; +__attribute__((aligned(1))) typedef uint64 unaligned_uint64; +#else +typedef uint16 unaligned_uint16; +typedef uint32 unaligned_uint32; +typedef uint64 unaligned_uint64; +#endif /* __GNUC__ || __clang__ */ + void NORETURN CDECL usererror(const char *str, ...) WARN_FORMAT(1, 2); void NORETURN CDECL error(const char *str, ...) WARN_FORMAT(1, 2); #define NOT_REACHED() error("NOT_REACHED triggered at line %i of %s", __LINE__, __FILE__) diff --git a/src/string.cpp b/src/string.cpp --- a/src/string.cpp +++ b/src/string.cpp @@ -120,9 +120,9 @@ char *strecpy(char *dst, const char *src if (dst == last && *src != '\0') { #if defined(STRGEN) || defined(SETTINGSGEN) - error("String too long for destination buffer"); + error("String too long for destination buffer: %s", src); #else /* STRGEN || SETTINGSGEN */ - Debug(misc, 0, "String too long for destination buffer"); + Debug(misc, 0, "String too long for destination buffer: {}", src); #endif /* STRGEN || SETTINGSGEN */ } return dst; @@ -143,6 +143,16 @@ char *stredup(const char *s, const char return tmp; } +char *str_vfmt(const char *str, va_list va) +{ + char buf[4096]; + + int len = vseprintf(buf, lastof(buf), str, va); + char *p = MallocT(len + 1); + memcpy(p, buf, len + 1); + return p; +} + /** * Format, "printf", into a newly allocated string. * @param str The formatting string. diff --git a/src/string_func.h b/src/string_func.h --- a/src/string_func.h +++ b/src/string_func.h @@ -39,6 +39,7 @@ int CDECL seprintf(char *str, const char int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap) WARN_FORMAT(3, 0) NOACCESS(2); char *CDECL str_fmt(const char *str, ...) WARN_FORMAT(1, 2); +char *str_vfmt(const char *str, va_list ap); std::string FormatArrayAsHex(span data); diff --git a/src/string_func_extra.h b/src/string_func_extra.h new file mode 100644 --- /dev/null +++ b/src/string_func_extra.h @@ -0,0 +1,23 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +#ifndef STRING_FUNC_EXTRA_H +#define STRING_FUNC_EXTRA_H + +#include "string_func.h" +#include + +static inline void str_validate(std::string &str, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK) +{ + if (str.empty()) return; + char *buf = const_cast(str.c_str()); + str.resize(str_validate_intl(buf, buf + str.size(), settings) - buf); +} + +#endif /* STRING_FUNC_EXTRA_H */ diff --git a/src/strings.cpp b/src/strings.cpp --- a/src/strings.cpp +++ b/src/strings.cpp @@ -1027,6 +1027,9 @@ static char *FormatString(char *buff, co case SCC_REVISION: // {REV} buff = strecpy(buff, _openttd_revision, last); + buff = strecpy(buff, " (", last); + buff = strecpy(buff, _openttd_build_date_short, last); + buff = strecpy(buff, ")", last); break; case SCC_RAW_STRING_POINTER: { // {RAW_STRING} @@ -1430,23 +1433,39 @@ static char *FormatString(char *buff, co StringParameters tmp_params(args_array); buff = GetStringWithArgs(buff, STR_JUST_RAW_STRING, &tmp_params, last); } else { - StringID str = st->string_id; - if (st->indtype != IT_INVALID) { - /* Special case where the industry provides the name for the station */ - const IndustrySpec *indsp = GetIndustrySpec(st->indtype); + bool station_name_from_industry_success = false; - /* Industry GRFs can change which might remove the station name and - * thus cause very strange things. Here we check for that before we - * actually set the station name. */ - if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) { - str = indsp->station_name; + StringID str = st->string_id; + if (st->indtype != IT_INVALID) + { + if (_settings_game.construction.name_stations_based_on_industries == 2) + { + int64 args_array[] = { st->town->index, GetIndustrySpec(st->indtype)->name }; + StringParameters tmp_params(args_array); + buff = FormatString(buff, GetStringPtr(STR_FORMAT_INDUSTRY_NAME), &tmp_params, last); + station_name_from_industry_success = true; + } + else if (_settings_game.construction.name_stations_based_on_industries == 1) + { + /* Special case where the industry provides the name for the station */ + const IndustrySpec *indsp = GetIndustrySpec(st->indtype); + + /* Industry GRFs can change which might remove the station name and + * thus cause very strange things. Here we check for that before we + * actually set the station name. */ + if (indsp->station_name != STR_NULL && indsp->station_name != STR_UNDEFINED) { + str = indsp->station_name; + } } } - - uint64 args_array[] = {STR_TOWN_NAME, st->town->index, st->index}; - WChar types_array[] = {0, SCC_TOWN_NAME, SCC_NUM}; - StringParameters tmp_params(args_array, 3, types_array); - buff = GetStringWithArgs(buff, str, &tmp_params, last); + + if (!station_name_from_industry_success) + { + uint64 args_array[] = {STR_TOWN_NAME, st->town->index, st->index}; + WChar types_array[] = {0, SCC_TOWN_NAME, SCC_NUM}; + StringParameters tmp_params(args_array, 3, types_array); + buff = GetStringWithArgs(buff, str, &tmp_params, last); + } } break; } diff --git a/src/table/settings.h.preamble b/src/table/settings.h.preamble --- a/src/table/settings.h.preamble +++ b/src/table/settings.h.preamble @@ -56,56 +56,56 @@ static size_t ConvertLandscape(const cha /* Macros for various objects to go in the configuration file. * This section is for global variables */ -#define SDTG_VAR(name, type, flags, var, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Int, SLEG_GENERAL(name, SL_VAR, var, type, 1, from, to, extra), flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) +#define SDTG_VAR(name, type, flags, var, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(Int, SLEG_GENERAL_X(name, SL_VAR, var, type, 1, from, to, extra, extver), flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) -#define SDTG_BOOL(name, flags, var, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Bool, SLEG_GENERAL(name, SL_VAR, var, SLE_BOOL, 1, from, to, extra), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) +#define SDTG_BOOL(name, flags, var, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(Bool, SLEG_GENERAL_X(name, SL_VAR, var, SLE_BOOL, 1, from, to, extra, extver), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) -#define SDTG_LIST(name, type, flags, var, def, length, from, to, cat, extra, startup)\ - NSD(List, SLEG_GENERAL(name, SL_ARR, var, type, length, from, to, extra),flags, startup, def) +#define SDTG_LIST(name, type, flags, var, def, length, from, to, cat, extra, startup, extver)\ + NSD(List, SLEG_GENERAL_X(name, SL_ARR, var, type, length, from, to, extra, extver),flags, startup, def) -#define SDTG_SSTR(name, type, flags, var, def, max_length, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(String, SLEG_GENERAL(name, SL_STDSTR, var, type, sizeof(var), from, to, extra), flags, startup, def, max_length, pre_check, post_callback) +#define SDTG_SSTR(name, type, flags, var, def, max_length, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(String, SLEG_GENERAL_X(name, SL_STDSTR, var, type, sizeof(var), from, to, extra, extver), flags, startup, def, max_length, pre_check, post_callback) -#define SDTG_OMANY(name, type, flags, var, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(OneOfMany, SLEG_GENERAL(name, SL_VAR, var, type, 1, from, to, extra), flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) +#define SDTG_OMANY(name, type, flags, var, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(OneOfMany, SLEG_GENERAL_X(name, SL_VAR, var, type, 1, from, to, extra, extver), flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) -#define SDTG_MMANY(name, type, flags, var, def, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(ManyOfMany, SLEG_GENERAL(name, SL_VAR, var, type, 1, from, to, extra), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) +#define SDTG_MMANY(name, type, flags, var, def, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(ManyOfMany, SLEG_GENERAL_X(name, SL_VAR, var, type, 1, from, to, extra, extver), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) /* Macros for various objects to go in the configuration file. * This section is for structures where their various members are saved */ -#define SDT_VAR(base, var, type, flags, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Int, SLE_GENERAL(SL_VAR, base, var, type, 1, from, to, extra), flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) +#define SDT_VAR(base, var, type, flags, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(Int, SLE_GENERAL_X(SL_VAR, base, var, type, 1, from, to, extra, extver), flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) -#define SDT_BOOL(base, var, flags, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Bool, SLE_GENERAL(SL_VAR, base, var, SLE_BOOL, 1, from, to, extra), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) +#define SDT_BOOL(base, var, flags, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(Bool, SLE_GENERAL_X(SL_VAR, base, var, SLE_BOOL, 1, from, to, extra, extver), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) -#define SDT_LIST(base, var, type, flags, def, from, to, cat, extra, startup)\ - NSD(List, SLE_GENERAL(SL_ARR, base, var, type, lengthof(((base*)8)->var), from, to, extra), flags, startup, def) +#define SDT_LIST(base, var, type, flags, def, from, to, cat, extra, startup, extver)\ + NSD(List, SLE_GENERAL_X(SL_ARR, base, var, type, lengthof(((base*)8)->var), from, to, extra, extver), flags, startup, def) -#define SDT_SSTR(base, var, type, flags, def, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(String, SLE_GENERAL(SL_STDSTR, base, var, type, sizeof(((base*)8)->var), from, to, extra), flags, startup, def, 0, pre_check, post_callback) +#define SDT_SSTR(base, var, type, flags, def, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + NSD(String, SLE_GENERAL_X(SL_STDSTR, base, var, type, sizeof(((base*)8)->var), from, to, extra, extver), flags, startup, def, 0, pre_check, post_callback) -#define SDT_OMANY(base, var, type, flags, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, load, cat, extra, startup)\ - NSD(OneOfMany, SLE_GENERAL(SL_VAR, base, var, type, 1, from, to, extra), flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, load) +#define SDT_OMANY(base, var, type, flags, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, load, cat, extra, startup, extver)\ + NSD(OneOfMany, SLE_GENERAL_X(SL_VAR, base, var, type, 1, from, to, extra, extver), flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, load) -#define SDT_MMANY(base, var, type, flags, def, full, str, pre_check, post_callback, strhelp, strval, from, to, cat, extra, startup)\ - NSD(ManyOfMany, SLE_GENERAL(SL_VAR, base, var, type, 1, from, to, extra), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) +#define SDT_MMANY(base, var, type, flags, def, full, str, pre_check, post_callback, strhelp, strval, from, to, cat, extra, startup, extver)\ + NSD(ManyOfMany, SLE_GENERAL_X(SL_VAR, base, var, type, 1, from, to, extra, extver), flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) -#define SDTC_VAR(var, type, flags, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - SDTG_VAR(#var, type, flags, _settings_client.var, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup) +#define SDTC_VAR(var, type, flags, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + SDTG_VAR(#var, type, flags, _settings_client.var, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver) -#define SDTC_BOOL(var, flags, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - SDTG_BOOL(#var, flags, _settings_client.var, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup) +#define SDTC_BOOL(var, flags, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + SDTG_BOOL(#var, flags, _settings_client.var, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver) -#define SDTC_LIST(var, type, flags, def, from, to, cat, extra, startup)\ - SDTG_LIST(#var, type, flags, _settings_client.var, def, lengthof(_settings_client.var), from, to, cat, extra, startup) +#define SDTC_LIST(var, type, flags, def, from, to, cat, extra, startup, extver)\ + SDTG_LIST(#var, type, flags, _settings_client.var, def, lengthof(_settings_client.var), from, to, cat, extra, startup, extver) -#define SDTC_SSTR(var, type, flags, def, max_length, pre_check, post_callback, from, to, cat, extra, startup)\ - SDTG_SSTR(#var, type, flags, _settings_client.var, def, max_length, pre_check, post_callback, from, to, cat, extra, startup)\ +#define SDTC_SSTR(var, type, flags, def, max_length, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + SDTG_SSTR(#var, type, flags, _settings_client.var, def, max_length, pre_check, post_callback, from, to, cat, extra, startup, extver)\ -#define SDTC_OMANY(var, type, flags, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - SDTG_OMANY(#var, type, flags, _settings_client.var, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup) +#define SDTC_OMANY(var, type, flags, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver)\ + SDTG_OMANY(#var, type, flags, _settings_client.var, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup, extver) diff --git a/src/table/settings/company_settings.ini b/src/table/settings/company_settings.ini --- a/src/table/settings/company_settings.ini +++ b/src/table/settings/company_settings.ini @@ -16,8 +16,8 @@ static const SettingVariant _company_set [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(CompanySettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(CompanySettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(CompanySettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(CompanySettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for CompanySettings.$var exceeds storage size"); @@ -36,6 +36,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() diff --git a/src/table/settings/currency_settings.ini b/src/table/settings/currency_settings.ini --- a/src/table/settings/currency_settings.ini +++ b/src/table/settings/currency_settings.ini @@ -11,8 +11,8 @@ static const SettingVariant _currency_se [post-amble] }; [templates] -SDT_VAR = SDT_VAR (CurrencySpec, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_SSTR = SDT_SSTR(CurrencySpec, $var, $type, $flags, $def, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_VAR = SDT_VAR (CurrencySpec, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_SSTR = SDT_SSTR(CurrencySpec, $var, $type, $flags, $def, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for CurrencySpec.$var exceeds storage size"); @@ -31,6 +31,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_VAR] diff --git a/src/table/settings/difficulty_settings.ini b/src/table/settings/difficulty_settings.ini --- a/src/table/settings/difficulty_settings.ini +++ b/src/table/settings/difficulty_settings.ini @@ -20,9 +20,9 @@ static const SettingVariant _difficulty_ [post-amble] }; [templates] -SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -42,6 +42,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() ; Saved settings variables. @@ -171,6 +172,18 @@ strval = STR_DISASTER_NONE cat = SC_BASIC [SDT_VAR] +var = difficulty.vehicle_breakdown_scaler +type = SLE_UINT32 +def = 16777215 +min = 0 +max = 16777215 +interval = 1 +str = STR_CONFIG_SETTING_VEHICLE_BREAKDOWN_SCALER +strhelp = STR_CONFIG_SETTING_VEHICLE_BREAKDOWN_SCALER_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_BREAKDOWN_SCALER) + +[SDT_VAR] var = difficulty.subsidy_multiplier type = SLE_UINT8 from = SLV_97 diff --git a/src/table/settings/economy_settings.ini b/src/table/settings/economy_settings.ini --- a/src/table/settings/economy_settings.ini +++ b/src/table/settings/economy_settings.ini @@ -9,13 +9,14 @@ [pre-amble] static void TownFoundingChanged(int32 new_value); +static void PatchpackInvalidateRunningCostsOrElse(int32 new_value); static const SettingVariant _economy_settings_table[] = { [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for GameSettings.$var exceeds storage size"); @@ -34,6 +35,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_VAR] @@ -127,6 +129,15 @@ post_cb = [](auto) { SetWindowClassesDi cat = SC_BASIC [SDT_BOOL] +var = economy.bribe_risky +def = true +str = STR_CONFIG_SETTING_BRIBE_RISKY +strhelp = STR_CONFIG_SETTING_BRIBE_RISKY_HELPTEXT +post_cb = [](auto) { SetWindowClassesDirty(WC_TOWN_AUTHORITY); } +cat = SC_BASIC +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_IMPROVEMENTS) + +[SDT_BOOL] var = economy.exclusive_rights from = SLV_79 def = true @@ -174,6 +185,30 @@ strval = STR_CONFIG_SETTING_ECONOMY_TY post_cb = [](auto) { InvalidateWindowClassesData(WC_INDUSTRY_VIEW); } cat = SC_BASIC +[SDT_VAR] +var = economy.daylength_multiplier +type = SLE_UINT8 +def = 1 +min = 1 +max = 255 +interval = 1 +str = STR_CONFIG_SETTING_DAYLENGTH_FACTOR +strhelp = STR_CONFIG_SETTING_DAYLENGTH_FACTOR_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DAYLENGTH) + +[SDT_VAR] +var = economy.town_growth_multiplier +type = SLE_UINT8 +def = 1 +min = 1 +max = 255 +interval = 1 +str = STR_CONFIG_SETTING_TOWN_GROWTH_FACTOR +strhelp = STR_CONFIG_SETTING_TOWN_GROWTH_FACTOR_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_TOWN_IMPROVEMENTS) + [SDT_BOOL] var = economy.allow_shares def = false @@ -292,3 +327,272 @@ str = STR_CONFIG_SETTING_INFRASTRUC strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_MAINTENANCE_HELPTEXT post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } cat = SC_BASIC + +[SDT_VAR] +var = economy.infrastructure_base_cost_rail +type = SLE_UINT16 +def = 10 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_RAIL +strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_RAIL_HELPTEXT +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRASTRUCTURE) + +[SDT_VAR] +var = economy.infrastructure_base_cost_road +type = SLE_UINT16 +def = 10 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_ROAD +strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_ROAD_HELPTEXT +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRASTRUCTURE) + +[SDT_VAR] +var = economy.infrastructure_base_cost_water +type = SLE_UINT16 +def = 8 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_WATER +strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_WATER_HELPTEXT +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRASTRUCTURE) + +[SDT_VAR] +var = economy.infrastructure_base_cost_air +type = SLE_UINT16 +def = 5000 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_AIR +strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_AIR_HELPTEXT +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRASTRUCTURE) + +[SDT_VAR] +var = economy.infrastructure_base_cost_station +type = SLE_UINT16 +def = 100 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_STATION +strhelp = STR_CONFIG_SETTING_INFRASTRUCTURE_BASE_COST_STATION_HELPTEXT +post_cb = [](auto) { InvalidateWindowClassesData(WC_COMPANY_INFRASTRUCTURE); } +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_INFRASTRUCTURE) + +[SDT_VAR] +var = economy.running_cost_multiplier_rail +type = SLE_UINT16 +def = 1 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_RAIL +strhelp = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_RAIL_HELPTEXT +post_cb = PatchpackInvalidateRunningCostsOrElse +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_RUNNING_COST_MULT) + +[SDT_VAR] +var = economy.running_cost_multiplier_road +type = SLE_UINT16 +def = 1 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_ROAD +strhelp = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_ROAD_HELPTEXT +post_cb = PatchpackInvalidateRunningCostsOrElse +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_RUNNING_COST_MULT) + +[SDT_VAR] +var = economy.running_cost_multiplier_water +type = SLE_UINT16 +def = 1 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_WATER +strhelp = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_WATER_HELPTEXT +post_cb = PatchpackInvalidateRunningCostsOrElse +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_RUNNING_COST_MULT) + +[SDT_VAR] +var = economy.running_cost_multiplier_air +type = SLE_UINT16 +def = 1 +min = 1 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_AIR +strhelp = STR_CONFIG_SETTING_RUNNING_COST_MULTIPLIER_AIR_HELPTEXT +post_cb = PatchpackInvalidateRunningCostsOrElse +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_RUNNING_COST_MULT) + +[SDT_VAR] +var = economy.dilapidation_max_amount +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_MAX_AMOUNT +strhelp = STR_CONFIG_SETTING_DILAPIDATION_MAX_AMOUNT_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_increase +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_INCREASE +strhelp = STR_CONFIG_SETTING_DILAPIDATION_INCREASE_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_decrease +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_DECREASE +strhelp = STR_CONFIG_SETTING_DILAPIDATION_DECREASE_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_pop_rail +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_POP_RAIL +strhelp = STR_CONFIG_SETTING_DILAPIDATION_POP_RAIL_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_pop_road +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_POP_ROAD +strhelp = STR_CONFIG_SETTING_DILAPIDATION_POP_ROAD_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_pop_water +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_POP_WATER +strhelp = STR_CONFIG_SETTING_DILAPIDATION_POP_WATER_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_pop_air +type = SLE_UINT16 +def = 0 +min = 0 +max = 65535 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_POP_AIR +strhelp = STR_CONFIG_SETTING_DILAPIDATION_POP_AIR_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_fine_rail +type = SLE_UINT32 +def = 0 +min = 0 +max = 1000000 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_FINE_RAIL +strhelp = STR_CONFIG_SETTING_DILAPIDATION_FINE_RAIL_HELPTEXT +strval = STR_JUST_CURRENCY_LONG +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_fine_road +type = SLE_UINT32 +def = 0 +min = 0 +max = 1000000 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_FINE_ROAD +strhelp = STR_CONFIG_SETTING_DILAPIDATION_FINE_ROAD_HELPTEXT +strval = STR_JUST_CURRENCY_LONG +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_fine_water +type = SLE_UINT32 +def = 0 +min = 0 +max = 1000000 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_FINE_WATER +strhelp = STR_CONFIG_SETTING_DILAPIDATION_FINE_WATER_HELPTEXT +strval = STR_JUST_CURRENCY_LONG +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) + +[SDT_VAR] +var = economy.dilapidation_fine_air +type = SLE_UINT32 +def = 0 +min = 0 +max = 1000000 +interval = 1 +cat = SC_EXPERT +str = STR_CONFIG_SETTING_DILAPIDATION_FINE_AIR +strhelp = STR_CONFIG_SETTING_DILAPIDATION_FINE_AIR_HELPTEXT +strval = STR_JUST_CURRENCY_LONG +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_DILAPIDATION) diff --git a/src/table/settings/game_settings.ini b/src/table/settings/game_settings.ini --- a/src/table/settings/game_settings.ini +++ b/src/table/settings/game_settings.ini @@ -26,12 +26,12 @@ static const SettingVariant _game_settin [post-amble] }; [templates] -SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -52,6 +52,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_BOOL] @@ -327,6 +328,18 @@ str = STR_CONFIG_SETTING_PLANE_SPEE strhelp = STR_CONFIG_SETTING_PLANE_SPEED_HELPTEXT strval = STR_CONFIG_SETTING_PLANE_SPEED_VALUE +[SDT_VAR] +var = vehicle.plane_range_multiplier +type = SLE_UINT8 +def = 1 +min = 1 +max = 255 +interval = 1 +str = STR_CONFIG_SETTING_PLANE_RANGE_MULTIPLIER +strhelp = STR_CONFIG_SETTING_PLANE_RANGE_MULTIPLIER_HELPTEXT +strval = STR_JUST_INT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_PLANE_RANGE_MULT) + [SDT_BOOL] var = vehicle.dynamic_engines from = SLV_95 @@ -349,6 +362,14 @@ strhelp = STR_CONFIG_SETTING_PLANE_CRAS strval = STR_CONFIG_SETTING_PLANE_CRASHES_NONE cat = SC_BASIC +[SDT_BOOL] +var = vehicle.improved_breakdowns +flags = SF_NO_NETWORK +def = false +str = STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS +strhelp = STR_CONFIG_SETTING_IMPROVED_BREAKDOWNS_HELPTEXT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_IMPROVED_BREAKDOWNS) + [SDT_VAR] var = vehicle.extend_vehicle_life type = SLE_UINT8 diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -23,9 +23,9 @@ static const SettingVariant _gui_setting [post-amble] }; [templates] -SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTC_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -45,6 +45,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDTC_OMANY] @@ -294,6 +295,14 @@ strhelp = STR_CONFIG_SETTING_POPULATION post_cb = [](auto) { UpdateAllTownVirtCoords(); } [SDTC_BOOL] +var = gui.colour_based_on_town_rating +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC +def = true +str = STR_CONFIG_SETTING_RATING_COLOUR_IN_LABEL +strhelp = STR_CONFIG_SETTING_RATING_COLOUR_IN_LABEL_HELPTEXT +post_cb = [](auto) { UpdateAllTownVirtCoords(); } + +[SDTC_BOOL] var = gui.link_terraform_toolbar flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC def = false @@ -856,3 +865,9 @@ min = 1 max = 65535 cat = SC_EXPERT +[SDTC_BOOL] +var = gui.load_legacy_patchpack_savedata +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC +def = true +str = STR_CONFIG_SETTING_LOAD_LEGACY_PATCHPACK_DATA +strhelp = STR_CONFIG_SETTING_LOAD_LEGACY_PATCHPACK_DATA_HELPTEXT diff --git a/src/table/settings/linkgraph_settings.ini b/src/table/settings/linkgraph_settings.ini --- a/src/table/settings/linkgraph_settings.ini +++ b/src/table/settings/linkgraph_settings.ini @@ -12,7 +12,7 @@ static const SettingVariant _linkgraph_s [post-amble] }; [templates] -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for GameSettings.$var exceeds storage size"); @@ -31,6 +31,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_VAR] diff --git a/src/table/settings/locale_settings.ini b/src/table/settings/locale_settings.ini --- a/src/table/settings/locale_settings.ini +++ b/src/table/settings/locale_settings.ini @@ -19,9 +19,9 @@ static const SettingVariant _locale_sett [post-amble] }; [templates] -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup), -SDT_SSTR = SDT_SSTR(GameSettings, $var, $type, $flags, $def, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup, $extver), +SDT_SSTR = SDT_SSTR(GameSettings, $var, $type, $flags, $def, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -41,6 +41,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_OMANY] diff --git a/src/table/settings/misc_settings.ini b/src/table/settings/misc_settings.ini --- a/src/table/settings/misc_settings.ini +++ b/src/table/settings/misc_settings.ini @@ -24,12 +24,12 @@ static const SettingVariant _misc_settin [post-amble] }; [templates] -SDTG_LIST = SDTG_LIST($name, $type, $flags, $var, $def, $length, $from, $to, $cat, $extra, $startup), -SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $var, $def, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_SSTR = SDTG_SSTR($name, $type, $flags, $var, $def, 0, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_LIST = SDTG_LIST($name, $type, $flags, $var, $def, $length, $from, $to, $cat, $extra, $startup, $extver), +SDTG_MMANY = SDTG_MMANY($name, $type, $flags, $var, $def, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_SSTR = SDTG_SSTR($name, $type, $flags, $var, $def, 0, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -49,6 +49,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = true +extver = SlXvFeatureTest() diff --git a/src/table/settings/multimedia_settings.ini b/src/table/settings/multimedia_settings.ini --- a/src/table/settings/multimedia_settings.ini +++ b/src/table/settings/multimedia_settings.ini @@ -12,9 +12,9 @@ static const SettingVariant _multimedia_ [post-amble] }; [templates] -SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_LIST = SDTC_LIST( $var, $type, $flags, $def, $from, $to, $cat, $extra, $startup), -SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_LIST = SDTC_LIST( $var, $type, $flags, $def, $from, $to, $cat, $extra, $startup, $extver), +SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTC_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -33,6 +33,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDTC_BOOL] diff --git a/src/table/settings/network_private_settings.ini b/src/table/settings/network_private_settings.ini --- a/src/table/settings/network_private_settings.ini +++ b/src/table/settings/network_private_settings.ini @@ -11,7 +11,7 @@ static const SettingVariant _network_pri [post-amble] }; [templates] -SDTC_SSTR = SDTC_SSTR( $var, $type, $flags, $def, $length, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_SSTR = SDTC_SSTR( $var, $type, $flags, $def, $length, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] @@ -29,6 +29,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() diff --git a/src/table/settings/network_secrets_settings.ini b/src/table/settings/network_secrets_settings.ini --- a/src/table/settings/network_secrets_settings.ini +++ b/src/table/settings/network_secrets_settings.ini @@ -13,7 +13,7 @@ static const SettingVariant _network_sec [post-amble] }; [templates] -SDTC_SSTR = SDTC_SSTR( $var, $type, $flags, $def, $length, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_SSTR = SDTC_SSTR( $var, $type, $flags, $def, $length, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] @@ -31,6 +31,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() diff --git a/src/table/settings/network_settings.ini b/src/table/settings/network_settings.ini --- a/src/table/settings/network_settings.ini +++ b/src/table/settings/network_settings.ini @@ -16,9 +16,9 @@ static const SettingVariant _network_set [post-amble] }; [templates] -SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTC_VAR = SDTC_VAR( $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTC_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -38,6 +38,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDTC_VAR] @@ -253,6 +254,11 @@ flags = SF_NOT_IN_SAVE | SF_NO_NETWOR def = false cat = SC_EXPERT +[SDTC_BOOL] +var = network.save_password +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC +def = false + [SDTC_OMANY] var = network.use_relay_service type = SLE_UINT8 diff --git a/src/table/settings/news_display_settings.ini b/src/table/settings/news_display_settings.ini --- a/src/table/settings/news_display_settings.ini +++ b/src/table/settings/news_display_settings.ini @@ -13,7 +13,7 @@ static const SettingVariant _news_displa [post-amble] }; [templates] -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTC_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -32,6 +32,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDTC_OMANY] diff --git a/src/table/settings/old_gameopt_settings.ini b/src/table/settings/old_gameopt_settings.ini --- a/src/table/settings/old_gameopt_settings.ini +++ b/src/table/settings/old_gameopt_settings.ini @@ -30,13 +30,13 @@ static const SettingVariant _old_gameopt [post-amble] }; [templates] -SDTG_LIST = SDTG_LIST($name, $type, $flags, $var, $def, $length, $from, $to, $cat, $extra, $startup), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_LIST = SDTG_LIST($name, $type, $flags, $var, $def, $length, $from, $to, $cat, $extra, $startup, $extver), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), SDT_NULL = SDT_NULL( $length, $from, $to), -SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_OMANY = SDTG_OMANY($name, $type, $flags, $var, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -59,6 +59,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() diff --git a/src/table/settings/pathfinding_settings.ini b/src/table/settings/pathfinding_settings.ini --- a/src/table/settings/pathfinding_settings.ini +++ b/src/table/settings/pathfinding_settings.ini @@ -14,8 +14,8 @@ static const SettingVariant _pathfinding [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for GameSettings.$var exceeds storage size"); @@ -34,6 +34,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_BOOL] diff --git a/src/table/settings/script_settings.ini b/src/table/settings/script_settings.ini --- a/src/table/settings/script_settings.ini +++ b/src/table/settings/script_settings.ini @@ -14,9 +14,9 @@ static const SettingVariant _script_sett [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_OMANY = static_assert($max <= MAX_$type, "Maximum value for GameSettings.$var exceeds storage size"); @@ -36,6 +36,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() [SDT_OMANY] diff --git a/src/table/settings/win32_settings.ini b/src/table/settings/win32_settings.ini --- a/src/table/settings/win32_settings.ini +++ b/src/table/settings/win32_settings.ini @@ -17,8 +17,8 @@ static const SettingVariant _win32_setti }; #endif /* _WIN32 */ [templates] -SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDTG_BOOL = SDTG_BOOL($name, $flags, $var, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDTG_VAR = SDTG_VAR($name, $type, $flags, $var, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDTG_VAR = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size"); @@ -37,6 +37,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = true +extver = SlXvFeatureTest() [SDTG_BOOL] diff --git a/src/table/settings/window_settings.ini b/src/table/settings/window_settings.ini --- a/src/table/settings/window_settings.ini +++ b/src/table/settings/window_settings.ini @@ -13,8 +13,8 @@ static const SettingVariant _window_sett [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(WindowDesc, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(WindowDesc, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(WindowDesc, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(WindowDesc, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_VAR = static_assert($max <= MAX_$type, "Maximum value for WindowDesc.$var exceeds storage size"); @@ -33,6 +33,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() diff --git a/src/table/settings/world_settings.ini b/src/table/settings/world_settings.ini --- a/src/table/settings/world_settings.ini +++ b/src/table/settings/world_settings.ini @@ -18,9 +18,9 @@ static const SettingVariant _world_setti [post-amble] }; [templates] -SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), -SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup), -SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup), +SDT_BOOL = SDT_BOOL(GameSettings, $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), +SDT_OMANY = SDT_OMANY(GameSettings, $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $load, $cat, $extra, $startup, $extver), +SDT_VAR = SDT_VAR(GameSettings, $var, $type, $flags, $def, $min, $max, $interval, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup, $extver), [validation] SDT_OMANY = static_assert($max <= MAX_$type, "Maximum value for GameSettings.$var exceeds storage size"); @@ -40,6 +40,7 @@ to = SL_MAX_VERSION cat = SC_ADVANCED extra = 0 startup = false +extver = SlXvFeatureTest() ; There are only 21 predefined town_name values (0-20), but you can have more with newgrf action F so allow @@ -482,6 +483,19 @@ strval = STR_CONFIG_SETTING_SIGNALSIDE post_cb = [](auto) { MarkWholeScreenDirty(); } cat = SC_BASIC +[SDT_VAR] +var = construction.name_stations_based_on_industries +type = SLE_UINT8 +flags = SF_GUI_DROPDOWN | SF_NO_NETWORK +def = 1 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES +strhelp = STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_HELPTEXT +strval = STR_CONFIG_SETTING_NAME_STATIONS_BY_INDUSTRIES_SELECTION_0 +post_cb = [](auto) { UpdateAllStationVirtCoords(); } +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_STATION_NEWGRF) + [SDT_BOOL] var = construction.road_stop_on_town_road from = SLV_47 diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -132,7 +132,7 @@ static std::tuple CmdTerraformLand(DoCommandFlag flags, TileIndex tile, Slope slope, bool dir_up) { - CommandCost total_cost(EXPENSES_CONSTRUCTION); + CommandCost total_cost(EXPENSES_T_LANDSCAPING); int direction = (dir_up ? 1 : -1); TerraformerState ts; @@ -352,7 +352,7 @@ std::tuple _settings_game.construction.map_height_limit) return { CommandCost(oldh == 0 ? STR_ERROR_ALREADY_AT_SEA_LEVEL : STR_ERROR_TOO_HIGH), 0, INVALID_TILE }; Money money = GetAvailableMoneyForCommand(); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_LANDSCAPING); CommandCost last_error(lm == LM_LEVEL ? STR_ERROR_ALREADY_LEVELLED : INVALID_STRING_ID); bool had_success = false; diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -406,7 +406,7 @@ void UpdateVehicleTimetable(Vehicle *v, * the timetable entry like is done for road vehicles/ships. * Thus always make sure at least one tick is used between the * processing of different orders when filling the timetable. */ - uint time_to_set = CeilDiv(std::max(time_taken, 1U), DAY_TICKS) * DAY_TICKS; + uint time_to_set = CeilDiv(std::max(time_taken, 1U), ORIG_DAY_TICKS) * ORIG_DAY_TICKS; if (travelling && (autofilling || !real_current_order->IsTravelTimetabled())) { ChangeTimetable(v, v->cur_real_order_index, time_to_set, MTF_TRAVEL_TIME, autofilling); diff --git a/src/town.h b/src/town.h --- a/src/town.h +++ b/src/town.h @@ -13,6 +13,9 @@ #include "viewport_type.h" #include "town_map.h" #include "subsidy_type.h" +#include "openttd.h" +#include "table/strings.h" +#include "company_func.h" #include "newgrf_storage.h" #include "cargotype.h" #include @@ -71,6 +74,7 @@ struct Town : TownPool::PoolItem<&_town_ CompanyID exclusivity; ///< which company has exclusivity uint8 exclusive_counter; ///< months till the exclusivity expires int16 ratings[MAX_COMPANIES]; ///< ratings of each company for this town + StringID town_label; TransportedCargoStat supplied[NUM_CARGO]; ///< Cargo statistics about supplied cargo. TransportedCargoStat received[NUM_TE]; ///< Cargo statistics about received cargotypes. @@ -108,8 +112,31 @@ struct Town : TownPool::PoolItem<&_town_ void InitializeLayout(TownLayout layout); + void UpdateLabel(); + /** - * Calculate the max town noise. + * Returns the correct town label, based on rating and population display settings. + */ + __forceinline StringID Label() const{ + if ((!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) && _settings_client.gui.colour_based_on_town_rating) { + return _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP_VERY_POOR_RATING + this->town_label : STR_VIEWPORT_TOWN_VERY_POOR_RATING + this->town_label; + } else { + return _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN; + } + } + + /** + * Returns the correct town small label, based on rating and population display settings. + */ + __forceinline StringID SmallLabel() const{ + if ((!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) && _settings_client.gui.colour_based_on_town_rating) { + return STR_VIEWPORT_TOWN_TINY_VERY_POOR_RATING + this->town_label; + } else { + return STR_VIEWPORT_TOWN_TINY_WHITE; + } + } + + /** Calculate the max town noise * The value is counted using the population divided by the content of the * entry in town_noise_population corresponding to the town's tolerance. * @return the maximum noise level the town will tolerate. diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -211,6 +211,26 @@ void Town::FillCachedName() const } /** + * Updates the town label of the town after changes in rating. The colour scheme is: + * Red: Appalling and Very poor ratings. + * Orange: Poor and mediocre ratings. + * Yellow: Good rating. + * White: Very good rating (standard). + * Green: Excellent and outstanding ratings. + */ +void Town::UpdateLabel() +{ + if (!(_game_mode == GM_EDITOR) && (_local_company < MAX_COMPANIES)) { + int r = this->ratings[_local_company]; + (this->town_label = 0, r <= RATING_VERYPOOR) || // Appalling and Very Poor + (this->town_label++, r <= RATING_MEDIOCRE) || // Poor and Mediocre + (this->town_label++, r <= RATING_GOOD) || // Good + (this->town_label++, r <= RATING_VERYGOOD) || // Very Good + (this->town_label++, true); // Excellent and Outstanding + } +} + +/** * Get the cost for removing this house * @return the cost (inflation corrected etc) */ @@ -400,15 +420,14 @@ static bool IsCloseToTown(TileIndex tile */ void Town::UpdateVirtCoord() { + this->UpdateLabel(); Point pt = RemapCoords2(TileX(this->xy) * TILE_SIZE, TileY(this->xy) * TILE_SIZE); if (this->cache.sign.kdtree_valid) _viewport_sign_kdtree.Remove(ViewportSignKdtreeItem::MakeTown(this->index)); SetDParam(0, this->index); SetDParam(1, this->cache.population); - this->cache.sign.UpdatePosition(pt.x, pt.y - 24 * ZOOM_LVL_BASE, - _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN, - STR_VIEWPORT_TOWN); + this->cache.sign.UpdatePosition(pt.x, pt.y - 24 * ZOOM_LVL_BASE, this->Label(), STR_VIEWPORT_TOWN); _viewport_sign_kdtree.Insert(ViewportSignKdtreeItem::MakeTown(this->index)); @@ -668,7 +687,7 @@ static CommandCost ClearTile_Town(TileIn const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile)); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_DEMOLITION); cost.AddCost(hs->GetRemovalCost()); int rating = hs->remove_rating_decrease; @@ -1905,7 +1924,7 @@ static CommandCost TownCanBePlacedHere(T return_cmd_error(STR_ERROR_SITE_UNSUITABLE); } - return CommandCost(EXPENSES_OTHER); + return CommandCost(EXPENSES_CONSTRUCTION); } /** @@ -1975,7 +1994,7 @@ std::tuple C /* multidimensional arrays have to have defined length of non-first dimension */ static_assert(lengthof(price_mult[0]) == 4); - CommandCost cost(EXPENSES_OTHER, _price[PR_BUILD_TOWN]); + CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_TOWN]); byte mult = price_mult[city][size]; cost.MultiplyCost(mult); @@ -1984,7 +2003,7 @@ std::tuple C TownID new_town = INVALID_TOWN; if (flags & DC_EXEC) { if (cost.GetCost() > GetAvailableMoneyForCommand()) { - return { CommandCost(EXPENSES_OTHER), cost.GetCost(), INVALID_TOWN }; + return { CommandCost(EXPENSES_CONSTRUCTION), cost.GetCost(), INVALID_TOWN }; } Backup old_generating_world(_generating_world, true, FILE_LINE); @@ -3221,7 +3240,7 @@ static CommandCost TownActionBuyRights(T static CommandCost TownActionBribe(Town *t, DoCommandFlag flags) { if (flags & DC_EXEC) { - if (Chance16(1, 14)) { + if (_settings_game.economy.bribe_risky && Chance16(1, 14)) { /* set as unwanted for 6 months */ t->unwanted[_current_company] = 6; @@ -3242,6 +3261,7 @@ static CommandCost TownActionBribe(Town */ if (t->ratings[_current_company] > RATING_BRIBE_DOWN_TO) { t->ratings[_current_company] = RATING_BRIBE_DOWN_TO; + t->UpdateVirtCoord(); SetWindowDirty(WC_TOWN_AUTHORITY, t->index); } } else { @@ -3383,6 +3403,7 @@ static void UpdateTownRating(Town *t) t->ratings[i] = Clamp(t->ratings[i], RATING_MINIMUM, RATING_MAXIMUM); } + t->UpdateVirtCoord(); SetWindowDirty(WC_TOWN_AUTHORITY, t->index); } @@ -3669,6 +3690,7 @@ void ChangeTownRating(Town *t, int add, } else { SetBit(t->have_ratings, _current_company); t->ratings[_current_company] = rating; + t->UpdateVirtCoord(); SetWindowDirty(WC_TOWN_AUTHORITY, t->index); } } diff --git a/src/town_type.h b/src/town_type.h --- a/src/town_type.h +++ b/src/town_type.h @@ -105,7 +105,7 @@ enum TownCargoGenMode : byte { TCGM_END, }; -static const uint MAX_LENGTH_TOWN_NAME_CHARS = 32; ///< The maximum length of a town name in characters including '\0' +static const uint MAX_LENGTH_TOWN_NAME_CHARS = 128; ///< The maximum length of a town name in characters including '\0' /** Store the maximum and actually transported cargo amount for the current and the last month. */ template diff --git a/src/train.h b/src/train.h --- a/src/train.h +++ b/src/train.h @@ -31,6 +31,14 @@ enum VehicleRailFlags { VRF_TOGGLE_REVERSE = 7, ///< Used for vehicle var 0xFE bit 8 (toggled each time the train is reversed, accurate for first vehicle only). VRF_TRAIN_STUCK = 8, ///< Train can't get a path reservation. VRF_LEAVING_STATION = 9, ///< Train is just leaving a station. + VRF_BREAKDOWN_BRAKING = 10,///< used to mark a train that is braking because it is broken down + VRF_BREAKDOWN_POWER = 11,///< used to mark a train in which the power of one (or more) of the engines is reduced because of a breakdown + VRF_BREAKDOWN_SPEED = 12,///< used to mark a train that has a reduced maximum speed because of a breakdown + VRF_BREAKDOWN_STOPPED = 13,///< used to mark a train that is stopped because of a breakdown + /* Bitmask of all flags that indicate a broken train (braking is not included) */ + VRF_IS_BROKEN = (1 << VRF_BREAKDOWN_POWER) | (1 << VRF_BREAKDOWN_SPEED) | (1 << VRF_BREAKDOWN_STOPPED), + VRF_NEED_REPAIR = 14,///< used to mark a train that has a reduced maximum speed because of a critical breakdown + VRF_TO_HEAVY = 15, }; /** Modes for ignoring signals. */ @@ -62,7 +70,7 @@ void FreeTrainTrackReservation(const Tra bool TryPathReserve(Train *v, bool mark_as_stuck = false, bool first_tile_okay = false); int GetTrainStopLocation(StationID station_id, TileIndex tile, const Train *v, int *station_ahead, int *station_length); - +void CheckBreakdownFlags(Train *v); void GetTrainSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type); /** Variables that are cached to improve performance and such */ @@ -72,6 +80,7 @@ struct TrainCache { /* cached values, recalculated on load and each time a vehicle is added to/removed from the consist. */ bool cached_tilt; ///< train can tilt; feature provides a bonus in curves + uint8 cached_num_engines; ///< total number of engines, including rear ends of multiheaded engines int cached_curve_speed_mod; ///< curve speed modifier of the entire train byte user_def_data; ///< Cached property 0x25. Can be set by Callback 0x36. @@ -175,7 +184,25 @@ struct Train FINAL : public GroundVehicl } protected: // These functions should not be called outside acceleration code. + /** + * Gets the speed a broken down train (low speed breakdown) is limited to. + * @note This value is not cached, because changing cached_max_speed would have unwanted consequences (e.g. in the GUI). + * @param v The front engine of the vehicle. + * @return The speed the train is limited to. + */ + inline uint16 GetBreakdownSpeed() const + { + assert(this->IsFrontEngine()); + uint16 speed = UINT16_MAX; + for (const Train *w = this; w != NULL; w = w->Next()) { + if (w->breakdown_ctr == 1 && w->breakdown_type == BREAKDOWN_LOW_SPEED) { + speed = std::min(speed, w->breakdown_severity); + } + } + return speed; + } + /** * Allows to know the power value that this vehicle will use. * @return Power value from the engine in HP, or zero if the vehicle is not powered. @@ -262,7 +289,7 @@ protected: // These functions should not */ inline AccelStatus GetAccelerationStatus() const { - return (this->vehstatus & VS_STOPPED) || HasBit(this->flags, VRF_REVERSING) || HasBit(this->flags, VRF_TRAIN_STUCK) ? AS_BRAKE : AS_ACCEL; + return (this->vehstatus & VS_STOPPED) || HasBit(this->flags, VRF_REVERSING) || HasBit(this->flags, VRF_TRAIN_STUCK ) || HasBit(this->flags, VRF_BREAKDOWN_BRAKING) ? AS_BRAKE : AS_ACCEL; } /** diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -100,6 +100,34 @@ void CheckTrainsLengths() } /** + * Checks the breakdown flags (VehicleRailFlags 9-12) and sets the correct value in the first vehicle of the consist. + * This function is generally only called to check if a flag may be cleared. + * @param v the front engine + * @param flags bitmask of the flags to check. + */ +void CheckBreakdownFlags(Train *v) +{ + assert(v->IsFrontEngine()); + /* clear the flags we're gonna check first, we'll set them again later (if applicable ) */ + CLRBITS(v->flags, (1 << VRF_BREAKDOWN_BRAKING) | VRF_IS_BROKEN); + + for (const Train *w = v; w != NULL; w = w->Next()) { + if (v->IsEngine() || w->IsMultiheaded()) { + if (w->breakdown_ctr == 2) { + SetBit(v->flags, VRF_BREAKDOWN_BRAKING); + } else if (w->breakdown_ctr == 1) { + switch (w->breakdown_type) { + case BREAKDOWN_CRITICAL: + case BREAKDOWN_EM_STOP: SetBit(v->flags, VRF_BREAKDOWN_STOPPED); break; + case BREAKDOWN_LOW_SPEED: SetBit(v->flags, VRF_BREAKDOWN_SPEED); break; + case BREAKDOWN_LOW_POWER: SetBit(v->flags, VRF_BREAKDOWN_POWER); break; + } + } + } + } +} + +/** * Recalculates the cached stuff of a train. Should be called each time a vehicle is added * to/removed from the chain, and when the game is loaded. * Note: this needs to be called too for 'wagon chains' (in the depot, without an engine) @@ -115,6 +143,7 @@ void Train::ConsistChanged(ConsistChange EngineID first_engine = this->IsFrontEngine() ? this->engine_type : INVALID_ENGINE; this->gcache.cached_total_length = 0; this->compatible_railtypes = RAILTYPES_NONE; + this->tcache.cached_num_engines = 0; bool train_can_tilt = true; int min_curve_speed_mod = INT_MAX; @@ -185,8 +214,13 @@ void Train::ConsistChanged(ConsistChange /* max speed is the minimum of the speed limits of all vehicles in the consist */ if ((rvi_u->railveh_type != RAILVEH_WAGON || _settings_game.vehicle.wagon_speed_limits) && !UsesWagonOverride(u)) { uint16 speed = GetVehicleProperty(u, PROP_TRAIN_SPEED, rvi_u->max_speed); + if (HasBit(u->flags, VRF_NEED_REPAIR)) speed = u->vcache.cached_max_speed; if (speed != 0) max_speed = std::min(speed, max_speed); } + + if(u->IsEngine() || u-> IsMultiheaded()) { + this->tcache.cached_num_engines++; + } } uint16 new_cap = e_u->DetermineCapacity(u); @@ -420,6 +454,10 @@ int Train::GetCurrentMaxSpeed() const } max_speed = std::min(max_speed, this->current_order.GetMaxSpeed()); + if ( HasBit(this->flags, VRF_BREAKDOWN_SPEED) ) { + max_speed = std::min(max_speed, this->GetBreakdownSpeed()); + } + return std::min(max_speed, this->gcache.cached_max_track_speed); } @@ -432,6 +470,14 @@ void Train::UpdateAcceleration() uint weight = this->gcache.cached_weight; assert(weight != 0); this->acceleration = Clamp(power / weight * 4, 1, 255); + + if (_settings_game.vehicle.improved_breakdowns) { + if (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) { + this->breakdown_chance = std::max(128 * 3 / (this->tcache.cached_num_engines + 2), 5); + } + } else { + this->breakdown_chance = 128; + } } /** @@ -697,6 +743,8 @@ static void AddRearEngineToMultiheadedTr u->refit_cap = v->refit_cap; u->railtype = v->railtype; u->engine_type = v->engine_type; + u->reliability = v->reliability; + u->reliability_spd_dec = v->reliability_spd_dec; u->date_of_last_service = v->date_of_last_service; u->build_year = v->build_year; u->sprite_cache.sprite_seq.Set(SPR_IMG_QUERY); @@ -1936,7 +1984,7 @@ CommandCost CmdReverseTrainDirection(DoC } } else { /* turn the whole train around */ - if ((v->vehstatus & VS_CRASHED) || v->breakdown_ctr != 0) return CMD_ERROR; + if ((v->vehstatus & VS_CRASHED) || HasBit(v->flags, VRF_BREAKDOWN_STOPPED)) return CMD_ERROR; if (flags & DC_EXEC) { /* Properly leave the station if we are loading and won't be loading anymore */ @@ -2816,6 +2864,25 @@ int Train::UpdateSpeed() return this->DoUpdateSpeed(this->GetAcceleration(), this->GetAccelerationStatus() == AS_BRAKE ? 0 : 2, this->GetCurrentMaxSpeed()); } } +/** + * Handle all breakdown related stuff for a train consist. + * @param v The front engine. + */ +static bool HandlePossibleBreakdowns(Train *v) +{ + assert(v->IsFrontEngine()); + for (Train *u = v; u != NULL; u = u->Next()) { + if (u->breakdown_ctr != 0 && (u->IsEngine() || u->IsMultiheaded())) { + if (u->breakdown_ctr <= 2) { + if ( u->HandleBreakdown() ) return true; + /* We check the order of v (the first vehicle) instead of u here! */ + } else if (!v->current_order.IsType(OT_LOADING)) { + u->breakdown_ctr--; + } + } + } + return false; +} /** * Trains enters a station, send out a news item if it is the first train, and start loading. @@ -3691,12 +3758,8 @@ static bool TrainCheckIfLineEnds(Train * { /* First, handle broken down train */ - int t = v->breakdown_ctr; - if (t > 1) { + if(HasBit(v->flags, VRF_BREAKDOWN_BRAKING)) { v->vehstatus |= VS_TRAIN_SLOWING; - - uint16 break_speed = _breakdown_speeds[GB(~t, 4, 4)]; - if (break_speed < v->cur_speed) v->cur_speed = break_speed; } else { v->vehstatus &= ~VS_TRAIN_SLOWING; } @@ -3751,7 +3814,7 @@ static bool TrainLocoHandler(Train *v, b } /* train is broken down? */ - if (v->HandleBreakdown()) return true; + if ( HandlePossibleBreakdowns(v) ) return true; if (HasBit(v->flags, VRF_REVERSING) && v->cur_speed == 0) { ReverseTrainDirection(v); @@ -3892,6 +3955,7 @@ Money Train::GetRunningCost() const if (e->u.rail.running_cost_class == INVALID_PRICE) continue; uint cost_factor = GetVehicleProperty(v, PROP_TRAIN_RUNNING_COST_FACTOR, e->u.rail.running_cost); + cost_factor *= _settings_game.economy.running_cost_multiplier_rail; if (cost_factor == 0) continue; /* Halve running cost for multiheaded parts */ @@ -3986,7 +4050,6 @@ void Train::OnNewDay() if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); if (this->IsFrontEngine()) { - CheckVehicleBreakdown(this); CheckIfTrainNeedsService(this); @@ -4011,6 +4074,9 @@ void Train::OnNewDay() SetWindowClassesDirty(WC_TRAINS_LIST); } } + if(IsEngine() || IsMultiheaded()) { + CheckVehicleBreakdown(this); + } } /** diff --git a/src/train_gui.cpp b/src/train_gui.cpp --- a/src/train_gui.cpp +++ b/src/train_gui.cpp @@ -221,17 +221,65 @@ static void TrainDetailsCargoTab(const C * @param right The right most coordinate to draw * @param y The y coordinate */ -static void TrainDetailsInfoTab(const Vehicle *v, int left, int right, int y) +static void TrainDetailsInfoTab(const Train *v, int left, int right, int y, byte line_number) { - if (RailVehInfo(v->engine_type)->railveh_type == RAILVEH_WAGON) { + const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); + bool show_speed = !UsesWagonOverride(v) && (_settings_game.vehicle.wagon_speed_limits || rvi->railveh_type != RAILVEH_WAGON); + uint16 speed; + + if (rvi->railveh_type == RAILVEH_WAGON) { SetDParam(0, v->engine_type); SetDParam(1, v->value); + + if ( show_speed && (speed = GetVehicleProperty(v, PROP_TRAIN_SPEED, rvi->max_speed))) { + SetDParam(2, speed); // StringID++ + DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE_AND_SPEED); + } else DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE); } else { + switch ( line_number ) { + case 0: SetDParam(0, v->engine_type); SetDParam(1, v->build_year); SetDParam(2, v->value); + + if ( show_speed && (speed = GetVehicleProperty(v, PROP_TRAIN_SPEED, rvi->max_speed))) { + SetDParam(3, speed); // StringID++ + DrawString( left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE_AND_SPEED, TC_FROMSTRING, SA_LEFT); + } else DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE); + + break; + + case 1: + SetDParam(0, v->reliability * 100 >> 16); + SetDParam(1, v->breakdowns_since_last_service); + DrawString(left, right, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS, TC_FROMSTRING, SA_LEFT); + break; + + case 2: + if ( v->breakdown_ctr == 1 ) { + if ( _settings_game.vehicle.improved_breakdowns ) { + SetDParam( 0, STR_VEHICLE_STATUS_BROKEN_DOWN_VEL ); + SetDParam( 1, STR_BREAKDOWN_TYPE_CRITICAL + v->breakdown_type ); + if ( v->breakdown_type == BREAKDOWN_LOW_SPEED ) { + SetDParam( 2, std::min( v->First( )->GetCurrentMaxSpeed( ), v->breakdown_severity ) ); + } else if ( v->breakdown_type == BREAKDOWN_LOW_POWER ) { + SetDParam( 2, v->breakdown_severity * 100 / 256 ); + } + } else + SetDParam( 0, STR_VEHICLE_STATUS_BROKEN_DOWN ); + } else { + if ( HasBit( v->flags, VRF_NEED_REPAIR ) ) { + SetDParam( 0, STR_NEED_REPAIR ); + SetDParam( 1, v->vcache.cached_max_speed ); + } else + SetDParam( 0, STR_RUNNING ); + } + DrawString( left, right, y, STR_CURRENT_STATUS); + break; + default: NOT_REACHED(); + } } } @@ -342,6 +390,7 @@ int GetTrainDetailsWndVScroll(VehicleID uint length = GetLengthOfArticulatedVehicle(v); if (length > TRAIN_DETAILS_MAX_INDENT) num++; } + if (det_tab == 1) num += 2 * Train::Get(veh_id)->tcache.cached_num_engines; } return num; @@ -373,6 +422,7 @@ void DrawTrainDetails(const Train *v, in bool rtl = _current_text_dir == TD_RTL; Direction dir = rtl ? DIR_E : DIR_W; int x = rtl ? right : left; + byte line_number = 0; for (; v != nullptr && vscroll_pos > -vscroll_cap; v = v->GetNextVehicle()) { GetCargoSummaryOfArticulatedVehicle(v, &_cargo_summary); @@ -383,7 +433,7 @@ void DrawTrainDetails(const Train *v, in do { Point offset; int width = u->GetDisplayImageWidth(&offset); - if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) { + if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap && line_number == 0) { int pitch = 0; const Engine *e = Engine::Get(v->engine_type); if (e->GetGRF() != nullptr) { @@ -406,7 +456,7 @@ void DrawTrainDetails(const Train *v, in } uint num_lines = std::max(1u, (unsigned)_cargo_summary.size()); - for (uint i = 0; i < num_lines; i++) { + for (uint i = 0; i < num_lines;) { int sprite_width = std::max(dx, ScaleGUITrad(TRAIN_DETAILS_MIN_INDENT)) + 3; int data_left = left + (rtl ? 0 : sprite_width); int data_right = right - (rtl ? sprite_width : 0); @@ -425,7 +475,7 @@ void DrawTrainDetails(const Train *v, in break; case TDW_TAB_INFO: - if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py); + if (i == 0) TrainDetailsInfoTab(v, data_left, data_right, py, line_number); break; case TDW_TAB_CAPACITY: @@ -440,6 +490,11 @@ void DrawTrainDetails(const Train *v, in default: NOT_REACHED(); } } + if (det_tab != 1 || line_number >= (Train::From(v)->IsWagon() ? 0 : 2)) { + line_number = 0; + i++; + } else + line_number++; vscroll_pos--; } } diff --git a/src/tree_cmd.cpp b/src/tree_cmd.cpp --- a/src/tree_cmd.cpp +++ b/src/tree_cmd.cpp @@ -389,7 +389,7 @@ void GenerateTrees() CommandCost CmdPlantTree(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, byte tree_to_plant) { StringID msg = INVALID_STRING_ID; - CommandCost cost(EXPENSES_OTHER); + CommandCost cost(EXPENSES_T_TREES_CON); if (start_tile >= MapSize()) return CMD_ERROR; /* Check the tree type within the current climate */ @@ -613,7 +613,7 @@ static CommandCost ClearTile_Trees(TileI if (flags & DC_EXEC) DoClearSquare(tile); - return CommandCost(EXPENSES_CONSTRUCTION, num * _price[PR_CLEAR_TREES]); + return CommandCost(EXPENSES_T_DEMOLITION, num * _price[PR_CLEAR_TREES]); } static void GetTileDesc_Trees(TileIndex tile, TileDesc *td) diff --git a/src/tunnelbridge_cmd.cpp b/src/tunnelbridge_cmd.cpp --- a/src/tunnelbridge_cmd.cpp +++ b/src/tunnelbridge_cmd.cpp @@ -263,22 +263,26 @@ CommandCost CmdBuildBridge(DoCommandFlag RailType railtype = INVALID_RAILTYPE; RoadType roadtype = INVALID_ROADTYPE; + ExpensesType expensestype = EXPENSES_CONSTRUCTION; if (!IsValidTile(tile_start)) return_cmd_error(STR_ERROR_BRIDGE_THROUGH_MAP_BORDER); /* type of bridge */ switch (transport_type) { case TRANSPORT_ROAD: + expensestype = EXPENSES_T_ROAD_CON; roadtype = (RoadType)road_rail_type; if (!ValParamRoadType(roadtype)) return CMD_ERROR; break; case TRANSPORT_RAIL: + expensestype = EXPENSES_T_TRAIN_CON; railtype = (RailType)road_rail_type; if (!ValParamRailtype(railtype)) return CMD_ERROR; break; case TRANSPORT_WATER: + expensestype = EXPENSES_T_SHIP_CON; break; default: @@ -336,7 +340,7 @@ CommandCost CmdBuildBridge(DoCommandFlag if (transport_type == TRANSPORT_WATER && (tileh_start == SLOPE_FLAT || tileh_end == SLOPE_FLAT)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); if (z_start != z_end) return_cmd_error(STR_ERROR_BRIDGEHEADS_NOT_SAME_HEIGHT); - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(expensestype); Owner owner; bool is_new_owner; RoadType road_rt = INVALID_ROADTYPE; @@ -414,6 +418,7 @@ CommandCost CmdBuildBridge(DoCommandFlag CommandCost ret = Command::Do(flags, tile_start); if (ret.Failed()) return ret; cost = ret; + cost.expense_type = expensestype; if (terraform_cost_north.Failed() || (terraform_cost_north.GetCost() != 0 && !allow_on_slopes)) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); cost.AddCost(terraform_cost_north); @@ -627,14 +632,17 @@ CommandCost CmdBuildTunnel(DoCommandFlag RailType railtype = INVALID_RAILTYPE; RoadType roadtype = INVALID_ROADTYPE; + ExpensesType expensestype = EXPENSES_CONSTRUCTION; _build_tunnel_endtile = 0; switch (transport_type) { case TRANSPORT_RAIL: + expensestype = EXPENSES_T_TRAIN_CON; railtype = (RailType)road_rail_type; if (!ValParamRailtype(railtype)) return CMD_ERROR; break; case TRANSPORT_ROAD: + expensestype = EXPENSES_T_ROAD_CON; roadtype = (RoadType)road_rail_type; if (!ValParamRoadType(roadtype)) return CMD_ERROR; break; @@ -687,7 +695,7 @@ CommandCost CmdBuildTunnel(DoCommandFlag /* Number of tiles at which the cost increase coefficient per tile is halved */ int tiles_bump = 25; - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(expensestype); Slope end_tileh; for (;;) { end_tile += delta; @@ -913,8 +921,7 @@ static CommandCost DoClearTunnel(TileInd DoClearSquare(endtile); } } - - return CommandCost(EXPENSES_CONSTRUCTION, len * base_cost); + return CommandCost(EXPENSES_T_DEMOLITION, len * base_cost); } @@ -1013,7 +1020,7 @@ static CommandCost DoClearBridge(TileInd } } - return CommandCost(EXPENSES_CONSTRUCTION, len * base_cost); + return CommandCost(EXPENSES_T_DEMOLITION, len * base_cost); } /** diff --git a/src/vehicle.cpp b/src/vehicle.cpp --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -162,14 +162,29 @@ bool Vehicle::NeedsAutorenewing(const Co void VehicleServiceInDepot(Vehicle *v) { assert(v != nullptr); + const Engine *e = Engine::Get(v->engine_type); + if (v->type == VEH_TRAIN) { + if (v->Next() != NULL) VehicleServiceInDepot(v->Next()); + if (!(Train::From(v)->IsEngine()) && !(Train::From(v)->IsRearDualheaded())) return; + ClrBit(Train::From(v)->flags,VRF_NEED_REPAIR); + const RailVehicleInfo *rvi = &e->u.rail; + v->vcache.cached_max_speed = rvi->max_speed; + if (Train::From(v)->IsFrontEngine()) { + Train::From(v)->ConsistChanged(CCF_REFIT); + CLRBITS(Train::From(v)->flags, (1 << VRF_BREAKDOWN_BRAKING) | VRF_IS_BROKEN ); + } + } + v->date_of_last_service = _date; + v->breakdowns_since_last_service = 0; + v->reliability = e->reliability; + v->breakdown_ctr = 0; + v->vehstatus &= ~VS_AIRCRAFT_BROKEN; SetWindowDirty(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated do { v->date_of_last_service = _date; v->breakdowns_since_last_service = 0; v->reliability = v->GetEngine()->reliability; - /* Prevent vehicles from breaking down directly after exiting the depot. */ - v->breakdown_chance /= 4; if (_settings_game.difficulty.vehicle_breakdowns == 1) v->breakdown_chance = 0; // on reduced breakdown v = v->Next(); } while (v != nullptr && v->HasEngineType()); @@ -189,9 +204,10 @@ bool Vehicle::NeedsServicing() const /* Are we ready for the next service cycle? */ const Company *c = Company::Get(this->owner); - if (this->ServiceIntervalIsPercent() ? - (this->reliability >= this->GetEngine()->reliability * (100 - this->GetServiceInterval()) / 100) : - (this->date_of_last_service + this->GetServiceInterval() >= _date)) { + if ((this->ServiceIntervalIsPercent() ? + (this->reliability >= this->GetEngine()->reliability * (100 - this->service_interval) / 100) : + (this->date_of_last_service + this->service_interval >= _date)) + && !(this->type == VEH_TRAIN && HasBit(Train::From(this)->flags ,VRF_NEED_REPAIR))) { return false; } @@ -981,6 +997,16 @@ void CallVehicleTicks() default: break; case VEH_TRAIN: + if (HasBit(Train::From(v)->flags, VRF_TO_HEAVY)) { + _current_company = v->owner; + if (IsLocalCompany()) { + SetDParam(0, v->index); + SetDParam(1, STR_ERROR_TRAIN_TOO_HEAVY); + AddVehicleNewsItem(STR_ERROR_TRAIN_TOO_HEAVY, NT_ADVICE, v->index); + ClrBit(Train::From(v)->flags,VRF_TO_HEAVY); + } + _current_company = OWNER_NONE; + } case VEH_ROAD: case VEH_AIRCRAFT: case VEH_SHIP: { @@ -1253,17 +1279,117 @@ void DecreaseVehicleValue(Vehicle *v) SetWindowDirty(WC_VEHICLE_DETAILS, v->index); } -static const byte _breakdown_chance[64] = { - 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 5, 5, 6, 6, 7, 7, - 8, 8, 9, 9, 10, 10, 11, 11, - 12, 13, 13, 13, 13, 14, 15, 16, - 17, 19, 21, 25, 28, 31, 34, 37, - 40, 44, 48, 52, 56, 60, 64, 68, - 72, 80, 90, 100, 110, 120, 130, 140, - 150, 170, 190, 210, 230, 250, 250, 250, +/** The chances for the different types of vehicles to suffer from different types of breakdowns + * The chance for a given breakdown type n is _breakdown_chances[vehtype][n] - _breakdown_chances[vehtype][n-1] */ +static const byte _breakdown_chances[4][4] = { + { //Trains: + 25, ///< 10% chance for BREAKDOWN_CRITICAL. + 51, ///< 10% chance for BREAKDOWN_EM_STOP. + 127, ///< 30% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 50% chance for BREAKDOWN_LOW_POWER. + }, + { //Road Vehicles: + 51, ///< 20% chance for BREAKDOWN_CRITICAL. + 76, ///< 10% chance for BREAKDOWN_EM_STOP. + 153, ///< 30% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 40% chance for BREAKDOWN_LOW_POWER. + }, + { //Ships: + 51, ///< 20% chance for BREAKDOWN_CRITICAL. + 76, ///< 10% chance for BREAKDOWN_EM_STOP. + 178, ///< 40% chance for BREAKDOWN_LOW_SPEED. + 255, ///< 30% chance for BREAKDOWN_LOW_POWER. + }, + { //Aircraft: + 178, ///< 70% chance for BREAKDOWN_AIRCRAFT_SPEED. + 229, ///< 20% chance for BREAKDOWN_AIRCRAFT_DEPOT. + 255, ///< 10% chance for BREAKDOWN_AIRCRAFT_EM_LANDING. + 255, ///< Aircraft have only 3 breakdown types, so anything above 0% here will cause a crash. + }, }; +/** + * Determine the type of breakdown a vehicle will have. + * Results are saved in breakdown_type and breakdown_severity. + * @param v the vehicle in question. + * @param r the random number to use. (Note that bits 0..6 are already used) + */ +void +DetermineBreakdownType( Vehicle *v, uint32 r ) { + /* if 'improved breakdowns' is off, just do the classic breakdown */ + if ( !_settings_game.vehicle.improved_breakdowns ) { + v->breakdown_type = BREAKDOWN_CRITICAL; + v->breakdown_severity = 40; //only used by aircraft (321 km/h) + return; + } + byte rand = GB( r, 8, 8 ); + const byte *breakdown_type_chance = _breakdown_chances[v->type]; + + if ( v->type == VEH_AIRCRAFT ) { + if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_SPEED] ) { + v->breakdown_type = BREAKDOWN_AIRCRAFT_SPEED; + // RJD: replaced the max and min speeds here with percentages because engine type max speed is 0 in some GRF files. + // the severity is now used as a percentage of max speed rather than the actual allowed max speed + // and is multipled by the current situational max speed in aircraft_cmd.cpp to get a real limit. + // (this hopefully fixes the severity 0 bug) + byte max_speed_percentage = 80; + byte min_speed_percentage = 10; + v->breakdown_severity = min_speed_percentage + ( ( ( v->reliability + GB( r, 16, 16 ) ) * ( max_speed_percentage - min_speed_percentage) ) >> 17 ); + } else if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_DEPOT] ) { + v->breakdown_type = BREAKDOWN_AIRCRAFT_DEPOT; + } else if ( rand <= breakdown_type_chance[BREAKDOWN_AIRCRAFT_EM_LANDING] ) { + /* emergency landings only happen when reliability < 87% */ + if ( v->reliability < 0xDDDD ) { + v->breakdown_type = BREAKDOWN_AIRCRAFT_EM_LANDING; + } else { + /* try again */ + DetermineBreakdownType( v, Random( ) ); + } + } else { + NOT_REACHED( ); + } + return; + } + + if ( rand <= breakdown_type_chance[BREAKDOWN_CRITICAL] ) { + v->breakdown_type = BREAKDOWN_CRITICAL; + } else if ( rand <= breakdown_type_chance[BREAKDOWN_EM_STOP] ) { + /* Non-front engines cannot have emergency stops */ + if ( v->type == VEH_TRAIN && !( Train::From( v )->IsFrontEngine( ) ) ) { + return DetermineBreakdownType( v, Random( ) ); + } + v->breakdown_type = BREAKDOWN_EM_STOP; + v->breakdown_delay >>= 2; //emergency stops don't last long (1/4 of normal) + } else if ( rand <= breakdown_type_chance[BREAKDOWN_LOW_SPEED] ) { + v->breakdown_type = BREAKDOWN_LOW_SPEED; + /* average of random and reliability */ + uint16 rand2 = ( GB( r, 16, 16 ) + v->reliability ) >> 1; + uint16 max_speed = + ( v->type == VEH_TRAIN ) ? + GetVehicleProperty( v, PROP_TRAIN_SPEED, RailVehInfo( v->engine_type )->max_speed ) : + ( v->type == VEH_ROAD ) ? + GetVehicleProperty( v, PROP_ROADVEH_SPEED, RoadVehInfo( v->engine_type )->max_speed ) : + ( v->type == VEH_SHIP ) ? + GetVehicleProperty( v, PROP_SHIP_SPEED, ShipVehInfo( v->engine_type )->max_speed ) : + GetVehicleProperty( v, PROP_AIRCRAFT_SPEED, AircraftVehInfo( v->engine_type )->max_speed ); + byte min_speed = std::min( 41, max_speed >> 2 ); + /* we use the min() function here because we want to use the real value of max_speed for the min_speed calculation */ + max_speed = std::min( max_speed, 255 ); + v->breakdown_severity = Clamp( ( max_speed * rand2 ) >> 16, min_speed, max_speed ); + } else if ( rand <= breakdown_type_chance[BREAKDOWN_LOW_POWER] ) { + v->breakdown_type = BREAKDOWN_LOW_POWER; + /** within this type there are two possibilities: (50/50) + * power reduction (10-90%), or no power at all */ + if ( GB( r, 7, 1 ) ) { + v->breakdown_severity = Clamp( ( GB( r, 16, 16 ) + v->reliability ) >> 9, 26, 231 ); + } else { + v->breakdown_severity = 0; + } + } else { + NOT_REACHED( ); + } +} + void CheckVehicleBreakdown(Vehicle *v) { int rel, rel_old; @@ -1272,34 +1398,42 @@ void CheckVehicleBreakdown(Vehicle *v) if (!_settings_game.order.no_servicing_if_no_breakdowns || _settings_game.difficulty.vehicle_breakdowns != 0) { v->reliability = rel = std::max((rel_old = v->reliability) - v->reliability_spd_dec, 0); - if ((rel_old >> 8) != (rel >> 8)) SetWindowDirty(WC_VEHICLE_DETAILS, v->index); + if ((rel_old >> 8) != (rel >> 8)) SetWindowDirty(WC_VEHICLE_DETAILS, v->First()->index); } - if (v->breakdown_ctr != 0 || (v->vehstatus & VS_STOPPED) || + if (v->breakdown_ctr != 0 || (v->First()->vehstatus & VS_STOPPED) || _settings_game.difficulty.vehicle_breakdowns < 1 || - v->cur_speed < 5 || _game_mode == GM_MENU) { + v->First()->cur_speed < 5 || _game_mode == GM_MENU || + (v->type == VEH_AIRCRAFT && ((Aircraft*)v)->state != FLYING) || + (v->type == VEH_TRAIN && !(Train::From(v)->IsFrontEngine()) && !_settings_game.vehicle.improved_breakdowns)) { return; } - uint32 r = Random(); - - /* increase chance of failure */ - int chance = v->breakdown_chance + 1; - if (Chance16I(1, 25, r)) chance += 25; - v->breakdown_chance = std::min(255, chance); - - /* calculate reliability value to use in comparison */ - rel = v->reliability; - if (v->type == VEH_SHIP) rel += 0x6666; - - /* reduced breakdowns? */ - if (_settings_game.difficulty.vehicle_breakdowns == 1) rel += 0x6666; - - /* check if to break down */ - if (_breakdown_chance[(uint)std::min(rel, 0xffff) >> 10] <= v->breakdown_chance) { - v->breakdown_ctr = GB(r, 16, 6) + 0x3F; - v->breakdown_delay = GB(r, 24, 7) + 0x80; - v->breakdown_chance = 0; + uint32 r1 = Random(); + uint32 r2 = Random(); + uint32 r3 = Random(); + + byte chance = 128; + if (_settings_game.vehicle.improved_breakdowns) { + /* Dual engines have their breakdown chances reduced to 70% of the normal value */ + chance = (v->type == VEH_TRAIN && Train::From(v)->IsMultiheaded()) ? v->First()->breakdown_chance * 7 / 10 : v->First()->breakdown_chance; + } else if(v->type == VEH_SHIP) { + chance = 64; + } + /** + * Chance is (1 - reliability) * breakdown_setting * breakdown_chance / 10. + * At 90% reliabilty, normal setting (2) and average breakdown_chance (128), + * a vehicle will break down (on average) every 100 days. + * This *should* mean that vehicles break down about as often as (or a little less than) they used to. + * However, because breakdowns are no longer by definition a complete stop, + * their impact will be significantly less. + */ + if ( (uint32) ( 0xffff - v->reliability ) * _settings_game.difficulty.vehicle_breakdowns * chance > GB( r1, 0, 24 ) * 10 ) { + if (GB(r3, 0, 24) <= _settings_game.difficulty.vehicle_breakdown_scaler) { // uint32 + v->breakdown_ctr = GB(r1, 24, 6) + 0xF; + v->breakdown_delay = GB(r2, 0, 7) + 0x80; + DetermineBreakdownType(v, r2); + } } } @@ -1328,28 +1462,108 @@ bool Vehicle::HandleBreakdown() } if (this->type == VEH_AIRCRAFT) { + this->MarkDirty(); + assert(this->breakdown_type <= BREAKDOWN_AIRCRAFT_EM_LANDING); /* Aircraft just need this flag, the rest is handled elsewhere */ this->vehstatus |= VS_AIRCRAFT_BROKEN; - } else { - this->cur_speed = 0; - - if (!PlayVehicleSound(this, VSE_BREAKDOWN)) { - bool train_or_ship = this->type == VEH_TRAIN || this->type == VEH_SHIP; - SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? - (train_or_ship ? SND_10_BREAKDOWN_TRAIN_SHIP : SND_0F_BREAKDOWN_ROADVEHICLE) : - (train_or_ship ? SND_3A_BREAKDOWN_TRAIN_SHIP_TOYLAND : SND_35_BREAKDOWN_ROADVEHICLE_TOYLAND), this); + if(this->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED || + (this->current_order.IsType(OT_GOTO_DEPOT) && + (this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN) && + GetTargetAirportIfValid(Aircraft::From(this)) != NULL)) return false; + FindBreakdownDestination(Aircraft::From(this)); + + } else if ( this->type == VEH_TRAIN ) { + if ( this->breakdown_type == BREAKDOWN_LOW_POWER || + this->First( )->cur_speed <= ( ( this->breakdown_type == BREAKDOWN_LOW_SPEED ) ? this->breakdown_severity : 0 ) ) { + switch ( this->breakdown_type ) { + case BREAKDOWN_CRITICAL: + if (!PlayVehicleSound(this, VSE_BREAKDOWN)) { + bool train_or_ship = this->type == VEH_TRAIN || this->type == VEH_SHIP; + SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? + (train_or_ship ? SND_10_BREAKDOWN_TRAIN_SHIP : SND_0F_BREAKDOWN_ROADVEHICLE) : + (train_or_ship ? SND_3A_BREAKDOWN_TRAIN_SHIP_TOYLAND : SND_35_BREAKDOWN_ROADVEHICLE_TOYLAND), this); + } + if (!(this->vehstatus & VS_HIDDEN) && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE) && this->breakdown_delay > 0) { + EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE); + if (u != NULL) u->animation_state = this->breakdown_delay * 2; + } + /* Max Speed reduction*/ + if (_settings_game.vehicle.improved_breakdowns) { + if (!HasBit(Train::From(this)->flags,VRF_NEED_REPAIR)) { + const Engine *e = Engine::Get(this->engine_type); + const RailVehicleInfo *rvi = &e->u.rail; + if (rvi->max_speed > this->vcache.cached_max_speed) + this->vcache.cached_max_speed = rvi->max_speed; + } + /* Setting max speed. The lowest max speed can go is 28 km/h */ + this->vcache.cached_max_speed = + std::max(std::min( + this->vcache.cached_max_speed - (this->vcache.cached_max_speed >> 1) / Train::From(this->First())->tcache.cached_num_engines + 1, + this->vcache.cached_max_speed), 28); + SetBit(Train::From(this)->flags, VRF_NEED_REPAIR); + Train::From(this->First())->ConsistChanged(CCF_TRACK); + } + /* FALL THROUGH */ + case BREAKDOWN_EM_STOP: + CheckBreakdownFlags(Train::From(this->First())); + SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_STOPPED); + break; + case BREAKDOWN_LOW_SPEED: + CheckBreakdownFlags(Train::From(this->First())); + SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_SPEED); + break; + case BREAKDOWN_LOW_POWER: + SetBit(Train::From(this->First())->flags, VRF_BREAKDOWN_POWER); + break; + default: NOT_REACHED(); + } + + this->First()->MarkDirty(); + SetWindowDirty(WC_VEHICLE_VIEW, this->index); + SetWindowDirty(WC_VEHICLE_DETAILS, this->index); + } else { + this->breakdown_ctr = 2; // wait until slowdown (handled by GetAccelerationStatus) + this->breakdowns_since_last_service--; + SetBit(Train::From(this)->flags, VRF_BREAKDOWN_BRAKING); + return false; } - - if (!(this->vehstatus & VS_HIDDEN) && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE)) { - EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE); - if (u != nullptr) u->animation_state = this->breakdown_delay * 2; + if ((!(this->vehstatus & VS_HIDDEN)) && (this->breakdown_type == BREAKDOWN_LOW_SPEED || this->breakdown_type == BREAKDOWN_LOW_POWER) + && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE)) { + EffectVehicle *u = CreateEffectVehicleRel(this, 0, 0, 2, EV_BREAKDOWN_SMOKE); //some grey clouds to indicate a broken engine + if (u != NULL) u->animation_state = 25; } + } else { + switch (this->breakdown_type) { + case BREAKDOWN_CRITICAL: + if (!PlayVehicleSound(this, VSE_BREAKDOWN)) { + SndPlayVehicleFx((_settings_game.game_creation.landscape != LT_TOYLAND) ? SND_0F_BREAKDOWN_ROADVEHICLE : SND_35_BREAKDOWN_ROADVEHICLE_TOYLAND, this); + } + if (!(this->vehstatus & VS_HIDDEN) && !HasBit(EngInfo(this->engine_type)->misc_flags, EF_NO_BREAKDOWN_SMOKE) && this->breakdown_delay > 0) { + EffectVehicle *u = CreateEffectVehicleRel(this, 4, 4, 5, EV_BREAKDOWN_SMOKE); + if (u != NULL) u->animation_state = this->breakdown_delay * 2; + } + /* FALL THROUGH */ + case BREAKDOWN_EM_STOP: + this->cur_speed = 0; + break; + case BREAKDOWN_LOW_SPEED: + case BREAKDOWN_LOW_POWER: + /* do nothing */ + break; + default: NOT_REACHED( ); + } + if ( ( !( this->vehstatus & VS_HIDDEN ) ) && + ( this->breakdown_type == BREAKDOWN_LOW_SPEED || this->breakdown_type == BREAKDOWN_LOW_POWER )) { + /* Some gray clouds to indicate a broken RV */ + EffectVehicle *u = CreateEffectVehicleRel(this, 0, 0, 2, EV_BREAKDOWN_SMOKE); + if (u != NULL) u->animation_state = 25; + } + this->First()->MarkDirty(); + SetWindowDirty(WC_VEHICLE_VIEW, this->index); + SetWindowDirty(WC_VEHICLE_DETAILS, this->index); + return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP ); } - this->MarkDirty(); // Update graphics after speed is zeroed - SetWindowDirty(WC_VEHICLE_VIEW, this->index); - SetWindowDirty(WC_VEHICLE_DETAILS, this->index); - FALLTHROUGH; case 1: /* Aircraft breakdowns end only when arriving at the airport */ @@ -1359,11 +1573,17 @@ bool Vehicle::HandleBreakdown() if ((this->tick_counter & (this->type == VEH_TRAIN ? 3 : 1)) == 0) { if (--this->breakdown_delay == 0) { this->breakdown_ctr = 0; - this->MarkDirty(); - SetWindowDirty(WC_VEHICLE_VIEW, this->index); + if( this->type == VEH_TRAIN ) { + CheckBreakdownFlags(Train::From(this->First())); + this->First()->MarkDirty(); + SetWindowDirty(WC_VEHICLE_VIEW, this->First()->index); + } else { + this->MarkDirty(); + SetWindowDirty(WC_VEHICLE_VIEW, this->index); + } } } - return true; + return (this->breakdown_type == BREAKDOWN_CRITICAL || this->breakdown_type == BREAKDOWN_EM_STOP); default: if (!this->current_order.IsType(OT_LOADING)) this->breakdown_ctr--; @@ -2398,7 +2618,7 @@ CommandCost Vehicle::SendToDepot(DoComma * Now we change the setting to apply the new one and let the vehicle head for the same depot. * Note: the if is (true for requesting service == true for ordered to stop in depot) */ if (flags & DC_EXEC) { - this->current_order.SetDepotOrderType(ODTF_MANUAL); + if (!(this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN)) this->current_order.SetDepotOrderType(ODTF_MANUAL); this->current_order.SetDepotActionType(halt_in_depot ? ODATF_SERVICE_ONLY : ODATFB_HALT); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); } @@ -2416,8 +2636,13 @@ CommandCost Vehicle::SendToDepot(DoComma SetBit(gv_flags, GVF_SUPPRESS_IMPLICIT_ORDERS); } - this->current_order.MakeDummy(); - SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); + /* We don't cancel a breakdown-related goto depot order, we only change whether to halt or not */ + if (this->current_order.GetDepotOrderType() & ODTFB_BREAKDOWN) { + this->current_order.SetDepotActionType(this->current_order.GetDepotActionType() == ODATFB_HALT ? ODATF_SERVICE_ONLY : ODATFB_HALT); + } else { + this->current_order.MakeDummy(); + SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); + } } return CommandCost(); } diff --git a/src/vehicle_base.h b/src/vehicle_base.h --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -266,6 +266,8 @@ public: Vehicle **hash_tile_prev; ///< NOSAVE: Previous vehicle in the tile location hash. Vehicle **hash_tile_current; ///< NOSAVE: Cache of the current hash chain. + byte breakdown_severity; ///< severity of the breakdown. Note that lower means more severe + byte breakdown_type; ///< Type of breakdown SpriteID colourmap; ///< NOSAVE: cached colour mapping /* Related to age and service time */ diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -35,6 +35,7 @@ #include "engine_func.h" #include "station_base.h" #include "tilehighlight_func.h" +#include "train.h" #include "zoom_func.h" #include "depot_cmd.h" #include "vehicle_cmd.h" @@ -382,6 +383,31 @@ byte GetBestFittingSubType(Vehicle *v_fr return ret_refit_cyc; } +/** + * Get the engine that suffers from the most severe breakdown. + * This means the engine with the lowest breakdown_type. + * If the breakdown types of 2 engines are equal, the one with the lowest breakdown_severity (most severe) is picked. + * @param v The front engine of the train. + * @return The most severly broken engine. + */ +const Vehicle *GetMostSeverelyBrokenEngine(const Train *v) +{ + assert(v->IsFrontEngine()); + const Vehicle *w = v; + byte most_severe_type = 255; + for (const Vehicle *u = v; u != NULL; u = u->Next()) { + if (u->breakdown_ctr == 1) { + if (u->breakdown_type < most_severe_type) { + most_severe_type = u->breakdown_type; + w = u; + } else if (u->breakdown_type == most_severe_type && u->breakdown_severity < w->breakdown_severity) { + w = u; + } + } + } + return w; +} + /** Option to refit a vehicle chain */ struct RefitOption { CargoID cargo; ///< Cargo to refit to @@ -1974,14 +2000,14 @@ public: }; static WindowDesc _vehicle_list_other_desc( - WDP_AUTO, "list_vehicles", 260, 246, + WDP_AUTO, "list_vehicles", 360, 246, WC_INVALID, WC_NONE, 0, _nested_vehicle_list, lengthof(_nested_vehicle_list) ); static WindowDesc _vehicle_list_train_desc( - WDP_AUTO, "list_vehicles_train", 325, 246, + WDP_AUTO, "list_vehicles_train", 425, 246, WC_TRAINS_LIST, WC_NONE, 0, _nested_vehicle_list, lengthof(_nested_vehicle_list) @@ -2347,8 +2373,25 @@ struct VehicleDetailsWindow : Window { y += FONT_HEIGHT_NORMAL; /* Draw breakdown & reliability */ - SetDParam(0, ToPercent16(v->reliability)); - SetDParam(1, v->breakdowns_since_last_service); + byte total_engines = 0; + if (v->type == VEH_TRAIN) { + /* we want to draw the average reliability and total number of breakdowns */ + uint32 total_reliability = 0; + uint16 total_breakdowns = 0; + for (const Vehicle *w = v; w != NULL; w = w->Next()) { + if (Train::From(w)->IsEngine() || Train::From(w)->IsMultiheaded()) { + total_reliability += w->reliability; + total_breakdowns += w->breakdowns_since_last_service; + } + } + total_engines = Train::From(v)->tcache.cached_num_engines; + assert(total_engines > 0); + SetDParam(0, ToPercent16(total_reliability / total_engines)); + SetDParam(1, total_breakdowns); + } else { + SetDParam(0, ToPercent16(v->reliability)); + SetDParam(1, v->breakdowns_since_last_service); + } DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS); break; } @@ -2644,6 +2687,13 @@ void StartStopVehicle(const Vehicle *v, Command::Post(_vehicle_msg_translation_table[VCT_CMD_START_STOP][v->type], texteffect ? CcStartStopVehicle : nullptr, v->tile, v->index, false); } +/** Strings for aircraft breakdown types */ +static const StringID _aircraft_breakdown_strings[] = { + STR_BREAKDOWN_TYPE_LOW_SPEED, + STR_BREAKDOWN_TYPE_DEPOT, + STR_BREAKDOWN_TYPE_LANDING, +}; + /** Checks whether the vehicle may be refitted at the moment.*/ static bool IsVehicleRefitable(const Vehicle *v) { @@ -2820,8 +2870,33 @@ public: TextColour text_colour = TC_FROMSTRING; if (v->vehstatus & VS_CRASHED) { str = STR_VEHICLE_STATUS_CRASHED; - } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary? - str = STR_VEHICLE_STATUS_BROKEN_DOWN; + } else if ( v->breakdown_ctr == 1 || ( v->type == VEH_TRAIN && Train::From( v )->flags & VRF_IS_BROKEN ) ) { + if ( _settings_game.vehicle.improved_breakdowns ) { + str = STR_VEHICLE_STATUS_BROKEN_DOWN_VEL; + SetDParam( 2, v->GetDisplaySpeed( ) ); + } else + str = STR_VEHICLE_STATUS_BROKEN_DOWN; + + if ( v->type == VEH_AIRCRAFT ) { + SetDParam( 0, _aircraft_breakdown_strings[v->breakdown_type] ); + if ( v->breakdown_type == BREAKDOWN_AIRCRAFT_SPEED ) { + uint percentage_speed_limit = v->breakdown_severity; + uint cached_max_speed = v->vcache.cached_max_speed; + uint broken_down_max_speed = ((cached_max_speed * percentage_speed_limit) + 99) / 100; + SetDParam( 1, broken_down_max_speed); + } else { + SetDParam( 1, v->current_order.GetDestination( ) ); + } + } else { + const Vehicle *w = ( v->type == VEH_TRAIN ) ? GetMostSeverelyBrokenEngine( Train::From( v ) ) : v; + SetDParam( 0, STR_BREAKDOWN_TYPE_CRITICAL + w->breakdown_type ); + + if ( w->breakdown_type == BREAKDOWN_LOW_SPEED ) { + SetDParam( 1, std::min( w->First()->GetDisplayMaxSpeed( ), w->breakdown_severity >> ( v->type == VEH_TRAIN ? 0 : 1 ) ) ); + } else if ( w->breakdown_type == BREAKDOWN_LOW_POWER ) { + SetDParam( 1, w->breakdown_severity * 100 / 256 ); + } + } } else if (v->vehstatus & VS_STOPPED && (!mouse_over_start_stop || v->IsStoppedInDepot())) { if (v->type == VEH_TRAIN) { if (v->cur_speed == 0) { diff --git a/src/vehicle_type.h b/src/vehicle_type.h --- a/src/vehicle_type.h +++ b/src/vehicle_type.h @@ -71,11 +71,23 @@ enum class DepotCommand : byte { }; DECLARE_ENUM_AS_BIT_SET(DepotCommand) -static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 32; ///< The maximum length of a vehicle name in characters including '\0' +static const uint MAX_LENGTH_VEHICLE_NAME_CHARS = 128; ///< The maximum length of a vehicle name in characters including '\0' /** The length of a vehicle in tile units. */ static const uint VEHICLE_LENGTH = 8; +/* The different types of breakdowns */ +enum BreakdownType { + BREAKDOWN_CRITICAL = 0, ///< Old style breakdown (black smoke) + BREAKDOWN_EM_STOP = 1, ///< Emergency stop + BREAKDOWN_LOW_SPEED = 2, ///< Lower max speed + BREAKDOWN_LOW_POWER = 3, ///< Power reduction + /* Aircraft have totally different breakdowns, so we use aliases to make things clearer */ + BREAKDOWN_AIRCRAFT_SPEED = BREAKDOWN_CRITICAL, ///< Lower speed until the next airport + BREAKDOWN_AIRCRAFT_DEPOT = BREAKDOWN_EM_STOP, ///< We have to visit a depot at the next airport + BREAKDOWN_AIRCRAFT_EM_LANDING = BREAKDOWN_LOW_SPEED, ///< Emergency landing at the closest airport (with hangar!) we can find +}; + /** Vehicle acceleration models. */ enum AccelerationModel { AM_ORIGINAL, diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -212,7 +212,7 @@ bool VideoDriver_Win32Base::MakeWindow(b int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2; char window_title[64]; - seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision); + seprintf(window_title, lastof(window_title), "OpenTTD"); this->main_wnd = CreateWindow(L"OTTD", OTTD2FS(window_title).c_str(), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), this); if (this->main_wnd == nullptr) usererror("CreateWindow failed"); diff --git a/src/viewport.cpp b/src/viewport.cpp --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1407,8 +1407,8 @@ static void ViewportAddKdtreeSigns(DrawP for (const auto *t : towns) { ViewportAddString(dpi, ZOOM_LVL_OUT_16X, &t->cache.sign, - _settings_client.gui.population_in_label ? STR_VIEWPORT_TOWN_POP : STR_VIEWPORT_TOWN, - STR_VIEWPORT_TOWN_TINY_WHITE, STR_VIEWPORT_TOWN_TINY_BLACK, + t->Label(), + t->SmallLabel(), STR_VIEWPORT_TOWN_TINY_BLACK, t->index, t->cache.population); } diff --git a/src/water_cmd.cpp b/src/water_cmd.cpp --- a/src/water_cmd.cpp +++ b/src/water_cmd.cpp @@ -117,7 +117,7 @@ CommandCost CmdBuildShipDepot(DoCommandF WaterClass wc1 = GetWaterClass(tile); WaterClass wc2 = GetWaterClass(tile2); - CommandCost cost = CommandCost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_DEPOT_SHIP]); + CommandCost cost = CommandCost(EXPENSES_T_SHIP_CON, _price[PR_BUILD_DEPOT_SHIP]); bool add_cost = !IsWaterTile(tile); CommandCost ret = Command::Do(flags | DC_AUTO, tile); @@ -282,7 +282,7 @@ static CommandCost RemoveShipDepot(TileI MakeWaterKeepingClass(tile2, GetTileOwner(tile2)); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_DEPOT_SHIP]); + return CommandCost(EXPENSES_T_SHIP_CON, _price[PR_CLEAR_DEPOT_SHIP]); } /** @@ -294,7 +294,7 @@ static CommandCost RemoveShipDepot(TileI */ static CommandCost DoBuildLock(TileIndex tile, DiagDirection dir, DoCommandFlag flags) { - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_SHIP_CON); int delta = TileOffsByDiagDir(dir); CommandCost ret = EnsureNoVehicleOnGround(tile); @@ -403,7 +403,7 @@ static CommandCost RemoveLock(TileIndex MarkCanalsAndRiversAroundDirty(tile + delta); } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_LOCK]); + return CommandCost(EXPENSES_T_SHIP_CON, _price[PR_CLEAR_LOCK]); } /** @@ -449,7 +449,7 @@ CommandCost CmdBuildCanal(DoCommandFlag if (ta.w != 1 && ta.h != 1) return CMD_ERROR; } - CommandCost cost(EXPENSES_CONSTRUCTION); + CommandCost cost(EXPENSES_T_SHIP_CON); std::unique_ptr iter; if (diagonal) { @@ -551,7 +551,7 @@ static CommandCost ClearTile_Water(TileI if (remove) RemoveDockingTile(tile); } - return CommandCost(EXPENSES_CONSTRUCTION, base_cost); + return CommandCost(EXPENSES_T_DEMOLITION, base_cost); } case WATER_TILE_COAST: { @@ -568,9 +568,9 @@ static CommandCost ClearTile_Water(TileI if (remove) RemoveDockingTile(tile); } if (IsSlopeWithOneCornerRaised(slope)) { - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WATER]); + return CommandCost(EXPENSES_T_DEMOLITION, _price[PR_CLEAR_WATER]); } else { - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_ROUGH]); + return CommandCost(EXPENSES_T_DEMOLITION, _price[PR_CLEAR_ROUGH]); } } diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -279,7 +279,7 @@ CommandCost CmdBuildRailWaypoint(DoComma DirtyCompanyInfrastructureWindows(wp->owner); } - return CommandCost(EXPENSES_CONSTRUCTION, count * _price[PR_BUILD_WAYPOINT_RAIL]); + return CommandCost(EXPENSES_T_TRAIN_CON, count * _price[PR_BUILD_WAYPOINT_RAIL]); } /** @@ -299,7 +299,7 @@ CommandCost CmdBuildBuoy(DoCommandFlag f Waypoint *wp = FindDeletedWaypointCloseTo(tile, STR_SV_STNAME_BUOY, OWNER_NONE); if (wp == nullptr && !Waypoint::CanAllocateItem()) return_cmd_error(STR_ERROR_TOO_MANY_STATIONS_LOADING); - CommandCost cost(EXPENSES_CONSTRUCTION, _price[PR_BUILD_WAYPOINT_BUOY]); + CommandCost cost(EXPENSES_T_SHIP_CON, _price[PR_BUILD_WAYPOINT_BUOY]); if (!IsWaterTile(tile)) { CommandCost ret = Command::Do(flags | DC_AUTO, tile); if (ret.Failed()) return ret; @@ -373,7 +373,7 @@ CommandCost RemoveBuoy(TileIndex tile, D wp->delete_ctr = 0; } - return CommandCost(EXPENSES_CONSTRUCTION, _price[PR_CLEAR_WAYPOINT_BUOY]); + return CommandCost(EXPENSES_T_SHIP_CON, _price[PR_CLEAR_WAYPOINT_BUOY]); } /** diff --git a/versiondump.py b/versiondump.py new file mode 100644 --- /dev/null +++ b/versiondump.py @@ -0,0 +1,51 @@ +import subprocess +import os +import datetime +import sys + + +#================================ +# the new version format is MAJORSAVE.MINORSAVE.BUGFIX + +# bump MAJORSAVE every time there is a breaking change to the save format +# bump MINORSAVE every time a save format change would stop save loading in older versions +# bump BUGFIX if save format is untouched +# bumps cause all later numbers to be reset. e.g. if you bump MINORSAVE then BUGFIX goes back to 0. + +# save version to end 'pre' for unreleased builds with the version number being that of the next likely release + +VERSION_TAG = "ginever-6.0.0" +#================================ + + +if sys.platform == "darwin": + hg_path = "/usr/local/bin/hg" + hg_shell = True +else: + hg_path = "hg" + hg_shell = False + +HG_BRANCH = subprocess.Popen(hg_path + " branch", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +HG_REVISION = subprocess.Popen(hg_path + " identify --num", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +HG_CHANGESET = subprocess.Popen(hg_path + " parents --template \"{node}\"", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +HG_CHANGESET_SHORT = subprocess.Popen(hg_path + " parents --template \"{node|short}\"", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +HG_REPO_OWNER = subprocess.Popen(hg_path + " showconfig ui.username", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +IS_TAGGED_VERSION = "pre" not in VERSION_TAG and "+" not in HG_REVISION + +if "+" in HG_REVISION: + HG_CHANGESET = HG_CHANGESET + "+" + HG_CHANGESET_SHORT = HG_CHANGESET_SHORT + "+" + HG_DATE = subprocess.Popen(hg_path + " identify --template \"{date|isodate}\"", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() +else: + HG_DATE = subprocess.Popen(hg_path + " parents --template \"{date|isodate}\"", cwd="src/", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=hg_shell).stdout.read().decode('utf-8').strip() + +if not IS_TAGGED_VERSION: + VERSION_TAG = VERSION_TAG + "-h" + HG_CHANGESET_SHORT + +# write in format VERSION ISODATE MODIFIED HASH ISTAG ISSTABLETAG YEAR +version_file = open('.ottdrev-gpp', 'w') +version_file.write("{}\t{}\t{}\t{}\t{}\t{}\t{}\n".format(VERSION_TAG, HG_DATE[:10].replace("-",""), "4" if "+" in HG_REVISION else "3", HG_CHANGESET, "1" if IS_TAGGED_VERSION else "0", "0", HG_DATE[:4])) +version_file.close() + +print('This is Ginever.net OpenTTD Patchpack hg r' + HG_REVISION + ' (' + HG_CHANGESET + ') in branch ' + HG_BRANCH) +print('This build is being performed by: ' + HG_REPO_OWNER)