diff --git a/docs/newgrf-additions.html b/docs/newgrf-additions.html index 9e1231b1aa..04f383150d 100644 --- a/docs/newgrf-additions.html +++ b/docs/newgrf-additions.html @@ -18,7 +18,7 @@ -

Additions to NewGRF Specifications in thi branch

+

Additions to NewGRF Specifications in this branch

This document describes non-standard additions to the Official OpenTTD NewGRF Specifications which are present in this branch.

This additions MAY also be present in other branches/repositories/etc. They MAY be removed or moved in future, if necessary.

NewGRFs which use any of these features SHOULD use the feature testing mechanism described below to check whether individual added features are supported.

@@ -72,7 +72,7 @@ -1 * -1 07 9D 01 \70 04 01
-

Action 14 - Property Mapping for Action 0

+

Action 14 - Property Mapping for Action 0

See Action 14 Specification and Action 0 Specification for background information.

The property mapping mechanism has the feature name: property_mapping, this document describes version 1.

Users of this mechanism SHOULD at minimum test for the presence of the feature above or test variable 8D, below.

@@ -92,7 +92,7 @@

Property ID: C "A0PM" -> B "PROP"

Within an A0PM chunk, the PROP binary (type B) field contains the property ID to allocate to the named property, this value can used in Action 0 sprites. This is 1 byte.
It is possible to override existing properties, however this use is not recommended.

-

Success Indicator Global Variable 0x8D Bit: C "A0PM" -> B "SETT"

+

Success Indicator Global Variable 0x8D Bit: C "A0PM" -> B "SETT"

Within an A0PM chunk, the SETT binary (type B) field contains the bit number to set/clear in global variable 0x8D (TTD version) to store whether the mapping operation was successful. This is 1 byte.
If the operation is successful, the bit is set (to 1), otherwise the bit is cleared (to 0).
@@ -100,7 +100,7 @@ Global variable 0x8D can then be tested by using a standard Action 7 or 9, or a standard Variational Action 2.
If this field is omitted, no bit is set or cleared.

-

Fallback Mode: C "A0PM" -> B "FLBK"

+

Fallback Mode: C "A0PM" -> B "FLBK"

Within an A0PM chunk, the FLBK binary (type B) field contains the fallback mode. This is 1 byte.
The fallback mode may take the following values: @@ -140,5 +140,46 @@ // Set sample_station_property for station ID 10 to 2 byte value: AB CD -1 * -1 00 04 01 01 10 F8 02 AB CD +
+

Action 14 - Type ID Mapping for Action 5

+

See Action 14 Specification and Action 5 Specification for background information.

+

The action 5 type ID mapping mechanism has the feature name: action5_type_id_mapping, this document describes version 1.

+

Users of this mechanism SHOULD at minimum test for the presence of the feature above or test variable 8D, below.

+

Action 5 type ID Mapping: C "A5TM"

+

Each A5TM chunk (type C) describes an individual action 5 type ID mapping.
+ Sub-chunks within each A5TM chunk may appear in any order, however except where otherwise noted each sub-chunk SHOULD only appear ONCE within an individual A5TM chunk.

+

Action 5 type ID mapping can be safely used on implementations which do not implement the type ID mapping mechanism if Action 5 sprites which use mapped type IDs are skipped if one or more of: +

+ Unknown Action 14 blocks are ignored, and do not need to be skipped.

+

Property Name: C "A5TM" -> T "NAME"

+

Within an A5TM chunk, the NAME text (type T) field contains the name of the type to map. The value of the language ID byte is ignored.

+

Property ID: C "A5TM" -> B "TYPE"

+

Within an A5TM chunk, the TYPE binary (type B) field contains the type ID to allocate to the named type, this value can used in Action 5 sprites. This is 1 byte. The value MUST be < 128 (i.e bit 7 must be clear).
+ It is possible to override existing type IDs, however this use is not recommended.

+

Success Indicator Global Variable 0x8D Bit: C "A5TM" -> B "SETT"

+

This behaves identically to the C "A0PM" -> B "SETT" case, above

+

Fallback Mode: C "A5TM" -> B "FLBK"

+

This behaves identically to the C "A0PM" -> B "FLBK" case, above

+

Example NFO:

+
+// Map action5 type "sample_action5_type" to type id 0x70, and set bit 5 of global variable 0x8D if successful
+-1 * -1 14
+	"C" "A5TM"
+		"T" "NAME" 00 "sample_action5_type" 00
+		"B" "TYPE" \w1 70
+		"B" "SETT" \w1 5
+		00
+	00
+....
+// Skip 3 sprites if bit 5 of global variable 0x8D is not set (indicating that station property sample_action5_type is NOT present)
+-1 * -1  07 8D 01 \70 05 03
+// Set 2 sprites at offset 7 into sample_action5_type
+-1 * -1  05 F0 02 07
+-1  sprites/sample.png 546 8 09 23 33 -26 0
+-1  sprites/sample.png 594 8 09 23 33 -5 0
+	
diff --git a/src/lang/english.txt b/src/lang/english.txt index 71b5abba68..8089cb021f 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2964,6 +2964,7 @@ STR_NEWGRF_ERROR_STATIC_GRF_CAUSES_DESYNC :Loading {1:RAW_ STR_NEWGRF_ERROR_UNEXPECTED_SPRITE :Unexpected sprite (sprite {3:NUM}) STR_NEWGRF_ERROR_UNKNOWN_PROPERTY :Unknown Action 0 property {4:HEX} (sprite {3:NUM}) STR_NEWGRF_ERROR_UNIMPLEMETED_MAPPED_PROPERTY :Unimplemented remapped Action 0 property feature: {4:HEX}, name: {2:RAW_STRING}, mapped to: {5:HEX} (sprite {3:NUM}) +STR_NEWGRF_ERROR_UNIMPLEMETED_MAPPED_ACTION5_TYPE :Unimplemented remapped Action 5 type: name: {2:RAW_STRING}, mapped to: {4:HEX} (sprite {3:NUM}) STR_NEWGRF_ERROR_INVALID_ID :Attempt to use invalid ID (sprite {3:NUM}) STR_NEWGRF_ERROR_CORRUPT_SPRITE :{YELLOW}{RAW_STRING} contains a corrupt sprite. All corrupt sprites will be shown as a red question mark (?) STR_NEWGRF_ERROR_MULTIPLE_ACTION_8 :Contains multiple Action 8 entries (sprite {3:NUM}) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 2d05e63d76..c98bc40721 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -5811,22 +5811,6 @@ static uint16 SanitizeSpriteOffset(uint16& num, uint16 offset, int max_sprites, return 0; } - -/** The type of action 5 type. */ -enum Action5BlockType { - A5BLOCK_FIXED, ///< Only allow replacing a whole block of sprites. (TTDP compatible) - A5BLOCK_ALLOW_OFFSET, ///< Allow replacing any subset by specifiing an offset. - A5BLOCK_INVALID, ///< unknown/not-implemented type -}; -/** Information about a single action 5 type. */ -struct Action5Type { - Action5BlockType block_type; ///< How is this Action5 type processed? - SpriteID sprite_base; ///< Load the sprites starting from this sprite. - uint16 min_sprites; ///< If the Action5 contains less sprites, the whole block will be ignored. - uint16 max_sprites; ///< If the Action5 contains more sprites, only the first max_sprites sprites will be used. - const char *name; ///< Name for error messages. -}; - /** The information about action 5 types. */ static const Action5Type _action5_types[] = { /* Note: min_sprites should not be changed. Therefore these constants are directly here and not in sprites.h */ @@ -5872,32 +5856,54 @@ static void GraphicsNew(ByteReader *buf) uint16 offset = HasBit(type, 7) ? buf->ReadExtendedByte() : 0; ClrBit(type, 7); // Clear the high bit as that only indicates whether there is an offset. - if ((type == 0x0D) && (num == 10) && HasBit(_cur.grfconfig->flags, GCF_SYSTEM)) { - /* Special not-TTDP-compatible case used in openttd.grf - * Missing shore sprites and initialisation of SPR_SHORE_BASE */ - grfmsg(2, "GraphicsNew: Loading 10 missing shore sprites from extra grf."); - LoadNextSprite(SPR_SHORE_BASE + 0, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_S - LoadNextSprite(SPR_SHORE_BASE + 5, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_W - LoadNextSprite(SPR_SHORE_BASE + 7, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_WSE - LoadNextSprite(SPR_SHORE_BASE + 10, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_N - LoadNextSprite(SPR_SHORE_BASE + 11, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NWS - LoadNextSprite(SPR_SHORE_BASE + 13, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_ENW - LoadNextSprite(SPR_SHORE_BASE + 14, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_SEN - LoadNextSprite(SPR_SHORE_BASE + 15, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_E - LoadNextSprite(SPR_SHORE_BASE + 16, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_EW - LoadNextSprite(SPR_SHORE_BASE + 17, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NS - if (_loaded_newgrf_features.shore == SHORE_REPLACE_NONE) _loaded_newgrf_features.shore = SHORE_REPLACE_ONLY_NEW; - return; - } + const Action5Type *action5_type; + const Action5TypeRemapSet &remap = _cur.grffile->action5_type_remaps; + if (remap.remapped_ids[type]) { + auto iter = remap.mapping.find(type); + assert(iter != remap.mapping.end()); + const Action5TypeRemapEntry &def = iter->second; + if (def.info == nullptr) { + if (def.fallback_mode == GPMFM_ERROR_ON_USE) { + grfmsg(0, "Error: Unimplemented action 5 type: %s, mapped to: %X", def.name, type); + GRFError *error = DisableGrf(STR_NEWGRF_ERROR_UNIMPLEMETED_MAPPED_ACTION5_TYPE); + error->data = stredup(def.name); + error->param_value[1] = type; + } else if (def.fallback_mode == GPMFM_IGNORE) { + grfmsg(2, "Ignoring unimplemented action 5 type: %s, mapped to: %X", def.name, type); + } + _cur.skip_sprites = num; + return; + } else { + action5_type = def.info; + } + } else { + if ((type == 0x0D) && (num == 10) && HasBit(_cur.grfconfig->flags, GCF_SYSTEM)) { + /* Special not-TTDP-compatible case used in openttd.grf + * Missing shore sprites and initialisation of SPR_SHORE_BASE */ + grfmsg(2, "GraphicsNew: Loading 10 missing shore sprites from extra grf."); + LoadNextSprite(SPR_SHORE_BASE + 0, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_S + LoadNextSprite(SPR_SHORE_BASE + 5, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_W + LoadNextSprite(SPR_SHORE_BASE + 7, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_WSE + LoadNextSprite(SPR_SHORE_BASE + 10, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_N + LoadNextSprite(SPR_SHORE_BASE + 11, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NWS + LoadNextSprite(SPR_SHORE_BASE + 13, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_ENW + LoadNextSprite(SPR_SHORE_BASE + 14, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_SEN + LoadNextSprite(SPR_SHORE_BASE + 15, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_STEEP_E + LoadNextSprite(SPR_SHORE_BASE + 16, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_EW + LoadNextSprite(SPR_SHORE_BASE + 17, _cur.file_index, _cur.nfo_line++, _cur.grf_container_ver); // SLOPE_NS + if (_loaded_newgrf_features.shore == SHORE_REPLACE_NONE) _loaded_newgrf_features.shore = SHORE_REPLACE_ONLY_NEW; + return; + } - /* Supported type? */ - if ((type >= lengthof(_action5_types)) || (_action5_types[type].block_type == A5BLOCK_INVALID)) { - grfmsg(2, "GraphicsNew: Custom graphics (type 0x%02X) sprite block of length %u (unimplemented, ignoring)", type, num); - _cur.skip_sprites = num; - return; - } + /* Supported type? */ + if ((type >= lengthof(_action5_types)) || (_action5_types[type].block_type == A5BLOCK_INVALID)) { + grfmsg(2, "GraphicsNew: Custom graphics (type 0x%02X) sprite block of length %u (unimplemented, ignoring)", type, num); + _cur.skip_sprites = num; + return; + } - const Action5Type *action5_type = &_action5_types[type]; + action5_type = &_action5_types[type]; + } /* Contrary to TTDP we allow always to specify too few sprites as we allow always an offset, * except for the long version of the shore type: @@ -7929,6 +7935,7 @@ struct GRFFeatureInfo { static const GRFFeatureInfo _grf_feature_list[] = { GRFFeatureInfo("feature_test", 1), GRFFeatureInfo("property_mapping", 1), + GRFFeatureInfo("action5_type_id_mapping", 1), GRFFeatureInfo(), }; @@ -8043,6 +8050,11 @@ static const GRFPropertyMapDefinition _grf_action0_remappable_properties[] = { GRFPropertyMapDefinition(), }; +/** Action14 Action5 remappable type list */ +static const Action5TypeRemapDefinition _grf_action5_remappable_types[] = { + Action5TypeRemapDefinition(), +}; + /** Action14 Action0 property map action instance */ struct GRFPropertyMapAction { const char *tag_name = NULL; @@ -8107,7 +8119,7 @@ struct GRFPropertyMapAction { const char *str_store = stredup(str); grfmsg(2, "Unimplemented mapped %s: %s, feature: %X, mapped to: %X, %s on use", this->descriptor, str, this->feature, this->prop_id, (this->fallback_mode == GPMFM_IGNORE) ? "ignoring" : "error"); - *(_cur.grffile->action0_unknown_property_names.Append()) = str_store; + *(_cur.grffile->remap_unknown_property_names.Append()) = str_store; GRFFilePropertyRemapEntry &entry = _cur.grffile->action0_property_remaps[this->feature].Entry(this->prop_id); entry.name = str_store; entry.id = (this->fallback_mode == GPMFM_IGNORE) ? A0RPI_UNKNOWN_IGNORE : A0RPI_UNKNOWN_ERROR; @@ -8116,6 +8128,51 @@ struct GRFPropertyMapAction { } } } + + void ExecuteAction5TypeRemapping() + { + if (this->prop_id < 0) { + grfmsg(2, "Action 14 %s remapping: no type ID defined, doing nothing", this->descriptor); + return; + } + if (this->name.empty()) { + grfmsg(2, "Action 14 %s remapping: no name defined, doing nothing", this->descriptor); + return; + } + bool success = false; + const char *str = this->name.c_str(); + for (const Action5TypeRemapDefinition *info = _grf_action5_remappable_types; info->name != NULL; info++) { + if (strcmp(info->name, str) == 0) { + Action5TypeRemapEntry &entry = _cur.grffile->action5_type_remaps.Entry(this->prop_id); + entry.name = info->name; + entry.info = &(info->info); + entry.type_id = this->prop_id; + success = true; + break; + } + } + if (this->ttd_ver_var_bit > 0) { + SB(_cur.grffile->var8D_overlay, this->ttd_ver_var_bit, 1, success ? 1 : 0); + } + if (!success) { + if (this->fallback_mode == GPMFM_ERROR_ON_DEFINITION) { + grfmsg(0, "Error: Unimplemented mapped %s: %s, mapped to: %X", this->descriptor, str, this->prop_id); + GRFError *error = DisableGrf(STR_NEWGRF_ERROR_UNIMPLEMETED_MAPPED_ACTION5_TYPE); + error->data = stredup(str); + error->param_value[1] = this->prop_id; + } else { + const char *str_store = stredup(str); + grfmsg(2, "Unimplemented mapped %s: %s, mapped to: %X, %s on use", + this->descriptor, str, this->prop_id, (this->fallback_mode == GPMFM_IGNORE) ? "ignoring" : "error"); + *(_cur.grffile->remap_unknown_property_names.Append()) = str_store; + Action5TypeRemapEntry &entry = _cur.grffile->action5_type_remaps.Entry(this->prop_id); + entry.name = str_store; + entry.info = nullptr; + entry.type_id = this->prop_id; + entry.fallback_mode = this->fallback_mode; + } + } + } }; static GRFPropertyMapAction _current_grf_property_map_action; @@ -8145,12 +8202,12 @@ static bool ChangePropertyRemapFeature(size_t len, ByteReader *buf) return true; } -/** Callback function for to set the property ID to which this item is being mapped. */ -static bool ChangePropertyRemapPropertyIdGeneric(size_t len, ByteReader *buf, const char *tag) +/** Callback function for ->'PROP' to set the property ID to which this item is being mapped. */ +static bool ChangePropertyRemapPropertyId(size_t len, ByteReader *buf) { GRFPropertyMapAction &action = _current_grf_property_map_action; if (len != 1) { - grfmsg(2, "Action 14 %s mapping: expected 1 byte for '%s'->'%s' but got " PRINTF_SIZE ", ignoring this field", action.descriptor, action.tag_name, tag, len); + grfmsg(2, "Action 14 %s mapping: expected 1 byte for '%s'->'PROP' but got " PRINTF_SIZE ", ignoring this field", action.descriptor, action.tag_name, len); buf->Skip(len); } else { action.prop_id = buf->ReadByte(); @@ -8158,10 +8215,22 @@ static bool ChangePropertyRemapPropertyIdGeneric(size_t len, ByteReader *buf, co return true; } -/** Callback function for ->'PROP' to set the property ID to which this item is being mapped. */ -static bool ChangePropertyRemapPropertyId(size_t len, ByteReader *buf) +/** Callback function for ->'TYPE' to set the property ID to which this item is being mapped. */ +static bool ChangePropertyRemapTypeId(size_t len, ByteReader *buf) { - return ChangePropertyRemapPropertyIdGeneric(len, buf, "PROP"); + GRFPropertyMapAction &action = _current_grf_property_map_action; + if (len != 1) { + grfmsg(2, "Action 14 %s mapping: expected 1 byte for '%s'->'TYPE' but got " PRINTF_SIZE ", ignoring this field", action.descriptor, action.tag_name, len); + buf->Skip(len); + } else { + uint8 prop = buf->ReadByte(); + if (prop < 128) { + action.prop_id = prop; + } else { + grfmsg(2, "Action 14 %s mapping: expected a type < 128 for '%s'->'TYPE' but got %u, ignoring this field", action.descriptor, action.tag_name, prop); + } + } + return true; } /** Callback function for ->'FLBK' to set the fallback mode. */ @@ -8216,11 +8285,32 @@ static bool HandleAction0PropertyMap(ByteReader *buf) return true; } +/** Action14 tags for the A5TM node */ +AllowedSubtags _tags_a5tm[] = { + AllowedSubtags('NAME', ChangePropertyRemapName), + AllowedSubtags('TYPE', ChangePropertyRemapTypeId), + AllowedSubtags('FLBK', ChangePropertyRemapSetFallbackMode), + AllowedSubtags('SETT', ChangePropertyRemapSetTTDVerVarBit), + AllowedSubtags() +}; + +/** + * Callback function for 'A5TM' (action 5 type mapping) + */ +static bool HandleAction5TypeMap(ByteReader *buf) +{ + _current_grf_property_map_action.Reset("A5TM", "Action 5 type"); + HandleNodes(buf, _tags_a5tm); + _current_grf_property_map_action.ExecuteAction5TypeRemapping(); + return true; +} + /** Action14 root tags */ AllowedSubtags _tags_root_static[] = { AllowedSubtags('INFO', _tags_info), AllowedSubtags('FTST', SkipInfoChunk), AllowedSubtags('A0PM', SkipInfoChunk), + AllowedSubtags('A5TM', SkipInfoChunk), AllowedSubtags() }; @@ -8229,6 +8319,7 @@ AllowedSubtags _tags_root_feature_tests[] = { AllowedSubtags('INFO', SkipInfoChunk), AllowedSubtags('FTST', HandleFeatureTestInfo), AllowedSubtags('A0PM', HandleAction0PropertyMap), + AllowedSubtags('A5TM', HandleAction5TypeMap), AllowedSubtags() }; diff --git a/src/newgrf.h b/src/newgrf.h index 95518cf5f3..49f2446b94 100644 --- a/src/newgrf.h +++ b/src/newgrf.h @@ -152,6 +152,55 @@ struct GRFFilePropertyRemapSet { } }; +/** The type of action 5 type. */ +enum Action5BlockType { + A5BLOCK_FIXED, ///< Only allow replacing a whole block of sprites. (TTDP compatible) + A5BLOCK_ALLOW_OFFSET, ///< Allow replacing any subset by specifiing an offset. + A5BLOCK_INVALID, ///< unknown/not-implemented type +}; +/** Information about a single action 5 type. */ +struct Action5Type { + Action5BlockType block_type; ///< How is this Action5 type processed? + SpriteID sprite_base; ///< Load the sprites starting from this sprite. + uint16 min_sprites; ///< If the Action5 contains less sprites, the whole block will be ignored. + uint16 max_sprites; ///< If the Action5 contains more sprites, only the first max_sprites sprites will be used. + const char *name; ///< Name for error messages. +}; + +struct Action5TypeRemapDefinition { + const char *name; // NULL indicates the end of the list + const Action5Type info; + + /** Create empty object used to identify the end of a list. */ + Action5TypeRemapDefinition() : + name(NULL), + info({ A5BLOCK_INVALID, 0, 0, 0, NULL }) + {} + + Action5TypeRemapDefinition(const char *type_name, Action5BlockType block_type, SpriteID sprite_base, uint16 min_sprites, uint16 max_sprites, const char *info_name) : + name(type_name), + info({ block_type, sprite_base, min_sprites, max_sprites, info_name }) + {} +}; + +struct Action5TypeRemapEntry { + const Action5Type *info = nullptr; + const char *name = nullptr; + uint8 type_id = 0; + GRFPropertyMapFallbackMode fallback_mode = GPMFM_IGNORE; +}; + +struct Action5TypeRemapSet { + std::bitset<256> remapped_ids; + std::map mapping; + + Action5TypeRemapEntry &Entry(uint8 property) + { + this->remapped_ids.set(property); + return this->mapping[property]; + } +}; + /** Dynamic data of a loaded NewGRF */ struct GRFFile : ZeroedMemoryAllocator { char *filename; @@ -170,7 +219,8 @@ struct GRFFile : ZeroedMemoryAllocator { struct AirportTileSpec **airtspec; GRFFilePropertyRemapSet action0_property_remaps[GSF_END]; - AutoFreeSmallVector action0_unknown_property_names; + Action5TypeRemapSet action5_type_remaps; + AutoFreeSmallVector remap_unknown_property_names; uint32 param[0x80]; uint param_end; ///< one more than the highest set parameter