mirror of
https://github.com/OpenTTD/OpenTTD
synced 2026-01-17 01:12:39 +01:00
Bits used by company faces are now defined by a variable system instead of being hardcoded, allowing future expansion. The four face types covering gender and skin colour are now separate face styles with their own definitions.
1873 lines
68 KiB
C++
1873 lines
68 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 "company_manager_face.h"
|
|
#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_badge_config.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_gps;
|
|
|
|
TypedIndexContainer<std::vector<GRFTempEngineData>, EngineID> _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)
|
|
{
|
|
if (_cur_gps.grfconfig == nullptr) {
|
|
Debug(grf, severity, "{}", msg);
|
|
} else {
|
|
Debug(grf, severity, "[{}:{}] {}", _cur_gps.grfconfig->filename, _cur_gps.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_gps.grfconfig;
|
|
file = _cur_gps.grffile;
|
|
}
|
|
|
|
config->status = GCS_DISABLED;
|
|
if (file != nullptr) ClearTemporaryNewGRFData(file);
|
|
if (config == _cur_gps.grfconfig) _cur_gps.skip_sprites = -1;
|
|
|
|
if (message == STR_NULL) return nullptr;
|
|
|
|
config->error = {STR_NEWGRF_ERROR_MSG_FATAL, message};
|
|
if (config == _cur_gps.grfconfig) config->error->param_value[0] = _cur_gps.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_gps.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_gps.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_gps.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, std::string_view 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_gps.grfconfig->flags.Set(GRFConfigFlag::Unsafe);
|
|
|
|
/* Skip remainder of GRF */
|
|
_cur_gps.skip_sprites = -1;
|
|
}
|
|
|
|
/** Reset and clear all NewGRFs */
|
|
static void ResetNewGRF()
|
|
{
|
|
_cur_gps.grfconfig = nullptr;
|
|
_cur_gps.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({});
|
|
|
|
ResetFaces();
|
|
|
|
/* 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_gps.grffile->cargo_map.fill(UINT8_MAX);
|
|
|
|
auto cargo_list = GetCargoTranslationTable(*_cur_gps.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_gps.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_gps.grffile = newfile;
|
|
return;
|
|
}
|
|
|
|
assert(_grf_files.size() < _grf_files.capacity()); // We must not invalidate pointers.
|
|
_cur_gps.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, 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.Any(HZ_ZONE_ALL) && hs.building_availability.Any(HZ_CLIMATE_ALL)) {
|
|
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.All(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.All(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 (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++) {
|
|
auto &hs = file.housespec[i];
|
|
|
|
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(std::move(*hs));
|
|
}
|
|
|
|
/* Won't be used again */
|
|
file.housespec.clear();
|
|
file.housespec.shrink_to_fit();
|
|
}
|
|
|
|
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 = GetClimateMaskForLandscape();
|
|
for (HouseZone climate : climate_mask) {
|
|
for (HouseZone zone : HZ_ZONE_ALL) {
|
|
EnsureEarlyHouse({climate, zone});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 (auto &file : _grf_files) {
|
|
for (auto &indsp : file.industryspec) {
|
|
if (indsp == nullptr || !indsp->enabled) continue;
|
|
|
|
_industry_mngr.SetEntitySpec(std::move(*indsp));
|
|
}
|
|
|
|
for (auto &indtsp : file.indtspec) {
|
|
if (indtsp != nullptr) {
|
|
_industile_mngr.SetEntitySpec(std::move(*indtsp));
|
|
}
|
|
}
|
|
|
|
/* Won't be used again */
|
|
file.industryspec.clear();
|
|
file.industryspec.shrink_to_fit();
|
|
file.indtspec.clear();
|
|
file.indtspec.shrink_to_fit();
|
|
}
|
|
|
|
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 (auto &file : _grf_files) {
|
|
for (auto &objectspec : file.objectspec) {
|
|
if (objectspec != nullptr && objectspec->grf_prop.HasGrfFile() && objectspec->IsEnabled()) {
|
|
_object_mngr.SetEntitySpec(std::move(*objectspec));
|
|
}
|
|
}
|
|
|
|
/* Won't be used again */
|
|
file.objectspec.clear();
|
|
file.objectspec.shrink_to_fit();
|
|
}
|
|
|
|
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 (auto &file : _grf_files) {
|
|
for (auto &as : file.airportspec) {
|
|
if (as != nullptr && as->enabled) {
|
|
_airport_mngr.SetEntitySpec(std::move(*as));
|
|
}
|
|
}
|
|
|
|
for (auto &ats : file.airtspec) {
|
|
if (ats != nullptr && ats->enabled) {
|
|
_airporttile_mngr.SetEntitySpec(std::move(*ats));
|
|
}
|
|
}
|
|
|
|
/* Won't be used again */
|
|
file.airportspec.clear();
|
|
file.airportspec.shrink_to_fit();
|
|
file.airtspec.clear();
|
|
file.airtspec.shrink_to_fit();
|
|
}
|
|
}
|
|
|
|
/** 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(ReusableBuffer<uint8_t> &allocator, uint num, GrfLoadingStage stage)
|
|
{
|
|
uint8_t *buf;
|
|
auto it = _grf_line_to_action6_sprite_override.find({_cur_gps.grfconfig->ident.grfid, _cur_gps.nfo_line});
|
|
if (it == _grf_line_to_action6_sprite_override.end()) {
|
|
/* No preloaded sprite to work with; read the
|
|
* pseudo sprite content. */
|
|
buf = allocator.Allocate(num);
|
|
_cur_gps.file->ReadBlock(buf, num);
|
|
} else {
|
|
/* Use the preloaded sprite data. */
|
|
buf = it->second.data();
|
|
assert(it->second.size() == num);
|
|
GrfMsg(7, "DecodeSpecialSprite: Using preloaded pseudo sprite data");
|
|
|
|
/* Skip the real (original) content of this action. */
|
|
_cur_gps.file->SeekTo(num, SEEK_CUR);
|
|
}
|
|
|
|
ByteReader br(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_gps.file, &file);
|
|
AutoRestoreBackup cur_config(_cur_gps.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_gps.ClearDataForNextFile();
|
|
|
|
ReusableBuffer<uint8_t> allocator;
|
|
|
|
while ((num = (grf_container_version >= 2 ? file.ReadDword() : file.ReadWord())) != 0) {
|
|
uint8_t type = file.ReadByte();
|
|
_cur_gps.nfo_line++;
|
|
|
|
if (type == 0xFF) {
|
|
if (_cur_gps.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(allocator, num, stage);
|
|
|
|
/* Stop all processing if we are to skip the remaining sprites */
|
|
if (_cur_gps.skip_sprites == -1) break;
|
|
|
|
continue;
|
|
} else {
|
|
file.SkipBytes(num);
|
|
}
|
|
} else {
|
|
if (_cur_gps.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_gps.skip_sprites > 0) _cur_gps.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_gps.grffile = GetFileByFilename(filename);
|
|
if (_cur_gps.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 constexpr GrfSpecFeatures override_features{GSF_TRAINS, GSF_ROADVEHICLES, GSF_SHIPS, 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]];
|
|
|
|
GrfSpecFeatures features = (source.grf_features | dest.grf_features) & override_features;
|
|
source.grf_features.Set(features);
|
|
dest.grf_features.Set(features);
|
|
|
|
for (Price p = PR_BEGIN; p < PR_END; p++) {
|
|
/* No price defined -> nothing to do */
|
|
if (!features.Test(_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]];
|
|
|
|
GrfSpecFeatures features = (source.grf_features | dest.grf_features) & override_features;
|
|
source.grf_features.Set(features);
|
|
dest.grf_features.Set(features);
|
|
|
|
for (Price p = PR_BEGIN; p < PR_END; p++) {
|
|
/* Already a price defined -> nothing to do */
|
|
if (!features.Test(_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]];
|
|
|
|
GrfSpecFeatures features = (source.grf_features | dest.grf_features) & override_features;
|
|
source.grf_features.Set(features);
|
|
dest.grf_features.Set(features);
|
|
|
|
for (Price p = PR_BEGIN; p < PR_END; p++) {
|
|
if (!features.Test(_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 (!file.grf_features.Test(_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();
|
|
AddBadgeClassesToConfiguration();
|
|
}
|
|
|
|
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_gps.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_gps.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_gps.grffile);
|
|
ClearTemporaryNewGRFData(_cur_gps.grffile);
|
|
BuildCargoTranslationMap();
|
|
Debug(sprite, 2, "LoadNewGRF: Currently {} sprites are loaded", _cur_gps.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_gps.grffile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We've finished reading files. */
|
|
_cur_gps.grfconfig = nullptr;
|
|
_cur_gps.grffile = nullptr;
|
|
|
|
/* Pseudo sprite processing is finished; free temporary stuff */
|
|
_cur_gps.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;
|
|
}
|