mirror of https://github.com/OpenTTD/OpenTTD
1844 lines
67 KiB
C++
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;
|
|
}
|