1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-16 00:42:45 +01:00
Files
OpenTTD/src/newgrf/newgrf_actd.cpp

492 lines
15 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_actd.cpp NewGRF Action 0x0D handler. */
#include "../stdafx.h"
#include "../debug.h"
#include "../newgrf.h"
#include "../newgrf_engine.h"
#include "../newgrf_cargo.h"
#include "../timer/timer_game_calendar.h"
#include "../error.h"
#include "../engine_func.h"
#include "../vehicle_base.h"
#include "../rail.h"
#include "../road.h"
#include "newgrf_bytereader.h"
#include "newgrf_internal.h"
#include "table/strings.h"
#include "../safeguards.h"
/**
* Contains the GRF ID of the owner of a vehicle if it has been reserved.
* GRM for vehicles is only used if dynamic engine allocation is disabled,
* so 256 is the number of original engines. */
static std::array<uint32_t, 256> _grm_engines{};
/** Contains the GRF ID of the owner of a cargo if it has been reserved */
static std::array<uint32_t, NUM_CARGO * 2> _grm_cargoes{};
void ResetGRM()
{
_grm_engines.fill(0);
_grm_cargoes.fill(0);
}
/* Action 0x0D (GLS_SAFETYSCAN) */
static void SafeParamSet(ByteReader &buf)
{
uint8_t target = buf.ReadByte();
/* Writing GRF parameters and some bits of 'misc GRF features' are safe. */
if (target < 0x80 || target == 0x9E) return;
/* GRM could be unsafe, but as here it can only happen after other GRFs
* are loaded, it should be okay. If the GRF tried to use the slots it
* reserved, it would be marked unsafe anyway. GRM for (e.g. bridge)
* sprites is considered safe. */
GRFUnsafe(buf);
}
static uint32_t GetPatchVariable(uint8_t param)
{
switch (param) {
/* start year - 1920 */
case 0x0B: return (std::max(_settings_game.game_creation.starting_year, CalendarTime::ORIGINAL_BASE_YEAR) - CalendarTime::ORIGINAL_BASE_YEAR).base();
/* freight trains weight factor */
case 0x0E: return _settings_game.vehicle.freight_trains;
/* empty wagon speed increase */
case 0x0F: return 0;
/* plane speed factor; our patch option is reversed from TTDPatch's,
* the following is good for 1x, 2x and 4x (most common?) and...
* well not really for 3x. */
case 0x10:
switch (_settings_game.vehicle.plane_speed) {
default:
case 4: return 1;
case 3: return 2;
case 2: return 2;
case 1: return 4;
}
/* 2CC colourmap base sprite */
case 0x11: return SPR_2CCMAP_BASE;
/* map size: format = -MABXYSS
* M : the type of map
* bit 0 : set : squared map. Bit 1 is now not relevant
* clear : rectangle map. Bit 1 will indicate the bigger edge of the map
* bit 1 : set : Y is the bigger edge. Bit 0 is clear
* clear : X is the bigger edge.
* A : minimum edge(log2) of the map
* B : maximum edge(log2) of the map
* XY : edges(log2) of each side of the map.
* SS : combination of both X and Y, thus giving the size(log2) of the map
*/
case 0x13: {
uint8_t map_bits = 0;
uint8_t log_X = Map::LogX() - 6; // subtraction is required to make the minimal size (64) zero based
uint8_t log_Y = Map::LogY() - 6;
uint8_t max_edge = std::max(log_X, log_Y);
if (log_X == log_Y) { // we have a squared map, since both edges are identical
SetBit(map_bits, 0);
} else {
if (max_edge == log_Y) SetBit(map_bits, 1); // edge Y been the biggest, mark it
}
return (map_bits << 24) | (std::min(log_X, log_Y) << 20) | (max_edge << 16) |
(log_X << 12) | (log_Y << 8) | (log_X + log_Y);
}
/* The maximum height of the map. */
case 0x14:
return _settings_game.construction.map_height_limit;
/* Extra foundations base sprite */
case 0x15:
return SPR_SLOPES_BASE;
/* Shore base sprite */
case 0x16:
return SPR_SHORE_BASE;
/* Game map seed */
case 0x17:
return _settings_game.game_creation.generation_seed;
default:
GrfMsg(2, "ParamSet: Unknown Patch variable 0x{:02X}.", param);
return 0;
}
}
static uint32_t PerformGRM(std::span<uint32_t> grm, uint16_t count, uint8_t op, uint8_t target, std::string_view type)
{
uint start = 0;
uint size = 0;
if (op == 6) {
/* Return GRFID of set that reserved ID */
return grm[_cur_gps.grffile->GetParam(target)];
}
/* With an operation of 2 or 3, we want to reserve a specific block of IDs */
if (op == 2 || op == 3) start = _cur_gps.grffile->GetParam(target);
for (uint i = start; i < std::size(grm); i++) {
if (grm[i] == 0) {
size++;
} else {
if (op == 2 || op == 3) break;
start = i + 1;
size = 0;
}
if (size == count) break;
}
if (size == count) {
/* Got the slot... */
if (op == 0 || op == 3) {
GrfMsg(2, "ParamSet: GRM: Reserving {} {} at {}", count, type, start);
for (uint i = 0; i < count; i++) grm[start + i] = _cur_gps.grffile->grfid;
}
return start;
}
/* Unable to allocate */
if (op != 4 && op != 5) {
/* Deactivate GRF */
GrfMsg(0, "ParamSet: GRM: Unable to allocate {} {}, deactivating", count, type);
DisableGrf(STR_NEWGRF_ERROR_GRM_FAILED);
return UINT_MAX;
}
GrfMsg(1, "ParamSet: GRM: Unable to allocate {} {}", count, type);
return UINT_MAX;
}
/** Action 0x0D: Set parameter */
static void ParamSet(ByteReader &buf)
{
/* <0D> <target> <operation> <source1> <source2> [<data>]
*
* B target parameter number where result is stored
* B operation operation to perform, see below
* B source1 first source operand
* B source2 second source operand
* D data data to use in the calculation, not necessary
* if both source1 and source2 refer to actual parameters
*
* Operations
* 00 Set parameter equal to source1
* 01 Addition, source1 + source2
* 02 Subtraction, source1 - source2
* 03 Unsigned multiplication, source1 * source2 (both unsigned)
* 04 Signed multiplication, source1 * source2 (both signed)
* 05 Unsigned bit shift, source1 by source2 (source2 taken to be a
* signed quantity; left shift if positive and right shift if
* negative, source1 is unsigned)
* 06 Signed bit shift, source1 by source2
* (source2 like in 05, and source1 as well)
*/
uint8_t target = buf.ReadByte();
uint8_t oper = buf.ReadByte();
uint32_t src1 = buf.ReadByte();
uint32_t src2 = buf.ReadByte();
uint32_t data = 0;
if (buf.Remaining() >= 4) data = buf.ReadDWord();
/* You can add 80 to the operation to make it apply only if the target
* is not defined yet. In this respect, a parameter is taken to be
* defined if any of the following applies:
* - it has been set to any value in the newgrf(w).cfg parameter list
* - it OR A PARAMETER WITH HIGHER NUMBER has been set to any value by
* an earlier action D */
if (HasBit(oper, 7)) {
if (target < 0x80 && target < std::size(_cur_gps.grffile->param)) {
GrfMsg(7, "ParamSet: Param {} already defined, skipping", target);
return;
}
oper = GB(oper, 0, 7);
}
if (src2 == 0xFE) {
if (GB(data, 0, 8) == 0xFF) {
if (data == 0x0000FFFF) {
/* Patch variables */
src1 = GetPatchVariable(src1);
} else {
/* GRF Resource Management */
uint8_t op = src1;
GrfSpecFeature feature{static_cast<uint8_t>(GB(data, 8, 8))};
uint16_t count = GB(data, 16, 16);
if (_cur_gps.stage == GLS_RESERVE) {
if (feature == GSF_GLOBALVAR) {
/* General sprites */
if (op == 0) {
/* Check if the allocated sprites will fit below the original sprite limit */
if (_cur_gps.spriteid + count >= 16384) {
GrfMsg(0, "ParamSet: GRM: Unable to allocate {} sprites; try changing NewGRF order", count);
DisableGrf(STR_NEWGRF_ERROR_GRM_FAILED);
return;
}
/* Reserve space at the current sprite ID */
GrfMsg(4, "ParamSet: GRM: Allocated {} sprites at {}", count, _cur_gps.spriteid);
_grm_sprites[GRFLocation(_cur_gps.grffile->grfid, _cur_gps.nfo_line)] = std::make_pair(_cur_gps.spriteid, count);
_cur_gps.spriteid += count;
}
}
/* Ignore GRM result during reservation */
src1 = 0;
} else if (_cur_gps.stage == GLS_ACTIVATION) {
switch (feature) {
case GSF_TRAINS:
case GSF_ROADVEHICLES:
case GSF_SHIPS:
case GSF_AIRCRAFT:
if (!_settings_game.vehicle.dynamic_engines) {
src1 = PerformGRM({std::begin(_grm_engines) + _engine_offsets[feature], _engine_counts[feature]}, count, op, target, "vehicles");
if (_cur_gps.skip_sprites == -1) return;
} else {
/* GRM does not apply for dynamic engine allocation. */
switch (op) {
case 2:
case 3:
src1 = _cur_gps.grffile->GetParam(target);
break;
default:
src1 = 0;
break;
}
}
break;
case GSF_GLOBALVAR: // General sprites
switch (op) {
case 0:
/* Return space reserved during reservation stage */
src1 = _grm_sprites[GRFLocation(_cur_gps.grffile->grfid, _cur_gps.nfo_line)].first;
GrfMsg(4, "ParamSet: GRM: Using pre-allocated sprites at {}", src1);
break;
case 1:
src1 = _cur_gps.spriteid;
break;
default:
GrfMsg(1, "ParamSet: GRM: Unsupported operation {} for general sprites", op);
return;
}
break;
case GSF_CARGOES: // Cargo
/* There are two ranges: one for cargo IDs and one for cargo bitmasks */
src1 = PerformGRM(_grm_cargoes, count, op, target, "cargoes");
if (_cur_gps.skip_sprites == -1) return;
break;
default: GrfMsg(1, "ParamSet: GRM: Unsupported feature 0x{:X}", feature); return;
}
} else {
/* Ignore GRM during initialization */
src1 = 0;
}
}
} else {
/* Read another GRF File's parameter */
const GRFFile *file = GetFileByGRFID(data);
GRFConfig *c = GetGRFConfig(data);
if (c != nullptr && c->flags.Test(GRFConfigFlag::Static) && !_cur_gps.grfconfig->flags.Test(GRFConfigFlag::Static) && _networking) {
/* Disable the read GRF if it is a static NewGRF. */
DisableStaticNewGRFInfluencingNonStaticNewGRFs(*c);
src1 = 0;
} else if (file == nullptr || c == nullptr || c->status == GCS_DISABLED) {
src1 = 0;
} else if (src1 == 0xFE) {
src1 = c->version;
} else {
src1 = file->GetParam(src1);
}
}
} else {
/* The source1 and source2 operands refer to the grf parameter number
* like in action 6 and 7. In addition, they can refer to the special
* variables available in action 7, or they can be FF to use the value
* of <data>. If referring to parameters that are undefined, a value
* of 0 is used instead. */
src1 = (src1 == 0xFF) ? data : GetParamVal(src1, nullptr);
src2 = (src2 == 0xFF) ? data : GetParamVal(src2, nullptr);
}
uint32_t res;
switch (oper) {
case 0x00:
res = src1;
break;
case 0x01:
res = src1 + src2;
break;
case 0x02:
res = src1 - src2;
break;
case 0x03:
res = src1 * src2;
break;
case 0x04:
res = (int32_t)src1 * (int32_t)src2;
break;
case 0x05:
if ((int32_t)src2 < 0) {
res = src1 >> -(int32_t)src2;
} else {
res = src1 << (src2 & 0x1F); // Same behaviour as in EvalAdjustT, mask 'value' to 5 bits, which should behave the same on all architectures.
}
break;
case 0x06:
if ((int32_t)src2 < 0) {
res = (int32_t)src1 >> -(int32_t)src2;
} else {
res = (int32_t)src1 << (src2 & 0x1F); // Same behaviour as in EvalAdjustT, mask 'value' to 5 bits, which should behave the same on all architectures.
}
break;
case 0x07: // Bitwise AND
res = src1 & src2;
break;
case 0x08: // Bitwise OR
res = src1 | src2;
break;
case 0x09: // Unsigned division
if (src2 == 0) {
res = src1;
} else {
res = src1 / src2;
}
break;
case 0x0A: // Signed division
if (src2 == 0) {
res = src1;
} else {
res = (int32_t)src1 / (int32_t)src2;
}
break;
case 0x0B: // Unsigned modulo
if (src2 == 0) {
res = src1;
} else {
res = src1 % src2;
}
break;
case 0x0C: // Signed modulo
if (src2 == 0) {
res = src1;
} else {
res = (int32_t)src1 % (int32_t)src2;
}
break;
default: GrfMsg(0, "ParamSet: Unknown operation {}, skipping", oper); return;
}
switch (target) {
case 0x8E: // Y-Offset for train sprites
_cur_gps.grffile->traininfo_vehicle_pitch = res;
break;
case 0x8F: { // Rail track type cost factors
extern RailTypeInfo _railtypes[RAILTYPE_END];
_railtypes[RAILTYPE_RAIL].cost_multiplier = GB(res, 0, 8);
if (_settings_game.vehicle.disable_elrails) {
_railtypes[RAILTYPE_ELECTRIC].cost_multiplier = GB(res, 0, 8);
_railtypes[RAILTYPE_MONO].cost_multiplier = GB(res, 8, 8);
} else {
_railtypes[RAILTYPE_ELECTRIC].cost_multiplier = GB(res, 8, 8);
_railtypes[RAILTYPE_MONO].cost_multiplier = GB(res, 16, 8);
}
_railtypes[RAILTYPE_MAGLEV].cost_multiplier = GB(res, 16, 8);
break;
}
/* not implemented */
case 0x93: // Tile refresh offset to left -- Intended to allow support for larger sprites, not necessary for OTTD
case 0x94: // Tile refresh offset to right
case 0x95: // Tile refresh offset upwards
case 0x96: // Tile refresh offset downwards
case 0x97: // Snow line height -- Better supported by feature 8 property 10h (snow line table) TODO: implement by filling the entire snow line table with the given value
case 0x99: // Global ID offset -- Not necessary since IDs are remapped automatically
GrfMsg(7, "ParamSet: Skipping unimplemented target 0x{:02X}", target);
break;
case 0x9E: { // Miscellaneous GRF features
GrfMiscBits bits(res);
/* Set train list engine width */
_cur_gps.grffile->traininfo_vehicle_width = bits.Test(GrfMiscBit::TrainWidth32Pixels) ? VEHICLEINFO_FULL_VEHICLE_WIDTH : TRAININFO_DEFAULT_VEHICLE_WIDTH;
/* Remove the local flags from the global flags */
bits.Reset(GrfMiscBit::TrainWidth32Pixels);
/* Only copy safe bits for static grfs */
if (_cur_gps.grfconfig->flags.Test(GRFConfigFlag::Static)) {
GrfMiscBits safe_bits = GrfMiscBit::SecondRockyTileSet;
_misc_grf_features.Reset(safe_bits);
_misc_grf_features.Set(bits & safe_bits);
} else {
_misc_grf_features = bits;
}
break;
}
case 0x9F: // locale-dependent settings
GrfMsg(7, "ParamSet: Skipping unimplemented target 0x{:02X}", target);
break;
default:
if (target < 0x80) {
/* Resize (and fill with zeroes) if needed. */
if (target >= std::size(_cur_gps.grffile->param)) _cur_gps.grffile->param.resize(target + 1);
_cur_gps.grffile->param[target] = res;
} else {
GrfMsg(7, "ParamSet: Skipping unknown target 0x{:02X}", target);
}
break;
}
}
template <> void GrfActionHandler<0x0D>::FileScan(ByteReader &) { }
template <> void GrfActionHandler<0x0D>::SafetyScan(ByteReader &buf) { SafeParamSet(buf); }
template <> void GrfActionHandler<0x0D>::LabelScan(ByteReader &) { }
template <> void GrfActionHandler<0x0D>::Init(ByteReader &buf) { ParamSet(buf); }
template <> void GrfActionHandler<0x0D>::Reserve(ByteReader &buf) { ParamSet(buf); }
template <> void GrfActionHandler<0x0D>::Activation(ByteReader &buf) { ParamSet(buf); }