1
0
Fork 0
OpenTTD/src/newgrf.cpp

1844 lines
67 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*/
/** @file newgrf.cpp Base of all NewGRF support. */
#include "stdafx.h"
#include "core/backup_type.hpp"
#include "core/container_func.hpp"
#include "debug.h"
#include "fileio_func.h"
#include "engine_func.h"
#include "engine_base.h"
#include "bridge.h"
#include "town.h"
#include "newgrf_engine.h"
#include "newgrf_text.h"
#include "spritecache.h"
#include "currency.h"
#include "landscape.h"
#include "newgrf_badge.h"
#include "newgrf_cargo.h"
#include "newgrf_sound.h"
#include "newgrf_station.h"
#include "industrytype.h"
#include "newgrf_canal.h"
#include "newgrf_townname.h"
#include "newgrf_industries.h"
#include "newgrf_airporttiles.h"
#include "newgrf_airport.h"
#include "newgrf_object.h"
#include "network/core/config.h"
#include "smallmap_gui.h"
#include "genworld.h"
#include "error_func.h"
#include "vehicle_base.h"
#include "road.h"
#include "newgrf_roadstop.h"
#include "newgrf/newgrf_bytereader.h"
#include "newgrf/newgrf_internal_vehicle.h"
#include "newgrf/newgrf_internal.h"
#include "newgrf/newgrf_stringmapping.h"
#include "table/strings.h"
#include "safeguards.h"
/* TTDPatch extended GRF format codec
* (c) Petr Baudis 2004 (GPL'd)
* Changes by Florian octo Forster are (c) by the OpenTTD development team.
*
* Contains portions of documentation by TTDPatch team.
* Thanks especially to Josef Drexler for the documentation as well as a lot
* of help at #tycoon. Also thanks to Michael Blunck for his GRF files which
* served as subject to the initial testing of this codec. */
/** List of all loaded GRF files */
static std::vector<GRFFile> _grf_files;
std::span<const GRFFile> GetAllGRFFiles()
{
return _grf_files;
}
/** Miscellaneous GRF features, set by Action 0x0D, parameter 0x9E */
GrfMiscBits _misc_grf_features{};
/** Indicates which are the newgrf features currently loaded ingame */
GRFLoadedFeatures _loaded_newgrf_features;
GrfProcessingState _cur;
ReferenceThroughBaseContainer<std::vector<GRFTempEngineData>> _gted; ///< Temporary engine data used during NewGRF loading
/**
* Debug() function dedicated to newGRF debugging messages
* Function is essentially the same as Debug(grf, severity, ...) with the
* addition of file:line information when parsing grf files.
* NOTE: for the above reason(s) GrfMsg() should ONLY be used for
* loading/parsing grf files, not for runtime debug messages as there
* is no file information available during that time.
* @param severity debugging severity level, see debug.h
* @param msg the message
*/
void GrfMsgI(int severity, const std::string &msg)
{
Debug(grf, severity, "[{}:{}] {}", _cur.grfconfig->filename, _cur.nfo_line, msg);
}
/**
* Obtain a NewGRF file by its grfID
* @param grfid The grfID to obtain the file for
* @return The file.
*/
GRFFile *GetFileByGRFID(uint32_t grfid)
{
auto it = std::ranges::find(_grf_files, grfid, &GRFFile::grfid);
if (it != std::end(_grf_files)) return &*it;
return nullptr;
}
/**
* Obtain a NewGRF file by its filename
* @param filename The filename to obtain the file for.
* @return The file.
*/
static GRFFile *GetFileByFilename(const std::string &filename)
{
auto it = std::ranges::find(_grf_files, filename, &GRFFile::filename);
if (it != std::end(_grf_files)) return &*it;
return nullptr;
}
/** Reset all NewGRFData that was used only while processing data */
static void ClearTemporaryNewGRFData(GRFFile *gf)
{
gf->labels.clear();
}
/**
* Disable a GRF
* @param message Error message or STR_NULL.
* @param config GRFConfig to disable, nullptr for current.
* @return Error message of the GRF for further customisation.
*/
GRFError *DisableGrf(StringID message, GRFConfig *config)
{
GRFFile *file;
if (config != nullptr) {
file = GetFileByGRFID(config->ident.grfid);
} else {
config = _cur.grfconfig;
file = _cur.grffile;
}
config->status = GCS_DISABLED;
if (file != nullptr) ClearTemporaryNewGRFData(file);
if (config == _cur.grfconfig) _cur.skip_sprites = -1;
if (message == STR_NULL) return nullptr;
config->error = {STR_NEWGRF_ERROR_MSG_FATAL, message};
if (config == _cur.grfconfig) config->error->param_value[0] = _cur.nfo_line;
return &config->error.value();
}
/**
* Disable a static NewGRF when it is influencing another (non-static)
* NewGRF as this could cause desyncs.
*
* We could just tell the NewGRF querying that the file doesn't exist,
* but that might give unwanted results. Disabling the NewGRF gives the
* best result as no NewGRF author can complain about that.
* @param c The NewGRF to disable.
*/
void DisableStaticNewGRFInfluencingNonStaticNewGRFs(GRFConfig &c)
{
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_STATIC_GRF_CAUSES_DESYNC, &c);
error->data = _cur.grfconfig->GetName();
}
static std::map<uint32_t, uint32_t> _grf_id_overrides;
/**
* Set the override for a NewGRF
* @param source_grfid The grfID which wants to override another NewGRF.
* @param target_grfid The grfID which is being overridden.
*/
void SetNewGRFOverride(uint32_t source_grfid, uint32_t target_grfid)
{
if (target_grfid == 0) {
_grf_id_overrides.erase(source_grfid);
GrfMsg(5, "SetNewGRFOverride: Removed override of 0x{:X}", std::byteswap(source_grfid));
} else {
_grf_id_overrides[source_grfid] = target_grfid;
GrfMsg(5, "SetNewGRFOverride: Added override of 0x{:X} to 0x{:X}", std::byteswap(source_grfid), std::byteswap(target_grfid));
}
}
/**
* Get overridden GRF for current GRF if present.
* @return Overridden GRFFile if present, or nullptr.
*/
GRFFile *GetCurrentGRFOverride()
{
auto found = _grf_id_overrides.find(_cur.grffile->grfid);
if (found != std::end(_grf_id_overrides)) {
GRFFile *grffile = GetFileByGRFID(found->second);
if (grffile != nullptr) return grffile;
}
return nullptr;
}
/**
* Returns the engine associated to a certain internal_id, resp. allocates it.
* @param file NewGRF that wants to change the engine.
* @param type Vehicle type.
* @param internal_id Engine ID inside the NewGRF.
* @param static_access If the engine is not present, return nullptr instead of allocating a new engine. (Used for static Action 0x04).
* @return The requested engine.
*/
Engine *GetNewEngine(const GRFFile *file, VehicleType type, uint16_t internal_id, bool static_access)
{
/* Hack for add-on GRFs that need to modify another GRF's engines. This lets
* them use the same engine slots. */
uint32_t scope_grfid = INVALID_GRFID; // If not using dynamic_engines, all newgrfs share their ID range
if (_settings_game.vehicle.dynamic_engines) {
/* If dynamic_engies is enabled, there can be multiple independent ID ranges. */
scope_grfid = file->grfid;
if (auto it = _grf_id_overrides.find(file->grfid); it != std::end(_grf_id_overrides)) {
scope_grfid = it->second;
const GRFFile *grf_match = GetFileByGRFID(scope_grfid);
if (grf_match == nullptr) {
GrfMsg(5, "Tried mapping from GRFID {:x} to {:x} but target is not loaded", std::byteswap(file->grfid), std::byteswap(scope_grfid));
} else {
GrfMsg(5, "Mapping from GRFID {:x} to {:x}", std::byteswap(file->grfid), std::byteswap(scope_grfid));
}
}
/* Check if the engine is registered in the override manager */
EngineID engine = _engine_mngr.GetID(type, internal_id, scope_grfid);
if (engine != EngineID::Invalid()) {
Engine *e = Engine::Get(engine);
if (!e->grf_prop.HasGrfFile()) {
e->grf_prop.SetGRFFile(file);
}
return e;
}
}
/* Check if there is an unreserved slot */
EngineID engine = _engine_mngr.UseUnreservedID(type, internal_id, scope_grfid, static_access);
if (engine != EngineID::Invalid()) {
Engine *e = Engine::Get(engine);
if (!e->grf_prop.HasGrfFile()) {
e->grf_prop.SetGRFFile(file);
GrfMsg(5, "Replaced engine at index {} for GRFID {:x}, type {}, index {}", e->index, std::byteswap(file->grfid), type, internal_id);
}
return e;
}
if (static_access) return nullptr;
if (!Engine::CanAllocateItem()) {
GrfMsg(0, "Can't allocate any more engines");
return nullptr;
}
size_t engine_pool_size = Engine::GetPoolSize();
/* ... it's not, so create a new one based off an existing engine */
Engine *e = new Engine(type, internal_id);
e->grf_prop.SetGRFFile(file);
/* Reserve the engine slot */
_engine_mngr.SetID(type, internal_id, scope_grfid, std::min<uint8_t>(internal_id, _engine_counts[type]), e->index);
if (engine_pool_size != Engine::GetPoolSize()) {
/* Resize temporary engine data ... */
_gted.resize(Engine::GetPoolSize());
}
if (type == VEH_TRAIN) {
_gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label;
}
GrfMsg(5, "Created new engine at index {} for GRFID {:x}, type {}, index {}", e->index, std::byteswap(file->grfid), type, internal_id);
return e;
}
/**
* Return the ID of a new engine
* @param file The NewGRF file providing the engine.
* @param type The Vehicle type.
* @param internal_id NewGRF-internal ID of the engine.
* @return The new EngineID.
* @note depending on the dynamic_engine setting and a possible override
* property the grfID may be unique or overwriting or partially re-defining
* properties of an existing engine.
*/
EngineID GetNewEngineID(const GRFFile *file, VehicleType type, uint16_t internal_id)
{
uint32_t scope_grfid = INVALID_GRFID; // If not using dynamic_engines, all newgrfs share their ID range
if (_settings_game.vehicle.dynamic_engines) {
scope_grfid = file->grfid;
if (auto it = _grf_id_overrides.find(file->grfid); it != std::end(_grf_id_overrides)) {
scope_grfid = it->second;
}
}
return _engine_mngr.GetID(type, internal_id, scope_grfid);
}
/**
* Translate the refit mask. refit_mask is uint32_t as it has not been mapped to CargoTypes.
*/
CargoTypes TranslateRefitMask(uint32_t refit_mask)
{
CargoTypes result = 0;
for (uint8_t bit : SetBitIterator(refit_mask)) {
CargoType cargo = GetCargoTranslation(bit, _cur.grffile, true);
if (IsValidCargoType(cargo)) SetBit(result, cargo);
}
return result;
}
/**
* Converts TTD(P) Base Price pointers into the enum used by OTTD
* See http://wiki.ttdpatch.net/tiki-index.php?page=BaseCosts
* @param base_pointer TTD(P) Base Price Pointer
* @param error_location Function name for grf error messages
* @param[out] index If \a base_pointer is valid, \a index is assigned to the matching price; else it is left unchanged
*/
void ConvertTTDBasePrice(uint32_t base_pointer, const char *error_location, Price *index)
{
/* Special value for 'none' */
if (base_pointer == 0) {
*index = INVALID_PRICE;
return;
}
static const uint32_t start = 0x4B34; ///< Position of first base price
static const uint32_t size = 6; ///< Size of each base price record
if (base_pointer < start || (base_pointer - start) % size != 0 || (base_pointer - start) / size >= PR_END) {
GrfMsg(1, "{}: Unsupported running cost base 0x{:04X}, ignoring", error_location, base_pointer);
return;
}
*index = (Price)((base_pointer - start) / size);
}
/**
* Get the language map associated with a given NewGRF and language.
* @param grfid The NewGRF to get the map for.
* @param language_id The (NewGRF) language ID to get the map for.
* @return The LanguageMap, or nullptr if it couldn't be found.
*/
/* static */ const LanguageMap *LanguageMap::GetLanguageMap(uint32_t grfid, uint8_t language_id)
{
/* LanguageID "MAX_LANG", i.e. 7F is any. This language can't have a gender/case mapping, but has to be handled gracefully. */
const GRFFile *grffile = GetFileByGRFID(grfid);
if (grffile == nullptr) return nullptr;
auto it = grffile->language_map.find(language_id);
if (it == std::end(grffile->language_map)) return nullptr;
return &it->second;
}
/**
* Set the current NewGRF as unsafe for static use
* @note Used during safety scan on unsafe actions.
*/
void GRFUnsafe(ByteReader &)
{
_cur.grfconfig->flags.Set(GRFConfigFlag::Unsafe);
/* Skip remainder of GRF */
_cur.skip_sprites = -1;
}
/** Reset and clear all NewGRFs */
static void ResetNewGRF()
{
_cur.grffile = nullptr;
_grf_files.clear();
/* We store pointers to GRFFiles in many places, so need to ensure that the pointers do not become invalid
* due to vector reallocation. Should not happen due to loading taking place in multiple stages, but
* reserving when the size is known is good practice anyway. */
_grf_files.reserve(_grfconfig.size());
}
/** Clear all NewGRF errors */
static void ResetNewGRFErrors()
{
for (const auto &c : _grfconfig) {
c->error.reset();
}
}
extern void ResetCallbacks(bool final);
extern void ResetGRM();
/**
* Reset all NewGRF loaded data
*/
void ResetNewGRFData()
{
CleanUpStrings();
CleanUpGRFTownNames();
ResetBadges();
/* Copy/reset original engine info data */
SetupEngines();
/* Copy/reset original bridge info data */
ResetBridges();
/* Reset rail type information */
ResetRailTypes();
/* Copy/reset original road type info data */
ResetRoadTypes();
/* Allocate temporary refit/cargo class data */
_gted.resize(Engine::GetPoolSize());
/* Fill rail type label temporary data for default trains */
for (const Engine *e : Engine::IterateType(VEH_TRAIN)) {
_gted[e->index].railtypelabel = GetRailTypeInfo(e->u.rail.railtype)->label;
}
/* Reset GRM reservations */
ResetGRM();
/* Reset generic feature callback lists */
ResetGenericCallbacks();
/* Reset price base data */
ResetPriceBaseMultipliers();
/* Reset the curencies array */
ResetCurrencies();
/* Reset the house array */
ResetHouses();
/* Reset the industries structures*/
ResetIndustries();
/* Reset the objects. */
ObjectClass::Reset();
ResetObjects();
/* Reset station classes */
StationClass::Reset();
/* Reset airport-related structures */
AirportClass::Reset();
AirportSpec::ResetAirports();
AirportTileSpec::ResetAirportTiles();
/* Reset road stop classes */
RoadStopClass::Reset();
/* Reset canal sprite groups and flags */
_water_feature.fill({});
/* Reset the snowline table. */
ClearSnowLine();
/* Reset NewGRF files */
ResetNewGRF();
/* Reset NewGRF errors. */
ResetNewGRFErrors();
/* Set up the default cargo types */
SetupCargoForClimate(_settings_game.game_creation.landscape);
/* Reset misc GRF features and train list display variables */
_misc_grf_features = {};
_loaded_newgrf_features.has_2CC = false;
_loaded_newgrf_features.used_liveries = 1 << LS_DEFAULT;
_loaded_newgrf_features.shore = SHORE_REPLACE_NONE;
_loaded_newgrf_features.tram = TRAMWAY_REPLACE_DEPOT_NONE;
/* Clear all GRF overrides */
_grf_id_overrides.clear();
InitializeSoundPool();
_spritegroup_pool.CleanPool();
ResetCallbacks(false);
}
/**
* Reset NewGRF data which is stored persistently in savegames.
*/
void ResetPersistentNewGRFData()
{
/* Reset override managers */
_engine_mngr.ResetToDefaultMapping();
_house_mngr.ResetMapping();
_industry_mngr.ResetMapping();
_industile_mngr.ResetMapping();
_airport_mngr.ResetMapping();
_airporttile_mngr.ResetMapping();
}
/**
* Get the cargo translation table to use for the given GRF file.
* @param grffile GRF file.
* @returns Readonly cargo translation table to use.
*/
std::span<const CargoLabel> GetCargoTranslationTable(const GRFFile &grffile)
{
/* Always use the translation table if it's installed. */
if (!grffile.cargo_list.empty()) return grffile.cargo_list;
/* Pre-v7 use climate-dependent "slot" table. */
if (grffile.grf_version < 7) return GetClimateDependentCargoTranslationTable();
/* Otherwise use climate-independent "bitnum" table. */
return GetClimateIndependentCargoTranslationTable();
}
/**
* Construct the Cargo Mapping
* @note This is the reverse of a cargo translation table
*/
static void BuildCargoTranslationMap()
{
_cur.grffile->cargo_map.fill(UINT8_MAX);
auto cargo_list = GetCargoTranslationTable(*_cur.grffile);
for (const CargoSpec *cs : CargoSpec::Iterate()) {
/* Check the translation table for this cargo's label */
int idx = find_index(cargo_list, cs->label);
if (idx >= 0) _cur.grffile->cargo_map[cs->Index()] = idx;
}
}
/**
* Prepare loading a NewGRF file with its config
* @param config The NewGRF configuration struct with name, id, parameters and alike.
*/
static void InitNewGRFFile(const GRFConfig &config)
{
GRFFile *newfile = GetFileByFilename(config.filename);
if (newfile != nullptr) {
/* We already loaded it once. */
_cur.grffile = newfile;
return;
}
assert(_grf_files.size() < _grf_files.capacity()); // We must not invalidate pointers.
_cur.grffile = &_grf_files.emplace_back(config);
}
/**
* Constructor for GRFFile
* @param config GRFConfig to copy name, grfid and parameters from.
*/
GRFFile::GRFFile(const GRFConfig &config)
{
this->filename = config.filename;
this->grfid = config.ident.grfid;
/* Initialise local settings to defaults */
this->traininfo_vehicle_pitch = 0;
this->traininfo_vehicle_width = TRAININFO_DEFAULT_VEHICLE_WIDTH;
/* Mark price_base_multipliers as 'not set' */
this->price_base_multipliers.fill(INVALID_PRICE_MODIFIER);
/* Initialise rail type map with default rail types */
this->railtype_map.fill(INVALID_RAILTYPE);
this->railtype_map[0] = RAILTYPE_RAIL;
this->railtype_map[1] = RAILTYPE_ELECTRIC;
this->railtype_map[2] = RAILTYPE_MONO;
this->railtype_map[3] = RAILTYPE_MAGLEV;
/* Initialise road type map with default road types */
this->roadtype_map.fill(INVALID_ROADTYPE);
this->roadtype_map[0] = ROADTYPE_ROAD;
/* Initialise tram type map with default tram types */
this->tramtype_map.fill(INVALID_ROADTYPE);
this->tramtype_map[0] = ROADTYPE_TRAM;
/* Copy the initial parameter list */
this->param = config.param;
}
/* Some compilers get confused about vectors of unique_ptrs. */
GRFFile::GRFFile() = default;
GRFFile::GRFFile(GRFFile &&other) = default;
GRFFile::~GRFFile() = default;
/**
* Find first cargo label that exists and is active from a list of cargo labels.
* @param labels List of cargo labels.
* @returns First cargo label in list that exists, or CT_INVALID if none exist.
*/
static CargoLabel GetActiveCargoLabel(const std::initializer_list<CargoLabel> &labels)
{
for (const CargoLabel &label : labels) {
CargoType cargo_type = GetCargoTypeByLabel(label);
if (cargo_type != INVALID_CARGO) return label;
}
return CT_INVALID;
}
/**
* Get active cargo label from either a cargo label or climate-dependent mixed cargo type.
* @param label Cargo label or climate-dependent mixed cargo type.
* @returns Active cargo label, or CT_INVALID if cargo label is not active.
*/
static CargoLabel GetActiveCargoLabel(const std::variant<CargoLabel, MixedCargoType> &label)
{
struct visitor {
CargoLabel operator()(const CargoLabel &label) { return label; }
CargoLabel operator()(const MixedCargoType &mixed)
{
switch (mixed) {
case MCT_LIVESTOCK_FRUIT: return GetActiveCargoLabel({CT_LIVESTOCK, CT_FRUIT});
case MCT_GRAIN_WHEAT_MAIZE: return GetActiveCargoLabel({CT_GRAIN, CT_WHEAT, CT_MAIZE});
case MCT_VALUABLES_GOLD_DIAMONDS: return GetActiveCargoLabel({CT_VALUABLES, CT_GOLD, CT_DIAMONDS});
default: NOT_REACHED();
}
}
};
return std::visit(visitor{}, label);
}
/**
* Precalculate refit masks from cargo classes for all vehicles.
*/
static void CalculateRefitMasks()
{
CargoTypes original_known_cargoes = 0;
for (CargoType cargo_type = 0; cargo_type != NUM_CARGO; ++cargo_type) {
if (IsDefaultCargo(cargo_type)) SetBit(original_known_cargoes, cargo_type);
}
for (Engine *e : Engine::Iterate()) {
EngineID engine = e->index;
EngineInfo *ei = &e->info;
bool only_defaultcargo; ///< Set if the vehicle shall carry only the default cargo
/* Apply default cargo translation map if cargo type hasn't been set, either explicitly or by aircraft cargo handling. */
if (!IsValidCargoType(e->info.cargo_type)) {
e->info.cargo_type = GetCargoTypeByLabel(GetActiveCargoLabel(e->info.cargo_label));
}
/* If the NewGRF did not set any cargo properties, we apply default values. */
if (_gted[engine].defaultcargo_grf == nullptr) {
/* If the vehicle has any capacity, apply the default refit masks */
if (e->type != VEH_TRAIN || e->u.rail.capacity != 0) {
static constexpr LandscapeType T = LandscapeType::Temperate;
static constexpr LandscapeType A = LandscapeType::Arctic;
static constexpr LandscapeType S = LandscapeType::Tropic;
static constexpr LandscapeType Y = LandscapeType::Toyland;
static const struct DefaultRefitMasks {
LandscapeTypes climate;
CargoLabel cargo_label;
CargoClasses cargo_allowed;
CargoClasses cargo_disallowed;
} _default_refit_masks[] = {
{{T, A, S, Y}, CT_PASSENGERS, {CargoClass::Passengers}, {}},
{{T, A, S }, CT_MAIL, {CargoClass::Mail}, {}},
{{T, A, S }, CT_VALUABLES, {CargoClass::Armoured}, {CargoClass::Liquid}},
{{ Y}, CT_MAIL, {CargoClass::Mail, CargoClass::Armoured}, {CargoClass::Liquid}},
{{T, A }, CT_COAL, {CargoClass::Bulk}, {}},
{{ S }, CT_COPPER_ORE, {CargoClass::Bulk}, {}},
{{ Y}, CT_SUGAR, {CargoClass::Bulk}, {}},
{{T, A, S }, CT_OIL, {CargoClass::Liquid}, {}},
{{ Y}, CT_COLA, {CargoClass::Liquid}, {}},
{{T }, CT_GOODS, {CargoClass::PieceGoods, CargoClass::Express}, {CargoClass::Liquid, CargoClass::Passengers}},
{{ A, S }, CT_GOODS, {CargoClass::PieceGoods, CargoClass::Express}, {CargoClass::Liquid, CargoClass::Passengers, CargoClass::Refrigerated}},
{{ A, S }, CT_FOOD, {CargoClass::Refrigerated}, {}},
{{ Y}, CT_CANDY, {CargoClass::PieceGoods, CargoClass::Express}, {CargoClass::Liquid, CargoClass::Passengers}},
};
if (e->type == VEH_AIRCRAFT) {
/* Aircraft default to "light" cargoes */
_gted[engine].cargo_allowed = {CargoClass::Passengers, CargoClass::Mail, CargoClass::Armoured, CargoClass::Express};
_gted[engine].cargo_disallowed = {CargoClass::Liquid};
} else if (e->type == VEH_SHIP) {
CargoLabel label = GetActiveCargoLabel(ei->cargo_label);
switch (label.base()) {
case CT_PASSENGERS.base():
/* Ferries */
_gted[engine].cargo_allowed = {CargoClass::Passengers};
_gted[engine].cargo_disallowed = {};
break;
case CT_OIL.base():
/* Tankers */
_gted[engine].cargo_allowed = {CargoClass::Liquid};
_gted[engine].cargo_disallowed = {};
break;
default:
/* Cargo ships */
if (_settings_game.game_creation.landscape == LandscapeType::Toyland) {
/* No tanker in toyland :( */
_gted[engine].cargo_allowed = {CargoClass::Mail, CargoClass::Armoured, CargoClass::Express, CargoClass::Bulk, CargoClass::PieceGoods, CargoClass::Liquid};
_gted[engine].cargo_disallowed = {CargoClass::Passengers};
} else {
_gted[engine].cargo_allowed = {CargoClass::Mail, CargoClass::Armoured, CargoClass::Express, CargoClass::Bulk, CargoClass::PieceGoods};
_gted[engine].cargo_disallowed = {CargoClass::Liquid, CargoClass::Passengers};
}
break;
}
e->u.ship.old_refittable = true;
} else if (e->type == VEH_TRAIN && e->u.rail.railveh_type != RAILVEH_WAGON) {
/* Train engines default to all cargoes, so you can build single-cargo consists with fast engines.
* Trains loading multiple cargoes may start stations accepting unwanted cargoes. */
_gted[engine].cargo_allowed = {CargoClass::Passengers, CargoClass::Mail, CargoClass::Armoured, CargoClass::Express, CargoClass::Bulk, CargoClass::PieceGoods, CargoClass::Liquid};
_gted[engine].cargo_disallowed = {};
} else {
/* Train wagons and road vehicles are classified by their default cargo type */
CargoLabel label = GetActiveCargoLabel(ei->cargo_label);
for (const auto &drm : _default_refit_masks) {
if (!drm.climate.Test(_settings_game.game_creation.landscape)) continue;
if (drm.cargo_label != label) continue;
_gted[engine].cargo_allowed = drm.cargo_allowed;
_gted[engine].cargo_disallowed = drm.cargo_disallowed;
break;
}
/* All original cargoes have specialised vehicles, so exclude them */
_gted[engine].ctt_exclude_mask = original_known_cargoes;
}
}
_gted[engine].UpdateRefittability(_gted[engine].cargo_allowed.Any());
if (IsValidCargoType(ei->cargo_type)) ClrBit(_gted[engine].ctt_exclude_mask, ei->cargo_type);
}
/* Compute refittability */
{
CargoTypes mask = 0;
CargoTypes not_mask = 0;
CargoTypes xor_mask = ei->refit_mask;
/* If the original masks set by the grf are zero, the vehicle shall only carry the default cargo.
* Note: After applying the translations, the vehicle may end up carrying no defined cargo. It becomes unavailable in that case. */
only_defaultcargo = _gted[engine].refittability != GRFTempEngineData::NONEMPTY;
if (_gted[engine].cargo_allowed.Any()) {
/* Build up the list of cargo types from the set cargo classes. */
for (const CargoSpec *cs : CargoSpec::Iterate()) {
if (cs->classes.Any(_gted[engine].cargo_allowed) && cs->classes.All(_gted[engine].cargo_allowed_required)) SetBit(mask, cs->Index());
if (cs->classes.Any(_gted[engine].cargo_disallowed)) SetBit(not_mask, cs->Index());
}
}
ei->refit_mask = ((mask & ~not_mask) ^ xor_mask) & _cargo_mask;
/* Apply explicit refit includes/excludes. */
ei->refit_mask |= _gted[engine].ctt_include_mask;
ei->refit_mask &= ~_gted[engine].ctt_exclude_mask;
/* Custom refit mask callback. */
const GRFFile *file = _gted[e->index].defaultcargo_grf;
if (file == nullptr) file = e->GetGRF();
if (file != nullptr && e->info.callback_mask.Test(VehicleCallbackMask::CustomRefit)) {
for (const CargoSpec *cs : CargoSpec::Iterate()) {
uint8_t local_slot = file->cargo_map[cs->Index()];
uint16_t callback = GetVehicleCallback(CBID_VEHICLE_CUSTOM_REFIT, cs->classes.base(), local_slot, engine, nullptr);
switch (callback) {
case CALLBACK_FAILED:
case 0:
break; // Do nothing.
case 1: SetBit(ei->refit_mask, cs->Index()); break;
case 2: ClrBit(ei->refit_mask, cs->Index()); break;
default: ErrorUnknownCallbackResult(file->grfid, CBID_VEHICLE_CUSTOM_REFIT, callback);
}
}
}
}
/* Clear invalid cargoslots (from default vehicles or pre-NewCargo GRFs) */
if (IsValidCargoType(ei->cargo_type) && !HasBit(_cargo_mask, ei->cargo_type)) ei->cargo_type = INVALID_CARGO;
/* Ensure that the vehicle is either not refittable, or that the default cargo is one of the refittable cargoes.
* Note: Vehicles refittable to no cargo are handle differently to vehicle refittable to a single cargo. The latter might have subtypes. */
if (!only_defaultcargo && (e->type != VEH_SHIP || e->u.ship.old_refittable) && IsValidCargoType(ei->cargo_type) && !HasBit(ei->refit_mask, ei->cargo_type)) {
ei->cargo_type = INVALID_CARGO;
}
/* Check if this engine's cargo type is valid. If not, set to the first refittable
* cargo type. Finally disable the vehicle, if there is still no cargo. */
if (!IsValidCargoType(ei->cargo_type) && ei->refit_mask != 0) {
/* Figure out which CTT to use for the default cargo, if it is 'first refittable'. */
const GRFFile *file = _gted[engine].defaultcargo_grf;
if (file == nullptr) file = e->GetGRF();
if (file != nullptr && file->grf_version >= 8 && !file->cargo_list.empty()) {
/* Use first refittable cargo from cargo translation table */
uint8_t best_local_slot = UINT8_MAX;
for (CargoType cargo_type : SetCargoBitIterator(ei->refit_mask)) {
uint8_t local_slot = file->cargo_map[cargo_type];
if (local_slot < best_local_slot) {
best_local_slot = local_slot;
ei->cargo_type = cargo_type;
}
}
}
if (!IsValidCargoType(ei->cargo_type)) {
/* Use first refittable cargo slot */
ei->cargo_type = (CargoType)FindFirstBit(ei->refit_mask);
}
}
if (!IsValidCargoType(ei->cargo_type) && e->type == VEH_TRAIN && e->u.rail.railveh_type != RAILVEH_WAGON && e->u.rail.capacity == 0) {
/* For train engines which do not carry cargo it does not matter if their cargo type is invalid.
* Fallback to the first available instead, if the cargo type has not been changed (as indicated by
* cargo_label not being CT_INVALID). */
if (GetActiveCargoLabel(ei->cargo_label) != CT_INVALID) {
ei->cargo_type = static_cast<CargoType>(FindFirstBit(_standard_cargo_mask));
}
}
if (!IsValidCargoType(ei->cargo_type)) ei->climates = {};
/* Clear refit_mask for not refittable ships */
if (e->type == VEH_SHIP && !e->u.ship.old_refittable) {
ei->refit_mask = 0;
}
}
}
/** Set to use the correct action0 properties for each canal feature */
static void FinaliseCanals()
{
for (uint i = 0; i < CF_END; i++) {
if (_water_feature[i].grffile != nullptr) {
_water_feature[i].callback_mask = _water_feature[i].grffile->canal_local_properties[i].callback_mask;
_water_feature[i].flags = _water_feature[i].grffile->canal_local_properties[i].flags;
}
}
}
/** Check for invalid engines */
static void FinaliseEngineArray()
{
for (Engine *e : Engine::Iterate()) {
if (e->GetGRF() == nullptr) {
auto found = std::ranges::find(_engine_mngr.mappings[e->type], e->index, &EngineIDMapping::engine);
if (found == std::end(_engine_mngr.mappings[e->type]) || found->grfid != INVALID_GRFID || found->internal_id != found->substitute_id) {
e->info.string_id = STR_NEWGRF_INVALID_ENGINE;
}
}
/* Do final mapping on variant engine ID. */
if (e->info.variant_id != EngineID::Invalid()) {
e->info.variant_id = GetNewEngineID(e->grf_prop.grffile, e->type, e->info.variant_id.base());
}
if (!e->info.climates.Test(_settings_game.game_creation.landscape)) continue;
switch (e->type) {
case VEH_TRAIN: AppendCopyableBadgeList(e->badges, GetRailTypeInfo(e->u.rail.railtype)->badges, GSF_TRAINS); break;
case VEH_ROAD: AppendCopyableBadgeList(e->badges, GetRoadTypeInfo(e->u.road.roadtype)->badges, GSF_ROADVEHICLES); break;
default: break;
}
/* Skip wagons, there livery is defined via the engine */
if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
LiveryScheme ls = GetEngineLiveryScheme(e->index, EngineID::Invalid(), nullptr);
SetBit(_loaded_newgrf_features.used_liveries, ls);
/* Note: For ships and roadvehicles we assume that they cannot be refitted between passenger and freight */
if (e->type == VEH_TRAIN) {
SetBit(_loaded_newgrf_features.used_liveries, LS_FREIGHT_WAGON);
switch (ls) {
case LS_STEAM:
case LS_DIESEL:
case LS_ELECTRIC:
case LS_MONORAIL:
case LS_MAGLEV:
SetBit(_loaded_newgrf_features.used_liveries, LS_PASSENGER_WAGON_STEAM + ls - LS_STEAM);
break;
case LS_DMU:
case LS_EMU:
SetBit(_loaded_newgrf_features.used_liveries, LS_PASSENGER_WAGON_DIESEL + ls - LS_DMU);
break;
default: NOT_REACHED();
}
}
}
}
/* Check engine variants don't point back on themselves (either directly or via a loop) then set appropriate flags
* on variant engine. This is performed separately as all variant engines need to have been resolved. */
for (Engine *e : Engine::Iterate()) {
EngineID parent = e->info.variant_id;
while (parent != EngineID::Invalid()) {
parent = Engine::Get(parent)->info.variant_id;
if (parent != e->index) continue;
/* Engine looped back on itself, so clear the variant. */
e->info.variant_id = EngineID::Invalid();
GrfMsg(1, "FinaliseEngineArray: Variant of engine {:x} in '{}' loops back on itself", e->grf_prop.local_id, e->GetGRF()->filename);
break;
}
if (e->info.variant_id != EngineID::Invalid()) {
Engine::Get(e->info.variant_id)->display_flags.Set(EngineDisplayFlag::HasVariants).Set(EngineDisplayFlag::IsFolded);
}
}
}
/** Check for invalid cargoes */
void FinaliseCargoArray()
{
for (CargoSpec &cs : CargoSpec::array) {
if (cs.town_production_effect == INVALID_TPE) {
/* Set default town production effect by cargo label. */
switch (cs.label.base()) {
case CT_PASSENGERS.base(): cs.town_production_effect = TPE_PASSENGERS; break;
case CT_MAIL.base(): cs.town_production_effect = TPE_MAIL; break;
default: cs.town_production_effect = TPE_NONE; break;
}
}
if (!cs.IsValid()) {
cs.name = cs.name_single = cs.units_volume = STR_NEWGRF_INVALID_CARGO;
cs.quantifier = STR_NEWGRF_INVALID_CARGO_QUANTITY;
cs.abbrev = STR_NEWGRF_INVALID_CARGO_ABBREV;
}
}
}
/**
* Check if a given housespec is valid and disable it if it's not.
* The housespecs that follow it are used to check the validity of
* multitile houses.
* @param hs The housespec to check.
* @param next1 The housespec that follows \c hs.
* @param next2 The housespec that follows \c next1.
* @param next3 The housespec that follows \c next2.
* @param filename The filename of the newgrf this house was defined in.
* @return Whether the given housespec is valid.
*/
static bool IsHouseSpecValid(HouseSpec *hs, const HouseSpec *next1, const HouseSpec *next2, const HouseSpec *next3, const std::string &filename)
{
if ((hs->building_flags.Any(BUILDING_HAS_2_TILES) &&
(next1 == nullptr || !next1->enabled || next1->building_flags.Any(BUILDING_HAS_1_TILE))) ||
(hs->building_flags.Any(BUILDING_HAS_4_TILES) &&
(next2 == nullptr || !next2->enabled || next2->building_flags.Any(BUILDING_HAS_1_TILE) ||
next3 == nullptr || !next3->enabled || next3->building_flags.Any(BUILDING_HAS_1_TILE)))) {
hs->enabled = false;
if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} as multitile, but no suitable tiles follow. Disabling house.", filename, hs->grf_prop.local_id);
return false;
}
/* Some places sum population by only counting north tiles. Other places use all tiles causing desyncs.
* As the newgrf specs define population to be zero for non-north tiles, we just disable the offending house.
* If you want to allow non-zero populations somewhen, make sure to sum the population of all tiles in all places. */
if ((hs->building_flags.Any(BUILDING_HAS_2_TILES) && next1->population != 0) ||
(hs->building_flags.Any(BUILDING_HAS_4_TILES) && (next2->population != 0 || next3->population != 0))) {
hs->enabled = false;
if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines multitile house {} with non-zero population on additional tiles. Disabling house.", filename, hs->grf_prop.local_id);
return false;
}
/* Substitute type is also used for override, and having an override with a different size causes crashes.
* This check should only be done for NewGRF houses because grf_prop.subst_id is not set for original houses.*/
if (!filename.empty() && (hs->building_flags & BUILDING_HAS_1_TILE) != (HouseSpec::Get(hs->grf_prop.subst_id)->building_flags & BUILDING_HAS_1_TILE)) {
hs->enabled = false;
Debug(grf, 1, "FinaliseHouseArray: {} defines house {} with different house size then it's substitute type. Disabling house.", filename, hs->grf_prop.local_id);
return false;
}
/* Make sure that additional parts of multitile houses are not available. */
if (!hs->building_flags.Any(BUILDING_HAS_1_TILE) && (hs->building_availability & HZ_ZONALL) != 0 && (hs->building_availability & HZ_CLIMALL) != 0) {
hs->enabled = false;
if (!filename.empty()) Debug(grf, 1, "FinaliseHouseArray: {} defines house {} without a size but marked it as available. Disabling house.", filename, hs->grf_prop.local_id);
return false;
}
return true;
}
/**
* Make sure there is at least one house available in the year 0 for the given
* climate / housezone combination.
* @param bitmask The climate and housezone to check for. Exactly one climate
* bit and one housezone bit should be set.
*/
static void EnsureEarlyHouse(HouseZones bitmask)
{
TimerGameCalendar::Year min_year = CalendarTime::MAX_YEAR;
for (const auto &hs : HouseSpec::Specs()) {
if (!hs.enabled) continue;
if ((hs.building_availability & bitmask) != bitmask) continue;
if (hs.min_year < min_year) min_year = hs.min_year;
}
if (min_year == 0) return;
for (auto &hs : HouseSpec::Specs()) {
if (!hs.enabled) continue;
if ((hs.building_availability & bitmask) != bitmask) continue;
if (hs.min_year == min_year) hs.min_year = CalendarTime::MIN_YEAR;
}
}
/**
* Add all new houses to the house array. House properties can be set at any
* time in the GRF file, so we can only add a house spec to the house array
* after the file has finished loading. We also need to check the dates, due to
* the TTDPatch behaviour described below that we need to emulate.
*/
static void FinaliseHouseArray()
{
/* If there are no houses with start dates before 1930, then all houses
* with start dates of 1930 have them reset to 0. This is in order to be
* compatible with TTDPatch, where if no houses have start dates before
* 1930 and the date is before 1930, the game pretends that this is 1930.
* If there have been any houses defined with start dates before 1930 then
* the dates are left alone.
* On the other hand, why 1930? Just 'fix' the houses with the lowest
* minimum introduction date to 0.
*/
for (const auto &file : _grf_files) {
if (file.housespec.empty()) continue;
size_t num_houses = file.housespec.size();
for (size_t i = 0; i < num_houses; i++) {
HouseSpec *hs = file.housespec[i].get();
if (hs == nullptr) continue;
const HouseSpec *next1 = (i + 1 < num_houses ? file.housespec[i + 1].get() : nullptr);
const HouseSpec *next2 = (i + 2 < num_houses ? file.housespec[i + 2].get() : nullptr);
const HouseSpec *next3 = (i + 3 < num_houses ? file.housespec[i + 3].get() : nullptr);
if (!IsHouseSpecValid(hs, next1, next2, next3, file.filename)) continue;
_house_mngr.SetEntitySpec(hs);
}
}
for (size_t i = 0; i < HouseSpec::Specs().size(); i++) {
HouseSpec *hs = HouseSpec::Get(i);
const HouseSpec *next1 = (i + 1 < NUM_HOUSES ? HouseSpec::Get(i + 1) : nullptr);
const HouseSpec *next2 = (i + 2 < NUM_HOUSES ? HouseSpec::Get(i + 2) : nullptr);
const HouseSpec *next3 = (i + 3 < NUM_HOUSES ? HouseSpec::Get(i + 3) : nullptr);
/* We need to check all houses again to we are sure that multitile houses
* did get consecutive IDs and none of the parts are missing. */
if (!IsHouseSpecValid(hs, next1, next2, next3, std::string{})) {
/* GetHouseNorthPart checks 3 houses that are directly before
* it in the house pool. If any of those houses have multi-tile
* flags set it assumes it's part of a multitile house. Since
* we can have invalid houses in the pool marked as disabled, we
* don't want to have them influencing valid tiles. As such set
* building_flags to zero here to make sure any house following
* this one in the pool is properly handled as 1x1 house. */
hs->building_flags = {};
}
/* Apply default cargo translation map for unset cargo slots */
for (uint i = 0; i < lengthof(hs->accepts_cargo_label); ++i) {
if (!IsValidCargoType(hs->accepts_cargo[i])) hs->accepts_cargo[i] = GetCargoTypeByLabel(hs->accepts_cargo_label[i]);
/* Disable acceptance if cargo type is invalid. */
if (!IsValidCargoType(hs->accepts_cargo[i])) hs->cargo_acceptance[i] = 0;
}
}
HouseZones climate_mask = (HouseZones)(1 << (to_underlying(_settings_game.game_creation.landscape) + 12));
EnsureEarlyHouse(HZ_ZON1 | climate_mask);
EnsureEarlyHouse(HZ_ZON2 | climate_mask);
EnsureEarlyHouse(HZ_ZON3 | climate_mask);
EnsureEarlyHouse(HZ_ZON4 | climate_mask);
EnsureEarlyHouse(HZ_ZON5 | climate_mask);
if (_settings_game.game_creation.landscape == LandscapeType::Arctic) {
EnsureEarlyHouse(HZ_ZON1 | HZ_SUBARTC_ABOVE);
EnsureEarlyHouse(HZ_ZON2 | HZ_SUBARTC_ABOVE);
EnsureEarlyHouse(HZ_ZON3 | HZ_SUBARTC_ABOVE);
EnsureEarlyHouse(HZ_ZON4 | HZ_SUBARTC_ABOVE);
EnsureEarlyHouse(HZ_ZON5 | HZ_SUBARTC_ABOVE);
}
}
/**
* Add all new industries to the industry array. Industry properties can be set at any
* time in the GRF file, so we can only add a industry spec to the industry array
* after the file has finished loading.
*/
static void FinaliseIndustriesArray()
{
for (const auto &file : _grf_files) {
for (const auto &indsp : file.industryspec) {
if (indsp == nullptr || !indsp->enabled) continue;
_industry_mngr.SetEntitySpec(indsp.get());
}
for (const auto &indtsp : file.indtspec) {
if (indtsp != nullptr) {
_industile_mngr.SetEntitySpec(indtsp.get());
}
}
}
for (auto &indsp : _industry_specs) {
if (indsp.enabled && indsp.grf_prop.HasGrfFile()) {
for (auto &conflicting : indsp.conflicting) {
conflicting = MapNewGRFIndustryType(conflicting, indsp.grf_prop.grfid);
}
}
if (!indsp.enabled) {
indsp.name = STR_NEWGRF_INVALID_INDUSTRYTYPE;
}
/* Apply default cargo translation map for unset cargo slots */
for (size_t i = 0; i < std::size(indsp.produced_cargo_label); ++i) {
if (!IsValidCargoType(indsp.produced_cargo[i])) indsp.produced_cargo[i] = GetCargoTypeByLabel(GetActiveCargoLabel(indsp.produced_cargo_label[i]));
}
for (size_t i = 0; i < std::size(indsp.accepts_cargo_label); ++i) {
if (!IsValidCargoType(indsp.accepts_cargo[i])) indsp.accepts_cargo[i] = GetCargoTypeByLabel(GetActiveCargoLabel(indsp.accepts_cargo_label[i]));
}
}
for (auto &indtsp : _industry_tile_specs) {
/* Apply default cargo translation map for unset cargo slots */
for (size_t i = 0; i < std::size(indtsp.accepts_cargo_label); ++i) {
if (!IsValidCargoType(indtsp.accepts_cargo[i])) indtsp.accepts_cargo[i] = GetCargoTypeByLabel(GetActiveCargoLabel(indtsp.accepts_cargo_label[i]));
}
}
}
/**
* Add all new objects to the object array. Object properties can be set at any
* time in the GRF file, so we can only add an object spec to the object array
* after the file has finished loading.
*/
static void FinaliseObjectsArray()
{
for (const auto &file : _grf_files) {
for (auto &objectspec : file.objectspec) {
if (objectspec != nullptr && objectspec->grf_prop.HasGrfFile() && objectspec->IsEnabled()) {
_object_mngr.SetEntitySpec(objectspec.get());
}
}
}
ObjectSpec::BindToClasses();
}
/**
* Add all new airports to the airport array. Airport properties can be set at any
* time in the GRF file, so we can only add a airport spec to the airport array
* after the file has finished loading.
*/
static void FinaliseAirportsArray()
{
for (const auto &file : _grf_files) {
for (auto &as : file.airportspec) {
if (as != nullptr && as->enabled) {
_airport_mngr.SetEntitySpec(as.get());
}
}
for (auto &ats : file.airtspec) {
if (ats != nullptr && ats->enabled) {
_airporttile_mngr.SetEntitySpec(ats.get());
}
}
}
}
/** Helper class to invoke a GrfActionHandler. */
struct InvokeGrfActionHandler {
template <uint8_t TAction>
static void Invoke(ByteReader &buf, GrfLoadingStage stage)
{
switch (stage) {
case GLS_FILESCAN: GrfActionHandler<TAction>::FileScan(buf); break;
case GLS_SAFETYSCAN: GrfActionHandler<TAction>::SafetyScan(buf); break;
case GLS_LABELSCAN: GrfActionHandler<TAction>::LabelScan(buf); break;
case GLS_INIT: GrfActionHandler<TAction>::Init(buf); break;
case GLS_RESERVE: GrfActionHandler<TAction>::Reserve(buf); break;
case GLS_ACTIVATION: GrfActionHandler<TAction>::Activation(buf); break;
default: NOT_REACHED();
}
}
using Invoker = void(*)(ByteReader &buf, GrfLoadingStage stage);
static constexpr Invoker funcs[] = { // Must be listed in action order.
Invoke<0x00>, Invoke<0x01>, Invoke<0x02>, Invoke<0x03>, Invoke<0x04>, Invoke<0x05>, Invoke<0x06>, Invoke<0x07>,
Invoke<0x08>, Invoke<0x09>, Invoke<0x0A>, Invoke<0x0B>, Invoke<0x0C>, Invoke<0x0D>, Invoke<0x0E>, Invoke<0x0F>,
Invoke<0x10>, Invoke<0x11>, Invoke<0x12>, Invoke<0x13>, Invoke<0x14>,
};
static void Invoke(uint8_t action, GrfLoadingStage stage, ByteReader &buf)
{
Invoker func = action < std::size(funcs) ? funcs[action] : nullptr;
if (func == nullptr) {
GrfMsg(7, "DecodeSpecialSprite: Skipping unknown action 0x{:02X}", action);
} else {
GrfMsg(7, "DecodeSpecialSprite: Handling action 0x{:02X} in stage {}", action, stage);
func(buf, stage);
}
}
};
/* Here we perform initial decoding of some special sprites (as are they
* described at http://www.ttdpatch.net/src/newgrf.txt, but this is only a very
* partial implementation yet).
* XXX: We consider GRF files trusted. It would be trivial to exploit OTTD by
* a crafted invalid GRF file. We should tell that to the user somehow, or
* better make this more robust in the future. */
static void DecodeSpecialSprite(uint8_t *buf, uint num, GrfLoadingStage stage)
{
auto it = _grf_line_to_action6_sprite_override.find({_cur.grfconfig->ident.grfid, _cur.nfo_line});
if (it == _grf_line_to_action6_sprite_override.end()) {
/* No preloaded sprite to work with; read the
* pseudo sprite content. */
_cur.file->ReadBlock(buf, num);
} else {
/* Use the preloaded sprite data. */
buf = it->second.data();
GrfMsg(7, "DecodeSpecialSprite: Using preloaded pseudo sprite data");
/* Skip the real (original) content of this action. */
_cur.file->SeekTo(num, SEEK_CUR);
}
ByteReader br(buf, buf + num);
try {
uint8_t action = br.ReadByte();
if (action == 0xFF) {
GrfMsg(2, "DecodeSpecialSprite: Unexpected data block, skipping");
} else if (action == 0xFE) {
GrfMsg(2, "DecodeSpecialSprite: Unexpected import block, skipping");
} else {
InvokeGrfActionHandler::Invoke(action, stage, br);
}
} catch (...) {
GrfMsg(1, "DecodeSpecialSprite: Tried to read past end of pseudo-sprite data");
DisableGrf(STR_NEWGRF_ERROR_READ_BOUNDS);
}
}
/**
* Load a particular NewGRF from a SpriteFile.
* @param config The configuration of the to be loaded NewGRF.
* @param stage The loading stage of the NewGRF.
* @param file The file to load the GRF data from.
*/
static void LoadNewGRFFileFromFile(GRFConfig &config, GrfLoadingStage stage, SpriteFile &file)
{
AutoRestoreBackup cur_file(_cur.file, &file);
AutoRestoreBackup cur_config(_cur.grfconfig, &config);
Debug(grf, 2, "LoadNewGRFFile: Reading NewGRF-file '{}'", config.filename);
uint8_t grf_container_version = file.GetContainerVersion();
if (grf_container_version == 0) {
Debug(grf, 7, "LoadNewGRFFile: Custom .grf has invalid format");
return;
}
if (stage == GLS_INIT || stage == GLS_ACTIVATION) {
/* We need the sprite offsets in the init stage for NewGRF sounds
* and in the activation stage for real sprites. */
ReadGRFSpriteOffsets(file);
} else {
/* Skip sprite section offset if present. */
if (grf_container_version >= 2) file.ReadDword();
}
if (grf_container_version >= 2) {
/* Read compression value. */
uint8_t compression = file.ReadByte();
if (compression != 0) {
Debug(grf, 7, "LoadNewGRFFile: Unsupported compression format");
return;
}
}
/* Skip the first sprite; we don't care about how many sprites this
* does contain; newest TTDPatches and George's longvehicles don't
* neither, apparently. */
uint32_t num = grf_container_version >= 2 ? file.ReadDword() : file.ReadWord();
if (num == 4 && file.ReadByte() == 0xFF) {
file.ReadDword();
} else {
Debug(grf, 7, "LoadNewGRFFile: Custom .grf has invalid format");
return;
}
_cur.ClearDataForNextFile();
ReusableBuffer<uint8_t> buf;
while ((num = (grf_container_version >= 2 ? file.ReadDword() : file.ReadWord())) != 0) {
uint8_t type = file.ReadByte();
_cur.nfo_line++;
if (type == 0xFF) {
if (_cur.skip_sprites == 0) {
/* Limit the special sprites to 1 MiB. */
if (num > 1024 * 1024) {
GrfMsg(0, "LoadNewGRFFile: Unexpectedly large sprite, disabling");
DisableGrf(STR_NEWGRF_ERROR_UNEXPECTED_SPRITE);
break;
}
DecodeSpecialSprite(buf.Allocate(num), num, stage);
/* Stop all processing if we are to skip the remaining sprites */
if (_cur.skip_sprites == -1) break;
continue;
} else {
file.SkipBytes(num);
}
} else {
if (_cur.skip_sprites == 0) {
GrfMsg(0, "LoadNewGRFFile: Unexpected sprite, disabling");
DisableGrf(STR_NEWGRF_ERROR_UNEXPECTED_SPRITE);
break;
}
if (grf_container_version >= 2 && type == 0xFD) {
/* Reference to data section. Container version >= 2 only. */
file.SkipBytes(num);
} else {
file.SkipBytes(7);
SkipSpriteData(file, type, num - 8);
}
}
if (_cur.skip_sprites > 0) _cur.skip_sprites--;
}
}
/**
* Load a particular NewGRF.
* @param config The configuration of the to be loaded NewGRF.
* @param stage The loading stage of the NewGRF.
* @param subdir The sub directory to find the NewGRF in.
* @param temporary The NewGRF/sprite file is to be loaded temporarily and should be closed immediately,
* contrary to loading the SpriteFile and having it cached by the SpriteCache.
*/
void LoadNewGRFFile(GRFConfig &config, GrfLoadingStage stage, Subdirectory subdir, bool temporary)
{
const std::string &filename = config.filename;
/* A .grf file is activated only if it was active when the game was
* started. If a game is loaded, only its active .grfs will be
* reactivated, unless "loadallgraphics on" is used. A .grf file is
* considered active if its action 8 has been processed, i.e. its
* action 8 hasn't been skipped using an action 7.
*
* During activation, only actions 0, 1, 2, 3, 4, 5, 7, 8, 9, 0A and 0B are
* carried out. All others are ignored, because they only need to be
* processed once at initialization. */
if (stage != GLS_FILESCAN && stage != GLS_SAFETYSCAN && stage != GLS_LABELSCAN) {
_cur.grffile = GetFileByFilename(filename);
if (_cur.grffile == nullptr) UserError("File '{}' lost in cache.\n", filename);
if (stage == GLS_RESERVE && config.status != GCS_INITIALISED) return;
if (stage == GLS_ACTIVATION && !config.flags.Test(GRFConfigFlag::Reserved)) return;
}
bool needs_palette_remap = config.palette & GRFP_USE_MASK;
if (temporary) {
SpriteFile temporarySpriteFile(filename, subdir, needs_palette_remap);
LoadNewGRFFileFromFile(config, stage, temporarySpriteFile);
} else {
LoadNewGRFFileFromFile(config, stage, OpenCachedSpriteFile(filename, subdir, needs_palette_remap));
}
}
/**
* Relocates the old shore sprites at new positions.
*
* 1. If shore sprites are neither loaded by Action5 nor ActionA, the extra sprites from openttd(w/d).grf are used. (SHORE_REPLACE_ONLY_NEW)
* 2. If a newgrf replaces some shore sprites by ActionA. The (maybe also replaced) grass tiles are used for corner shores. (SHORE_REPLACE_ACTION_A)
* 3. If a newgrf replaces shore sprites by Action5 any shore replacement by ActionA has no effect. (SHORE_REPLACE_ACTION_5)
*/
static void ActivateOldShore()
{
/* Use default graphics, if no shore sprites were loaded.
* Should not happen, as the base set's extra grf should include some. */
if (_loaded_newgrf_features.shore == SHORE_REPLACE_NONE) _loaded_newgrf_features.shore = SHORE_REPLACE_ACTION_A;
if (_loaded_newgrf_features.shore != SHORE_REPLACE_ACTION_5) {
DupSprite(SPR_ORIGINALSHORE_START + 1, SPR_SHORE_BASE + 1); // SLOPE_W
DupSprite(SPR_ORIGINALSHORE_START + 2, SPR_SHORE_BASE + 2); // SLOPE_S
DupSprite(SPR_ORIGINALSHORE_START + 6, SPR_SHORE_BASE + 3); // SLOPE_SW
DupSprite(SPR_ORIGINALSHORE_START + 0, SPR_SHORE_BASE + 4); // SLOPE_E
DupSprite(SPR_ORIGINALSHORE_START + 4, SPR_SHORE_BASE + 6); // SLOPE_SE
DupSprite(SPR_ORIGINALSHORE_START + 3, SPR_SHORE_BASE + 8); // SLOPE_N
DupSprite(SPR_ORIGINALSHORE_START + 7, SPR_SHORE_BASE + 9); // SLOPE_NW
DupSprite(SPR_ORIGINALSHORE_START + 5, SPR_SHORE_BASE + 12); // SLOPE_NE
}
if (_loaded_newgrf_features.shore == SHORE_REPLACE_ACTION_A) {
DupSprite(SPR_FLAT_GRASS_TILE + 16, SPR_SHORE_BASE + 0); // SLOPE_STEEP_S
DupSprite(SPR_FLAT_GRASS_TILE + 17, SPR_SHORE_BASE + 5); // SLOPE_STEEP_W
DupSprite(SPR_FLAT_GRASS_TILE + 7, SPR_SHORE_BASE + 7); // SLOPE_WSE
DupSprite(SPR_FLAT_GRASS_TILE + 15, SPR_SHORE_BASE + 10); // SLOPE_STEEP_N
DupSprite(SPR_FLAT_GRASS_TILE + 11, SPR_SHORE_BASE + 11); // SLOPE_NWS
DupSprite(SPR_FLAT_GRASS_TILE + 13, SPR_SHORE_BASE + 13); // SLOPE_ENW
DupSprite(SPR_FLAT_GRASS_TILE + 14, SPR_SHORE_BASE + 14); // SLOPE_SEN
DupSprite(SPR_FLAT_GRASS_TILE + 18, SPR_SHORE_BASE + 15); // SLOPE_STEEP_E
/* XXX - SLOPE_EW, SLOPE_NS are currently not used.
* If they would be used somewhen, then these grass tiles will most like not look as needed */
DupSprite(SPR_FLAT_GRASS_TILE + 5, SPR_SHORE_BASE + 16); // SLOPE_EW
DupSprite(SPR_FLAT_GRASS_TILE + 10, SPR_SHORE_BASE + 17); // SLOPE_NS
}
}
/**
* Replocate the old tram depot sprites to the new position, if no new ones were loaded.
*/
static void ActivateOldTramDepot()
{
if (_loaded_newgrf_features.tram == TRAMWAY_REPLACE_DEPOT_WITH_TRACK) {
DupSprite(SPR_ROAD_DEPOT + 0, SPR_TRAMWAY_DEPOT_NO_TRACK + 0); // use road depot graphics for "no tracks"
DupSprite(SPR_TRAMWAY_DEPOT_WITH_TRACK + 1, SPR_TRAMWAY_DEPOT_NO_TRACK + 1);
DupSprite(SPR_ROAD_DEPOT + 2, SPR_TRAMWAY_DEPOT_NO_TRACK + 2); // use road depot graphics for "no tracks"
DupSprite(SPR_TRAMWAY_DEPOT_WITH_TRACK + 3, SPR_TRAMWAY_DEPOT_NO_TRACK + 3);
DupSprite(SPR_TRAMWAY_DEPOT_WITH_TRACK + 4, SPR_TRAMWAY_DEPOT_NO_TRACK + 4);
DupSprite(SPR_TRAMWAY_DEPOT_WITH_TRACK + 5, SPR_TRAMWAY_DEPOT_NO_TRACK + 5);
}
}
/**
* Decide whether price base multipliers of grfs shall apply globally or only to the grf specifying them
*/
static void FinalisePriceBaseMultipliers()
{
extern const PriceBaseSpec _price_base_specs[];
/** Features, to which '_grf_id_overrides' applies. Currently vehicle features only. */
static const uint32_t override_features = (1 << GSF_TRAINS) | (1 << GSF_ROADVEHICLES) | (1 << GSF_SHIPS) | (1 << GSF_AIRCRAFT);
/* Evaluate grf overrides */
int num_grfs = (uint)_grf_files.size();
std::vector<int> grf_overrides(num_grfs, -1);
for (int i = 0; i < num_grfs; i++) {
GRFFile &source = _grf_files[i];
auto it = _grf_id_overrides.find(source.grfid);
if (it == std::end(_grf_id_overrides)) continue;
uint32_t override_grfid = it->second;
auto dest = std::ranges::find(_grf_files, override_grfid, &GRFFile::grfid);
if (dest == std::end(_grf_files)) continue;
grf_overrides[i] = static_cast<int>(std::ranges::distance(std::begin(_grf_files), dest));
assert(grf_overrides[i] >= 0);
}
/* Override features and price base multipliers of earlier loaded grfs */
for (int i = 0; i < num_grfs; i++) {
if (grf_overrides[i] < 0 || grf_overrides[i] >= i) continue;
GRFFile &source = _grf_files[i];
GRFFile &dest = _grf_files[grf_overrides[i]];
uint32_t features = (source.grf_features | dest.grf_features) & override_features;
source.grf_features |= features;
dest.grf_features |= features;
for (Price p = PR_BEGIN; p < PR_END; p++) {
/* No price defined -> nothing to do */
if (!HasBit(features, _price_base_specs[p].grf_feature) || source.price_base_multipliers[p] == INVALID_PRICE_MODIFIER) continue;
Debug(grf, 3, "'{}' overrides price base multiplier {} of '{}'", source.filename, p, dest.filename);
dest.price_base_multipliers[p] = source.price_base_multipliers[p];
}
}
/* Propagate features and price base multipliers of afterwards loaded grfs, if none is present yet */
for (int i = num_grfs - 1; i >= 0; i--) {
if (grf_overrides[i] < 0 || grf_overrides[i] <= i) continue;
GRFFile &source = _grf_files[i];
GRFFile &dest = _grf_files[grf_overrides[i]];
uint32_t features = (source.grf_features | dest.grf_features) & override_features;
source.grf_features |= features;
dest.grf_features |= features;
for (Price p = PR_BEGIN; p < PR_END; p++) {
/* Already a price defined -> nothing to do */
if (!HasBit(features, _price_base_specs[p].grf_feature) || dest.price_base_multipliers[p] != INVALID_PRICE_MODIFIER) continue;
Debug(grf, 3, "Price base multiplier {} from '{}' propagated to '{}'", p, source.filename, dest.filename);
dest.price_base_multipliers[p] = source.price_base_multipliers[p];
}
}
/* The 'master grf' now have the correct multipliers. Assign them to the 'addon grfs' to make everything consistent. */
for (int i = 0; i < num_grfs; i++) {
if (grf_overrides[i] < 0) continue;
GRFFile &source = _grf_files[i];
GRFFile &dest = _grf_files[grf_overrides[i]];
uint32_t features = (source.grf_features | dest.grf_features) & override_features;
source.grf_features |= features;
dest.grf_features |= features;
for (Price p = PR_BEGIN; p < PR_END; p++) {
if (!HasBit(features, _price_base_specs[p].grf_feature)) continue;
if (source.price_base_multipliers[p] != dest.price_base_multipliers[p]) {
Debug(grf, 3, "Price base multiplier {} from '{}' propagated to '{}'", p, dest.filename, source.filename);
}
source.price_base_multipliers[p] = dest.price_base_multipliers[p];
}
}
/* Apply fallback prices for grf version < 8 */
for (auto &file : _grf_files) {
if (file.grf_version >= 8) continue;
PriceMultipliers &price_base_multipliers = file.price_base_multipliers;
for (Price p = PR_BEGIN; p < PR_END; p++) {
Price fallback_price = _price_base_specs[p].fallback_price;
if (fallback_price != INVALID_PRICE && price_base_multipliers[p] == INVALID_PRICE_MODIFIER) {
/* No price multiplier has been set.
* So copy the multiplier from the fallback price, maybe a multiplier was set there. */
price_base_multipliers[p] = price_base_multipliers[fallback_price];
}
}
}
/* Decide local/global scope of price base multipliers */
for (auto &file : _grf_files) {
PriceMultipliers &price_base_multipliers = file.price_base_multipliers;
for (Price p = PR_BEGIN; p < PR_END; p++) {
if (price_base_multipliers[p] == INVALID_PRICE_MODIFIER) {
/* No multiplier was set; set it to a neutral value */
price_base_multipliers[p] = 0;
} else {
if (!HasBit(file.grf_features, _price_base_specs[p].grf_feature)) {
/* The grf does not define any objects of the feature,
* so it must be a difficulty setting. Apply it globally */
Debug(grf, 3, "'{}' sets global price base multiplier {}", file.filename, p);
SetPriceBaseMultiplier(p, price_base_multipliers[p]);
price_base_multipliers[p] = 0;
} else {
Debug(grf, 3, "'{}' sets local price base multiplier {}", file.filename, p);
}
}
}
}
}
template <typename T>
void AddBadgeToSpecs(T &specs, GrfSpecFeature feature, Badge &badge)
{
for (auto &spec : specs) {
if (spec == nullptr) continue;
spec->badges.push_back(badge.index);
badge.features.Set(feature);
}
}
/** Finish up applying badges to things */
static void FinaliseBadges()
{
for (const auto &file : _grf_files) {
Badge *badge = GetBadgeByLabel(fmt::format("newgrf/{:08x}", std::byteswap(file.grfid)));
if (badge == nullptr) continue;
for (Engine *e : Engine::Iterate()) {
if (e->grf_prop.grffile != &file) continue;
e->badges.push_back(badge->index);
badge->features.Set(static_cast<GrfSpecFeature>(GSF_TRAINS + e->type));
}
AddBadgeToSpecs(file.stations, GSF_STATIONS, *badge);
AddBadgeToSpecs(file.housespec, GSF_HOUSES, *badge);
AddBadgeToSpecs(file.industryspec, GSF_INDUSTRIES, *badge);
AddBadgeToSpecs(file.indtspec, GSF_INDUSTRYTILES, *badge);
AddBadgeToSpecs(file.objectspec, GSF_OBJECTS, *badge);
AddBadgeToSpecs(file.airportspec, GSF_AIRPORTS, *badge);
AddBadgeToSpecs(file.airtspec, GSF_AIRPORTTILES, *badge);
AddBadgeToSpecs(file.roadstops, GSF_ROADSTOPS, *badge);
}
ApplyBadgeFeaturesToClassBadges();
}
extern void InitGRFTownGeneratorNames();
/** Finish loading NewGRFs and execute needed post-processing */
static void AfterLoadGRFs()
{
/* Cached callback groups are no longer needed. */
ResetCallbacks(true);
FinaliseStringMapping();
/* Clear the action 6 override sprites. */
_grf_line_to_action6_sprite_override.clear();
FinaliseBadges();
/* Polish cargoes */
FinaliseCargoArray();
/* Pre-calculate all refit masks after loading GRF files. */
CalculateRefitMasks();
/* Polish engines */
FinaliseEngineArray();
/* Set the actually used Canal properties */
FinaliseCanals();
/* Add all new houses to the house array. */
FinaliseHouseArray();
/* Add all new industries to the industry array. */
FinaliseIndustriesArray();
/* Add all new objects to the object array. */
FinaliseObjectsArray();
InitializeSortedCargoSpecs();
/* Sort the list of industry types. */
SortIndustryTypes();
/* Create dynamic list of industry legends for smallmap_gui.cpp */
BuildIndustriesLegend();
/* Build the routemap legend, based on the available cargos */
BuildLinkStatsLegend();
/* Add all new airports to the airports array. */
FinaliseAirportsArray();
BindAirportSpecs();
/* Update the townname generators list */
InitGRFTownGeneratorNames();
/* Run all queued vehicle list order changes */
CommitVehicleListOrderChanges();
/* Load old shore sprites in new position, if they were replaced by ActionA */
ActivateOldShore();
/* Load old tram depot sprites in new position, if no new ones are present */
ActivateOldTramDepot();
/* Set up custom rail types */
InitRailTypes();
InitRoadTypes();
for (Engine *e : Engine::IterateType(VEH_ROAD)) {
if (_gted[e->index].rv_max_speed != 0) {
/* Set RV maximum speed from the mph/0.8 unit value */
e->u.road.max_speed = _gted[e->index].rv_max_speed * 4;
}
RoadTramType rtt = e->info.misc_flags.Test(EngineMiscFlag::RoadIsTram) ? RTT_TRAM : RTT_ROAD;
const GRFFile *file = e->GetGRF();
if (file == nullptr || _gted[e->index].roadtramtype == 0) {
e->u.road.roadtype = (rtt == RTT_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD;
continue;
}
/* Remove +1 offset. */
_gted[e->index].roadtramtype--;
const std::vector<RoadTypeLabel> *list = (rtt == RTT_TRAM) ? &file->tramtype_list : &file->roadtype_list;
if (_gted[e->index].roadtramtype < list->size())
{
RoadTypeLabel rtl = (*list)[_gted[e->index].roadtramtype];
RoadType rt = GetRoadTypeByLabel(rtl);
if (rt != INVALID_ROADTYPE && GetRoadTramType(rt) == rtt) {
e->u.road.roadtype = rt;
continue;
}
}
/* Road type is not available, so disable this engine */
e->info.climates = {};
}
for (Engine *e : Engine::IterateType(VEH_TRAIN)) {
RailType railtype = GetRailTypeByLabel(_gted[e->index].railtypelabel);
if (railtype == INVALID_RAILTYPE) {
/* Rail type is not available, so disable this engine */
e->info.climates = {};
} else {
e->u.rail.railtype = railtype;
e->u.rail.intended_railtype = railtype;
}
}
SetYearEngineAgingStops();
FinalisePriceBaseMultipliers();
/* Deallocate temporary loading data */
_gted.clear();
_grm_sprites.clear();
}
/**
* Load all the NewGRFs.
* @param load_index The offset for the first sprite to add.
* @param num_baseset Number of NewGRFs at the front of the list to look up in the baseset dir instead of the newgrf dir.
*/
void LoadNewGRF(SpriteID load_index, uint num_baseset)
{
/* In case of networking we need to "sync" the start values
* so all NewGRFs are loaded equally. For this we use the
* start date of the game and we set the counters, etc. to
* 0 so they're the same too. */
TimerGameCalendar::Date date = TimerGameCalendar::date;
TimerGameCalendar::Year year = TimerGameCalendar::year;
TimerGameCalendar::DateFract date_fract = TimerGameCalendar::date_fract;
TimerGameEconomy::Date economy_date = TimerGameEconomy::date;
TimerGameEconomy::Year economy_year = TimerGameEconomy::year;
TimerGameEconomy::DateFract economy_date_fract = TimerGameEconomy::date_fract;
uint64_t tick_counter = TimerGameTick::counter;
uint8_t display_opt = _display_opt;
if (_networking) {
TimerGameCalendar::year = _settings_game.game_creation.starting_year;
TimerGameCalendar::date = TimerGameCalendar::ConvertYMDToDate(TimerGameCalendar::year, 0, 1);
TimerGameCalendar::date_fract = 0;
TimerGameEconomy::year = TimerGameEconomy::Year{_settings_game.game_creation.starting_year.base()};
TimerGameEconomy::date = TimerGameEconomy::ConvertYMDToDate(TimerGameEconomy::year, 0, 1);
TimerGameEconomy::date_fract = 0;
TimerGameTick::counter = 0;
_display_opt = 0;
}
InitializePatchFlags();
ResetNewGRFData();
/*
* Reset the status of all files, so we can 'retry' to load them.
* This is needed when one for example rearranges the NewGRFs in-game
* and a previously disabled NewGRF becomes usable. If it would not
* be reset, the NewGRF would remain disabled even though it should
* have been enabled.
*/
for (const auto &c : _grfconfig) {
if (c->status != GCS_NOT_FOUND) c->status = GCS_UNKNOWN;
}
_cur.spriteid = load_index;
/* Load newgrf sprites
* in each loading stage, (try to) open each file specified in the config
* and load information from it. */
for (GrfLoadingStage stage = GLS_LABELSCAN; stage <= GLS_ACTIVATION; stage++) {
/* Set activated grfs back to will-be-activated between reservation- and activation-stage.
* This ensures that action7/9 conditions 0x06 - 0x0A work correctly. */
for (const auto &c : _grfconfig) {
if (c->status == GCS_ACTIVATED) c->status = GCS_INITIALISED;
}
if (stage == GLS_RESERVE) {
static const std::pair<uint32_t, uint32_t> default_grf_overrides[] = {
{ std::byteswap(0x44442202), std::byteswap(0x44440111) }, // UKRS addons modifies UKRS
{ std::byteswap(0x6D620402), std::byteswap(0x6D620401) }, // DBSetXL ECS extension modifies DBSetXL
{ std::byteswap(0x4D656f20), std::byteswap(0x4D656F17) }, // LV4cut modifies LV4
};
for (const auto &grf_override : default_grf_overrides) {
SetNewGRFOverride(grf_override.first, grf_override.second);
}
}
uint num_grfs = 0;
uint num_non_static = 0;
_cur.stage = stage;
for (const auto &c : _grfconfig) {
if (c->status == GCS_DISABLED || c->status == GCS_NOT_FOUND) continue;
if (stage > GLS_INIT && c->flags.Test(GRFConfigFlag::InitOnly)) continue;
Subdirectory subdir = num_grfs < num_baseset ? BASESET_DIR : NEWGRF_DIR;
if (!FioCheckFileExists(c->filename, subdir)) {
Debug(grf, 0, "NewGRF file is missing '{}'; disabling", c->filename);
c->status = GCS_NOT_FOUND;
continue;
}
if (stage == GLS_LABELSCAN) InitNewGRFFile(*c);
if (!c->flags.Test(GRFConfigFlag::Static) && !c->flags.Test(GRFConfigFlag::System)) {
if (num_non_static == NETWORK_MAX_GRF_COUNT) {
Debug(grf, 0, "'{}' is not loaded as the maximum number of non-static GRFs has been reached", c->filename);
c->status = GCS_DISABLED;
c->error = {STR_NEWGRF_ERROR_MSG_FATAL, STR_NEWGRF_ERROR_TOO_MANY_NEWGRFS_LOADED};
continue;
}
num_non_static++;
}
num_grfs++;
LoadNewGRFFile(*c, stage, subdir, false);
if (stage == GLS_RESERVE) {
c->flags.Set(GRFConfigFlag::Reserved);
} else if (stage == GLS_ACTIVATION) {
c->flags.Reset(GRFConfigFlag::Reserved);
assert(GetFileByGRFID(c->ident.grfid) == _cur.grffile);
ClearTemporaryNewGRFData(_cur.grffile);
BuildCargoTranslationMap();
Debug(sprite, 2, "LoadNewGRF: Currently {} sprites are loaded", _cur.spriteid);
} else if (stage == GLS_INIT && c->flags.Test(GRFConfigFlag::InitOnly)) {
/* We're not going to activate this, so free whatever data we allocated */
ClearTemporaryNewGRFData(_cur.grffile);
}
}
}
/* Pseudo sprite processing is finished; free temporary stuff */
_cur.ClearDataForNextFile();
/* Call any functions that should be run after GRFs have been loaded. */
AfterLoadGRFs();
/* Now revert back to the original situation */
TimerGameCalendar::year = year;
TimerGameCalendar::date = date;
TimerGameCalendar::date_fract = date_fract;
TimerGameEconomy::year = economy_year;
TimerGameEconomy::date = economy_date;
TimerGameEconomy::date_fract = economy_date_fract;
TimerGameTick::counter = tick_counter;
_display_opt = display_opt;
}