diff --git a/src/newgrf.cpp b/src/newgrf.cpp --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -5952,6 +5952,78 @@ static bool ChangeGRFPalette(size_t len, return true; } + +static GRFParameterInfo *_cur_parameter; ///< The parameter which info is currently changed by the newgrf. + +/** Callback function for 'INFO'->'PARAM'->param_num->'NAME' to set the name of a parameter. */ +static bool ChangeGRFParamName(byte langid, const char *str) +{ + AddGRFTextToList(&_cur_parameter->name, langid, _cur_grfconfig->ident.grfid, str); + return true; +} + +/** Callback function for 'INFO'->'PARAM'->param_num->'DESC' to set the description of a parameter. */ +static bool ChangeGRFParamDescription(byte langid, const char *str) +{ + AddGRFTextToList(&_cur_parameter->desc, langid, _cur_grfconfig->ident.grfid, str); + return true; +} + +/** Callback function for 'INFO'->'PARAM'->param_num->'TYPE' to set the typeof a parameter. */ +static bool ChangeGRFParamType(size_t len, ByteReader *buf) +{ + if (len != 1) { + grfmsg(2, "StaticGRFInfo: expected 1 byte for 'INFO'->'PARA'->'TYPE' but got " PRINTF_SIZE ", ignoring this field", len); + buf->Skip(len); + } else { + GRFParameterType type = (GRFParameterType)buf->ReadByte(); + if (type < PTYPE_END) { + _cur_parameter->type = type; + } else { + grfmsg(3, "StaticGRFInfo: unknown parameter type %d, ignoring this field", type); + } + } + return true; +} + +/** Callback function for 'INFO'->'PARAM'->param_num->'LIMI' to set the min/max value of a parameter. */ +static bool ChangeGRFParamLimits(size_t len, ByteReader *buf) +{ + if (_cur_parameter->type != PTYPE_UINT_ENUM) { + grfmsg(2, "StaticGRFInfo: 'INFO'->'PARA'->'LIMI' is only valid for parameters with type uint/enum, ignoring this field"); + buf->Skip(len); + } else if (len != 8) { + grfmsg(2, "StaticGRFInfo: expected 8 bytes for 'INFO'->'PARA'->'LIMI' but got " PRINTF_SIZE ", ignoring this field", len); + buf->Skip(len); + } else { + _cur_parameter->min_value = buf->ReadDWord(); + _cur_parameter->max_value = buf->ReadDWord(); + } + return true; +} + +/** Callback function for 'INFO'->'PARAM'->param_num->'MASK' to set the parameter and bits to use. */ +static bool ChangeGRFParamMask(size_t len, ByteReader *buf) +{ + if (len < 1 || len > 3) { + grfmsg(2, "StaticGRFInfo: expected 1 to 3 bytes for 'INFO'->'PARA'->'MASK' but got " PRINTF_SIZE ", ignoring this field", len); + buf->Skip(len); + } else { + byte param_nr = buf->ReadByte(); + if (param_nr >= lengthof(_cur_grfconfig->param)) { + grfmsg(2, "StaticGRFInfo: invalid parameter number in 'INFO'->'PARA'->'MASK', param %d, ignoring this field", param_nr); + buf->Skip(len - 1); + } else { + _cur_parameter->param_nr = param_nr; + if (len >= 2) _cur_parameter->first_bit = min(buf->ReadByte(), 31); + if (len >= 3) _cur_parameter->num_bit = min(buf->ReadByte(), 32 - _cur_parameter->first_bit); + } + } + + return true; +} + + typedef bool (*DataHandler)(size_t, ByteReader *); ///< Type of callback function for binary nodes typedef bool (*TextHandler)(byte, const char *str); ///< Type of callback function for text nodes typedef bool (*BranchHandler)(ByteReader *); ///< Type of callback function for branch nodes @@ -6035,11 +6107,95 @@ struct AllowedSubtags { } handler; }; +static bool SkipUnknownInfo(ByteReader *buf, byte type); +static bool HandleNode(byte type, uint32 id, ByteReader *buf, AllowedSubtags *tags); + +/** + * Callback function for 'INFO'->'PARA'->param_num->'VALU' to set the names + * of some parameter values (type uint/enum) or the names of some bits + * (type bitmask). In both cases the format is the same: + * Each subnode should be a text node with the value/bit number as id. + */ +static bool ChangeGRFParamValueNames(ByteReader *buf) +{ + byte type = buf->ReadByte(); + while (type != 0) { + uint32 id = buf->ReadDWord(); + if (type != 'T' || id > _cur_parameter->max_value) { + grfmsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA'->param_num->'VALU' should have type 't' and the value/bit number as id"); + if (!SkipUnknownInfo(buf, type)) return false; + } + + byte langid = buf->ReadByte(); + const char *name_string = buf->ReadString(); + + SmallPair *val_name = _cur_parameter->value_names.Find(id); + if (val_name != _cur_parameter->value_names.End()) { + AddGRFTextToList(&val_name->second, langid, _cur_grfconfig->ident.grfid, name_string); + } else { + GRFText *list = NULL; + AddGRFTextToList(&list, langid, _cur_grfconfig->ident.grfid, name_string); + _cur_parameter->value_names.Insert(id, list); + } + + type = buf->ReadByte(); + } + return true; +} + +AllowedSubtags _tags_parameters[] = { + AllowedSubtags('NAME', ChangeGRFParamName), + AllowedSubtags('DESC', ChangeGRFParamDescription), + AllowedSubtags('TYPE', ChangeGRFParamType), + AllowedSubtags('LIMI', ChangeGRFParamLimits), + AllowedSubtags('MASK', ChangeGRFParamMask), + AllowedSubtags('VALU', ChangeGRFParamValueNames), + AllowedSubtags() +}; + +/** + * Callback function for 'INFO'->'PARA' to set extra information about the + * parameters. Each subnode of 'INFO'->'PARA' should be a branch node with + * the parameter number as id. The first parameter has id 0. The maximum + * parameter that can be changed is set by 'INFO'->'NPAR' which defaults to 80. + */ +static bool HandleParameterInfo(ByteReader *buf) +{ + byte type = buf->ReadByte(); + while (type != 0) { + uint32 id = buf->ReadDWord(); + if (type != 'C' || id >= _cur_grfconfig->num_valid_params) { + grfmsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA' should have type 'C' and their parameter number as id"); + if (!SkipUnknownInfo(buf, type)) return false; + } + + if (id >= _cur_grfconfig->param_info.Length()) { + uint num_to_add = id - _cur_grfconfig->param_info.Length() + 1; + GRFParameterInfo **newdata = _cur_grfconfig->param_info.Append(num_to_add); + MemSetT(newdata, 0, num_to_add); + } + if (_cur_grfconfig->param_info[id] == NULL) { + _cur_grfconfig->param_info[id] = new GRFParameterInfo(id); + } + _cur_parameter = _cur_grfconfig->param_info[id]; + /* Read all parameter-data and process each node. */ + byte sub_type = buf->ReadByte(); + while (sub_type != 0) { + uint32 sub_id = buf->ReadDWord(); + if (!HandleNode(sub_type, sub_id, buf, _tags_parameters)) return false; + sub_type = buf->ReadByte(); + } + type = buf->ReadByte(); + } + return true; +} + AllowedSubtags _tags_info[] = { AllowedSubtags('NAME', ChangeGRFName), AllowedSubtags('DESC', ChangeGRFDescription), AllowedSubtags('NPAR', ChangeGRFNumUsedParams), AllowedSubtags('PALS', ChangeGRFPalette), + AllowedSubtags('PARA', HandleParameterInfo), AllowedSubtags() }; diff --git a/src/newgrf_config.cpp b/src/newgrf_config.cpp --- a/src/newgrf_config.cpp +++ b/src/newgrf_config.cpp @@ -51,6 +51,13 @@ GRFConfig::GRFConfig(const GRFConfig &co this->name = DuplicateGRFText(config.name); this->info = DuplicateGRFText(config.info); if (config.error != NULL) this->error = new GRFError(*config.error); + for (uint i = 0; i < config.param_info.Length(); i++) { + if (config.param_info[i] == NULL) { + *this->param_info.Append() = NULL; + } else { + *this->param_info.Append() = new GRFParameterInfo(*config.param_info[i]); + } + } } /** Cleanup a GRFConfig object. */ @@ -63,6 +70,8 @@ GRFConfig::~GRFConfig() delete this->error; } CleanUpGRFText(this->name); + + for (uint i = 0; i < this->param_info.Length(); i++) delete this->param_info[i]; } /** @@ -140,6 +149,53 @@ GRFError::~GRFError() } /** + * Create a new empty GRFParameterInfo object. + * @param nr The newgrf parameter that is changed. + */ +GRFParameterInfo::GRFParameterInfo(uint nr) : + name(NULL), + desc(NULL), + type(PTYPE_UINT_ENUM), + min_value(0), + max_value(UINT32_MAX), + param_nr(nr), + first_bit(0), + num_bit(32) +{} + +/** + * Create a new GRFParameterInfo object that is a deep copy of an existing + * parameter info object. + * @param info The GRFParameterInfo object to make a copy of. + */ +GRFParameterInfo::GRFParameterInfo(GRFParameterInfo &info) : + name(DuplicateGRFText(info.name)), + desc(DuplicateGRFText(info.desc)), + type(info.type), + min_value(info.min_value), + max_value(info.max_value), + param_nr(info.param_nr), + first_bit(info.first_bit), + num_bit(info.num_bit) +{ + for (uint i = 0; i < info.value_names.Length(); i++) { + SmallPair *data = info.value_names.Get(i); + this->value_names.Insert(data->first, DuplicateGRFText(data->second)); + } +} + +/** Cleanup all parameter info. */ +GRFParameterInfo::~GRFParameterInfo() +{ + CleanUpGRFText(this->name); + CleanUpGRFText(this->desc); + for (uint i = 0; i < this->value_names.Length(); i++) { + SmallPair *data = this->value_names.Get(i); + CleanUpGRFText(data->second); + } +} + +/** * Update the palettes of the graphics from the config file. * This is needed because the config file gets read and parsed * before the palette is chosen (one can configure the base diff --git a/src/newgrf_config.h b/src/newgrf_config.h --- a/src/newgrf_config.h +++ b/src/newgrf_config.h @@ -14,6 +14,7 @@ #include "strings_type.h" #include "core/alloc_type.hpp" +#include "core/smallmap_type.hpp" /** GRF config bit flags */ enum GCF_Flags { @@ -99,6 +100,29 @@ struct GRFError : ZeroedMemoryAllocator uint32 param_value[2]; ///< Values of GRF parameters to show for message and custom_message }; +/** The possible types of a newgrf parameter. */ +enum GRFParameterType { + PTYPE_UINT_ENUM, ///< The parameter allows a range of numbers, each of which can have a special name + PTYPE_BOOL, ///< The parameter is either 0 or 1 + PTYPE_END, ///< Invalid parameter type +}; + +/** Information about one grf parameter. */ +struct GRFParameterInfo { + GRFParameterInfo(uint nr); + GRFParameterInfo(GRFParameterInfo &info); + ~GRFParameterInfo(); + struct GRFText *name; ///< The name of this parameter + struct GRFText *desc; ///< The description of this parameter + GRFParameterType type; ///< The type of this parameter + uint32 min_value; ///< The minimal value this parameter can have + uint32 max_value; ///< The maximal value of this parameter + byte param_nr; ///< GRF parameter to store content in + byte first_bit; ///< First bit to use in the GRF parameter + byte num_bit; ///< Number of bits to use for this parameter + SmallMap value_names; ///< Names for each value. +}; + /** Information about GRF, used in the game and (part of it) in savegames */ struct GRFConfig : ZeroedMemoryAllocator { GRFConfig(const char *filename = NULL); @@ -119,6 +143,7 @@ struct GRFConfig : ZeroedMemoryAllocator uint8 num_params; ///< Number of used parameters uint8 num_valid_params; ///< NOSAVE: Number of valid parameters (action 0x14) uint8 palette; ///< GRFPalette, bitset + SmallVector param_info; ///< NOSAVE: extra information about the parameters struct GRFConfig *next; ///< NOSAVE: Next item in the linked list