1
0
Fork 0
OpenTTD/src/newgrf/newgrf_act0_industries.cpp

709 lines
21 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_act0_industries.cpp NewGRF Action 0x00 handler for industries and industrytiles. */
#include "../stdafx.h"
#include "../debug.h"
#include "../newgrf_cargo.h"
#include "../industry.h"
#include "../industrytype.h"
#include "../industry_map.h"
#include "../newgrf_industries.h"
#include "newgrf_bytereader.h"
#include "newgrf_internal.h"
#include "newgrf_stringmapping.h"
#include "table/strings.h"
#include "../table/build_industry.h"
#include "../safeguards.h"
/**
* Ignore an industry tile property
* @param prop The property to ignore.
* @param buf The property value.
* @return ChangeInfoResult.
*/
static ChangeInfoResult IgnoreIndustryTileProperty(int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
switch (prop) {
case 0x09:
case 0x0D:
case 0x0E:
case 0x10:
case 0x11:
case 0x12:
buf.ReadByte();
break;
case 0x0A:
case 0x0B:
case 0x0C:
case 0x0F:
buf.ReadWord();
break;
case 0x13:
buf.Skip(buf.ReadByte() * 2);
break;
default:
ret = CIR_UNKNOWN;
break;
}
return ret;
}
/**
* Define properties for industry tiles
* @param first Local ID of the first industry tile.
* @param last Local ID of the last industry tile.
* @param prop The property to change.
* @param buf The property value.
* @return ChangeInfoResult.
*/
static ChangeInfoResult IndustrytilesChangeInfo(uint first, uint last, int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
if (last > NUM_INDUSTRYTILES_PER_GRF) {
GrfMsg(1, "IndustryTilesChangeInfo: Too many industry tiles loaded ({}), max ({}). Ignoring.", last, NUM_INDUSTRYTILES_PER_GRF);
return CIR_INVALID_ID;
}
/* Allocate industry tile specs if they haven't been allocated already. */
if (_cur.grffile->indtspec.size() < last) _cur.grffile->indtspec.resize(last);
for (uint id = first; id < last; ++id) {
auto &tsp = _cur.grffile->indtspec[id];
if (prop != 0x08 && tsp == nullptr) {
ChangeInfoResult cir = IgnoreIndustryTileProperty(prop, buf);
if (cir > ret) ret = cir;
continue;
}
switch (prop) {
case 0x08: { // Substitute industry tile type
uint8_t subs_id = buf.ReadByte();
if (subs_id >= NEW_INDUSTRYTILEOFFSET) {
/* The substitute id must be one of the original industry tile. */
GrfMsg(2, "IndustryTilesChangeInfo: Attempt to use new industry tile {} as substitute industry tile for {}. Ignoring.", subs_id, id);
continue;
}
/* Allocate space for this industry. */
if (tsp == nullptr) {
tsp = std::make_unique<IndustryTileSpec>(_industry_tile_specs[subs_id]);
tsp->enabled = true;
/* A copied tile should not have the animation infos copied too.
* The anim_state should be left untouched, though
* It is up to the author to animate them */
tsp->anim_production = INDUSTRYTILE_NOANIM;
tsp->anim_next = INDUSTRYTILE_NOANIM;
tsp->grf_prop.local_id = id;
tsp->grf_prop.subst_id = subs_id;
tsp->grf_prop.SetGRFFile(_cur.grffile);
_industile_mngr.AddEntityID(id, _cur.grffile->grfid, subs_id); // pre-reserve the tile slot
}
break;
}
case 0x09: { // Industry tile override
uint8_t ovrid = buf.ReadByte();
/* The industry being overridden must be an original industry. */
if (ovrid >= NEW_INDUSTRYTILEOFFSET) {
GrfMsg(2, "IndustryTilesChangeInfo: Attempt to override new industry tile {} with industry tile id {}. Ignoring.", ovrid, id);
continue;
}
_industile_mngr.Add(id, _cur.grffile->grfid, ovrid);
break;
}
case 0x0A: // Tile acceptance
case 0x0B:
case 0x0C: {
uint16_t acctp = buf.ReadWord();
tsp->accepts_cargo[prop - 0x0A] = GetCargoTranslation(GB(acctp, 0, 8), _cur.grffile);
tsp->acceptance[prop - 0x0A] = Clamp(GB(acctp, 8, 8), 0, 16);
tsp->accepts_cargo_label[prop - 0x0A] = CT_INVALID;
break;
}
case 0x0D: // Land shape flags
tsp->slopes_refused = (Slope)buf.ReadByte();
break;
case 0x0E: // Callback mask
tsp->callback_mask = static_cast<IndustryTileCallbackMasks>(buf.ReadByte());
break;
case 0x0F: // Animation information
tsp->animation.frames = buf.ReadByte();
tsp->animation.status = buf.ReadByte();
break;
case 0x10: // Animation speed
tsp->animation.speed = buf.ReadByte();
break;
case 0x11: // Triggers for callback 25
tsp->animation.triggers = buf.ReadByte();
break;
case 0x12: // Special flags
tsp->special_flags = IndustryTileSpecialFlags{buf.ReadByte()};
break;
case 0x13: { // variable length cargo acceptance
uint8_t num_cargoes = buf.ReadByte();
if (num_cargoes > std::size(tsp->acceptance)) {
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
error->param_value[1] = prop;
return CIR_DISABLED;
}
for (uint i = 0; i < std::size(tsp->acceptance); i++) {
if (i < num_cargoes) {
tsp->accepts_cargo[i] = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
/* Tile acceptance can be negative to counteract the IndustryTileSpecialFlag::AcceptsAllCargo flag */
tsp->acceptance[i] = (int8_t)buf.ReadByte();
} else {
tsp->accepts_cargo[i] = INVALID_CARGO;
tsp->acceptance[i] = 0;
}
if (i < std::size(tsp->accepts_cargo_label)) tsp->accepts_cargo_label[i] = CT_INVALID;
}
break;
}
case 0x14: // Badge list
tsp->badges = ReadBadgeList(buf, GSF_INDUSTRYTILES);
break;
default:
ret = CIR_UNKNOWN;
break;
}
}
return ret;
}
/**
* Ignore an industry property
* @param prop The property to ignore.
* @param buf The property value.
* @return ChangeInfoResult.
*/
static ChangeInfoResult IgnoreIndustryProperty(int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
switch (prop) {
case 0x09:
case 0x0B:
case 0x0F:
case 0x12:
case 0x13:
case 0x14:
case 0x17:
case 0x18:
case 0x19:
case 0x21:
case 0x22:
buf.ReadByte();
break;
case 0x0C:
case 0x0D:
case 0x0E:
case 0x10: // INDUSTRY_ORIGINAL_NUM_OUTPUTS bytes
case 0x1B:
case 0x1F:
case 0x24:
buf.ReadWord();
break;
case 0x11: // INDUSTRY_ORIGINAL_NUM_INPUTS bytes + 1
case 0x1A:
case 0x1C:
case 0x1D:
case 0x1E:
case 0x20:
case 0x23:
buf.ReadDWord();
break;
case 0x0A: {
uint8_t num_table = buf.ReadByte();
for (uint8_t j = 0; j < num_table; j++) {
for (uint k = 0;; k++) {
uint8_t x = buf.ReadByte();
if (x == 0xFE && k == 0) {
buf.ReadByte();
buf.ReadByte();
break;
}
uint8_t y = buf.ReadByte();
if (x == 0 && y == 0x80) break;
uint8_t gfx = buf.ReadByte();
if (gfx == 0xFE) buf.ReadWord();
}
}
break;
}
case 0x16:
for (uint8_t j = 0; j < INDUSTRY_ORIGINAL_NUM_INPUTS; j++) buf.ReadByte();
break;
case 0x15:
case 0x25:
case 0x26:
case 0x27:
buf.Skip(buf.ReadByte());
break;
case 0x28: {
int num_inputs = buf.ReadByte();
int num_outputs = buf.ReadByte();
buf.Skip(num_inputs * num_outputs * 2);
break;
}
case 0x29: // Badge list
SkipBadgeList(buf);
break;
default:
ret = CIR_UNKNOWN;
break;
}
return ret;
}
/**
* Validate the industry layout; e.g. to prevent duplicate tiles.
* @param layout The layout to check.
* @return True if the layout is deemed valid.
*/
static bool ValidateIndustryLayout(const IndustryTileLayout &layout)
{
const size_t size = layout.size();
if (size == 0) return false;
for (size_t i = 0; i < size - 1; i++) {
for (size_t j = i + 1; j < size; j++) {
if (layout[i].ti.x == layout[j].ti.x &&
layout[i].ti.y == layout[j].ti.y) {
return false;
}
}
}
bool have_regular_tile = false;
for (const auto &tilelayout : layout) {
if (tilelayout.gfx != GFX_WATERTILE_SPECIALCHECK) {
have_regular_tile = true;
break;
}
}
return have_regular_tile;
}
/**
* Define properties for industries
* @param first Local ID of the first industry.
* @param last Local ID of the last industry.
* @param prop The property to change.
* @param buf The property value.
* @return ChangeInfoResult.
*/
static ChangeInfoResult IndustriesChangeInfo(uint first, uint last, int prop, ByteReader &buf)
{
ChangeInfoResult ret = CIR_SUCCESS;
if (last > NUM_INDUSTRYTYPES_PER_GRF) {
GrfMsg(1, "IndustriesChangeInfo: Too many industries loaded ({}), max ({}). Ignoring.", last, NUM_INDUSTRYTYPES_PER_GRF);
return CIR_INVALID_ID;
}
/* Allocate industry specs if they haven't been allocated already. */
if (_cur.grffile->industryspec.size() < last) _cur.grffile->industryspec.resize(last);
for (uint id = first; id < last; ++id) {
auto &indsp = _cur.grffile->industryspec[id];
if (prop != 0x08 && indsp == nullptr) {
ChangeInfoResult cir = IgnoreIndustryProperty(prop, buf);
if (cir > ret) ret = cir;
continue;
}
switch (prop) {
case 0x08: { // Substitute industry type
uint8_t subs_id = buf.ReadByte();
if (subs_id == 0xFF) {
/* Instead of defining a new industry, a substitute industry id
* of 0xFF disables the old industry with the current id. */
_industry_specs[id].enabled = false;
continue;
} else if (subs_id >= NEW_INDUSTRYOFFSET) {
/* The substitute id must be one of the original industry. */
GrfMsg(2, "_industry_specs: Attempt to use new industry {} as substitute industry for {}. Ignoring.", subs_id, id);
continue;
}
/* Allocate space for this industry.
* Only need to do it once. If ever it is called again, it should not
* do anything */
if (indsp == nullptr) {
indsp = std::make_unique<IndustrySpec>(_origin_industry_specs[subs_id]);
indsp->enabled = true;
indsp->grf_prop.local_id = id;
indsp->grf_prop.subst_id = subs_id;
indsp->grf_prop.SetGRFFile(_cur.grffile);
/* If the grf industry needs to check its surrounding upon creation, it should
* rely on callbacks, not on the original placement functions */
indsp->check_proc = CHECK_NOTHING;
}
break;
}
case 0x09: { // Industry type override
uint8_t ovrid = buf.ReadByte();
/* The industry being overridden must be an original industry. */
if (ovrid >= NEW_INDUSTRYOFFSET) {
GrfMsg(2, "IndustriesChangeInfo: Attempt to override new industry {} with industry id {}. Ignoring.", ovrid, id);
continue;
}
indsp->grf_prop.override_id = ovrid;
_industry_mngr.Add(id, _cur.grffile->grfid, ovrid);
break;
}
case 0x0A: { // Set industry layout(s)
uint8_t new_num_layouts = buf.ReadByte();
uint32_t definition_size = buf.ReadDWord();
uint32_t bytes_read = 0;
std::vector<IndustryTileLayout> new_layouts;
IndustryTileLayout layout;
for (uint8_t j = 0; j < new_num_layouts; j++) {
layout.clear();
for (uint k = 0;; k++) {
if (bytes_read >= definition_size) {
GrfMsg(3, "IndustriesChangeInfo: Incorrect size for industry tile layout definition for industry {}.", id);
/* Avoid warning twice */
definition_size = UINT32_MAX;
}
IndustryTileLayoutTile &it = layout.emplace_back();
it.ti.x = buf.ReadByte(); // Offsets from northermost tile
++bytes_read;
if (it.ti.x == 0xFE && k == 0) {
/* This means we have to borrow the layout from an old industry */
IndustryType type = buf.ReadByte();
uint8_t laynbr = buf.ReadByte();
bytes_read += 2;
if (type >= lengthof(_origin_industry_specs)) {
GrfMsg(1, "IndustriesChangeInfo: Invalid original industry number for layout import, industry {}", id);
DisableGrf(STR_NEWGRF_ERROR_INVALID_ID);
return CIR_DISABLED;
}
if (laynbr >= _origin_industry_specs[type].layouts.size()) {
GrfMsg(1, "IndustriesChangeInfo: Invalid original industry layout index for layout import, industry {}", id);
DisableGrf(STR_NEWGRF_ERROR_INVALID_ID);
return CIR_DISABLED;
}
layout = _origin_industry_specs[type].layouts[laynbr];
break;
}
it.ti.y = buf.ReadByte(); // Or table definition finalisation
++bytes_read;
if (it.ti.x == 0 && it.ti.y == 0x80) {
/* Terminator, remove and finish up */
layout.pop_back();
break;
}
it.gfx = buf.ReadByte();
++bytes_read;
if (it.gfx == 0xFE) {
/* Use a new tile from this GRF */
int local_tile_id = buf.ReadWord();
bytes_read += 2;
/* Read the ID from the _industile_mngr. */
int tempid = _industile_mngr.GetID(local_tile_id, _cur.grffile->grfid);
if (tempid == INVALID_INDUSTRYTILE) {
GrfMsg(2, "IndustriesChangeInfo: Attempt to use industry tile {} with industry id {}, not yet defined. Ignoring.", local_tile_id, id);
} else {
/* Declared as been valid, can be used */
it.gfx = tempid;
}
} else if (it.gfx == GFX_WATERTILE_SPECIALCHECK) {
it.ti.x = (int8_t)GB(it.ti.x, 0, 8);
it.ti.y = (int8_t)GB(it.ti.y, 0, 8);
/* When there were only 256x256 maps, TileIndex was a uint16_t and
* it.ti was just a TileIndexDiff that was added to it.
* As such negative "x" values were shifted into the "y" position.
* x = -1, y = 1 -> x = 255, y = 0
* Since GRF version 8 the position is interpreted as pair of independent int8.
* For GRF version < 8 we need to emulate the old shifting behaviour.
*/
if (_cur.grffile->grf_version < 8 && it.ti.x < 0) it.ti.y += 1;
}
}
if (!ValidateIndustryLayout(layout)) {
/* The industry layout was not valid, so skip this one. */
GrfMsg(1, "IndustriesChangeInfo: Invalid industry layout for industry id {}. Ignoring", id);
} else {
new_layouts.push_back(layout);
}
}
/* Install final layout construction in the industry spec */
indsp->layouts = std::move(new_layouts);
break;
}
case 0x0B: // Industry production flags
indsp->life_type = IndustryLifeTypes{buf.ReadByte()};
break;
case 0x0C: // Industry closure message
AddStringForMapping(GRFStringID{buf.ReadWord()}, &indsp->closure_text);
break;
case 0x0D: // Production increase message
AddStringForMapping(GRFStringID{buf.ReadWord()}, &indsp->production_up_text);
break;
case 0x0E: // Production decrease message
AddStringForMapping(GRFStringID{buf.ReadWord()}, &indsp->production_down_text);
break;
case 0x0F: // Fund cost multiplier
indsp->cost_multiplier = buf.ReadByte();
break;
case 0x10: // Production cargo types
for (uint8_t j = 0; j < INDUSTRY_ORIGINAL_NUM_OUTPUTS; j++) {
indsp->produced_cargo[j] = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
indsp->produced_cargo_label[j] = CT_INVALID;
}
break;
case 0x11: // Acceptance cargo types
for (uint8_t j = 0; j < INDUSTRY_ORIGINAL_NUM_INPUTS; j++) {
indsp->accepts_cargo[j] = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
indsp->accepts_cargo_label[j] = CT_INVALID;
}
buf.ReadByte(); // Unnused, eat it up
break;
case 0x12: // Production multipliers
case 0x13:
indsp->production_rate[prop - 0x12] = buf.ReadByte();
break;
case 0x14: // Minimal amount of cargo distributed
indsp->minimal_cargo = buf.ReadByte();
break;
case 0x15: { // Random sound effects
uint8_t num_sounds = buf.ReadByte();
std::vector<uint8_t> sounds;
sounds.reserve(num_sounds);
for (uint8_t j = 0; j < num_sounds; ++j) {
sounds.push_back(buf.ReadByte());
}
indsp->random_sounds = std::move(sounds);
break;
}
case 0x16: // Conflicting industry types
for (uint8_t j = 0; j < 3; j++) indsp->conflicting[j] = buf.ReadByte();
break;
case 0x17: // Probability in random game
indsp->appear_creation[to_underlying(_settings_game.game_creation.landscape)] = buf.ReadByte();
break;
case 0x18: // Probability during gameplay
indsp->appear_ingame[to_underlying(_settings_game.game_creation.landscape)] = buf.ReadByte();
break;
case 0x19: // Map colour
indsp->map_colour = buf.ReadByte();
break;
case 0x1A: // Special industry flags to define special behavior
indsp->behaviour = IndustryBehaviours{buf.ReadDWord()};
break;
case 0x1B: // New industry text ID
AddStringForMapping(GRFStringID{buf.ReadWord()}, &indsp->new_industry_text);
break;
case 0x1C: // Input cargo multipliers for the three input cargo types
case 0x1D:
case 0x1E: {
uint32_t multiples = buf.ReadDWord();
indsp->input_cargo_multiplier[prop - 0x1C][0] = GB(multiples, 0, 16);
indsp->input_cargo_multiplier[prop - 0x1C][1] = GB(multiples, 16, 16);
break;
}
case 0x1F: // Industry name
AddStringForMapping(GRFStringID{buf.ReadWord()}, &indsp->name);
break;
case 0x20: // Prospecting success chance
indsp->prospecting_chance = buf.ReadDWord();
break;
case 0x21: // Callback mask
case 0x22: { // Callback additional mask
auto mask = indsp->callback_mask.base();
SB(mask, (prop - 0x21) * 8, 8, buf.ReadByte());
indsp->callback_mask = IndustryCallbackMasks{mask};
break;
}
case 0x23: // removal cost multiplier
indsp->removal_cost_multiplier = buf.ReadDWord();
break;
case 0x24: { // name for nearby station
GRFStringID str{buf.ReadWord()};
if (str == 0) {
indsp->station_name = STR_NULL;
} else {
AddStringForMapping(str, &indsp->station_name);
}
break;
}
case 0x25: { // variable length produced cargoes
uint8_t num_cargoes = buf.ReadByte();
if (num_cargoes > std::size(indsp->produced_cargo)) {
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
error->param_value[1] = prop;
return CIR_DISABLED;
}
for (size_t i = 0; i < std::size(indsp->produced_cargo); i++) {
if (i < num_cargoes) {
CargoType cargo = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
indsp->produced_cargo[i] = cargo;
} else {
indsp->produced_cargo[i] = INVALID_CARGO;
}
if (i < std::size(indsp->produced_cargo_label)) indsp->produced_cargo_label[i] = CT_INVALID;
}
break;
}
case 0x26: { // variable length accepted cargoes
uint8_t num_cargoes = buf.ReadByte();
if (num_cargoes > std::size(indsp->accepts_cargo)) {
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
error->param_value[1] = prop;
return CIR_DISABLED;
}
for (size_t i = 0; i < std::size(indsp->accepts_cargo); i++) {
if (i < num_cargoes) {
CargoType cargo = GetCargoTranslation(buf.ReadByte(), _cur.grffile);
indsp->accepts_cargo[i] = cargo;
} else {
indsp->accepts_cargo[i] = INVALID_CARGO;
}
if (i < std::size(indsp->accepts_cargo_label)) indsp->accepts_cargo_label[i] = CT_INVALID;
}
break;
}
case 0x27: { // variable length production rates
uint8_t num_cargoes = buf.ReadByte();
if (num_cargoes > lengthof(indsp->production_rate)) {
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
error->param_value[1] = prop;
return CIR_DISABLED;
}
for (uint i = 0; i < lengthof(indsp->production_rate); i++) {
if (i < num_cargoes) {
indsp->production_rate[i] = buf.ReadByte();
} else {
indsp->production_rate[i] = 0;
}
}
break;
}
case 0x28: { // variable size input/output production multiplier table
uint8_t num_inputs = buf.ReadByte();
uint8_t num_outputs = buf.ReadByte();
if (num_inputs > std::size(indsp->accepts_cargo) || num_outputs > std::size(indsp->produced_cargo)) {
GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LIST_PROPERTY_TOO_LONG);
error->param_value[1] = prop;
return CIR_DISABLED;
}
for (size_t i = 0; i < std::size(indsp->accepts_cargo); i++) {
for (size_t j = 0; j < std::size(indsp->produced_cargo); j++) {
uint16_t mult = 0;
if (i < num_inputs && j < num_outputs) mult = buf.ReadWord();
indsp->input_cargo_multiplier[i][j] = mult;
}
}
break;
}
case 0x29: // Badge list
indsp->badges = ReadBadgeList(buf, GSF_INDUSTRIES);
break;
default:
ret = CIR_UNKNOWN;
break;
}
}
return ret;
}
template <> ChangeInfoResult GrfChangeInfoHandler<GSF_INDUSTRYTILES>::Reserve(uint, uint, int, ByteReader &) { return CIR_UNHANDLED; }
template <> ChangeInfoResult GrfChangeInfoHandler<GSF_INDUSTRYTILES>::Activation(uint first, uint last, int prop, ByteReader &buf) { return IndustrytilesChangeInfo(first, last, prop, buf); }
template <> ChangeInfoResult GrfChangeInfoHandler<GSF_INDUSTRIES>::Reserve(uint, uint, int, ByteReader &) { return CIR_UNHANDLED; }
template <> ChangeInfoResult GrfChangeInfoHandler<GSF_INDUSTRIES>::Activation(uint first, uint last, int prop, ByteReader &buf) { return IndustriesChangeInfo(first, last, prop, buf); }