From 6d2b9c7b5560a8805ebb35b47b5f871c160d21b2 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 3 Sep 2024 18:11:10 +0100 Subject: [PATCH 01/11] Add: Support for NewGRF badges. --- nml/actions/action0.py | 28 +++++ nml/actions/action0properties.py | 101 +++++++++++++++--- nml/actions/action2var_variables.py | 32 ++++-- nml/actions/action3_callbacks.py | 6 +- nml/ast/badgetable.py | 55 ++++++++++ nml/ast/general.py | 1 + nml/expression/functioncall.py | 5 +- nml/global_constants.py | 4 + nml/parser.py | 17 +++ nml/tokens.py | 1 + regression/042_badges.nml | 63 +++++++++++ regression/expected/013_train_callback.nfo | 2 +- regression/expected/017_articulated_tram.nfo | 2 +- regression/expected/030_house.nfo | 16 +-- regression/expected/040_station.nfo | 2 +- .../expected/041_articulated_tram_32bpp.nfo | 2 +- regression/expected/042_badges.grf | Bin 0 -> 593 bytes regression/expected/042_badges.nfo | 82 ++++++++++++++ regression/expected/example_industry.nfo | 12 +-- regression/expected/example_road_vehicle.nfo | 16 +-- regression/expected/example_train.nfo | 4 +- regression/lang/english.lng | 6 ++ 22 files changed, 405 insertions(+), 52 deletions(-) create mode 100644 nml/ast/badgetable.py create mode 100644 regression/042_badges.nml create mode 100644 regression/expected/042_badges.grf create mode 100644 regression/expected/042_badges.nfo diff --git a/nml/actions/action0.py b/nml/actions/action0.py index 9bf535b81..977060605 100644 --- a/nml/actions/action0.py +++ b/nml/actions/action0.py @@ -211,6 +211,7 @@ def find_unused(self, length): BlockAllocation(0, 62, "Roadtype"), BlockAllocation(0, 62, "Tramtype"), BlockAllocation(0, 0xFFFE, "RoadStop"), # UINT16_MAX - 1 + BlockAllocation(0, 64000, "Badge"), ] @@ -780,6 +781,26 @@ def get_size(self): return len(self.id_list) * 4 + 1 +class StringListProp(BaseAction0Property): + def __init__(self, prop_num, string_list): + self.prop_num = prop_num + self.string_list = string_list + + def write(self, file): + file.print_bytex(self.prop_num) + for i, string_val in enumerate(self.string_list): + if i > 0 and i % 5 == 0: + file.newline() + file.print_string(string_val.value, True, True) + file.newline() + + def get_size(self): + size = 1 + for i, string_val in enumerate(self.string_list): + size += grfstrings.get_string_size(string_val.value, True, True) + return size + + def get_cargolist_action(cargo_list): action0 = Action0(0x08, 0) action0.prop_list.append(IDListProp(0x09, cargo_list)) @@ -787,6 +808,13 @@ def get_cargolist_action(cargo_list): return [action0] +def get_badgelist_action(badge_list): + action0 = Action0(0x08, 0) + action0.prop_list.append(StringListProp(0x18, badge_list)) + action0.num_ids = len(badge_list) + return [action0] + + def get_tracktypelist_action(table_prop_id, cond_tracktype_not_defined, tracktype_list): action6.free_parameters.save() act6 = action6.Action6() diff --git a/nml/actions/action0properties.py b/nml/actions/action0properties.py index 5d931858e..c6528b5fb 100644 --- a/nml/actions/action0properties.py +++ b/nml/actions/action0properties.py @@ -15,7 +15,7 @@ import itertools -from nml import generic, nmlop, global_constants +from nml import generic, grfstrings, nmlop, global_constants from nml.expression import ( AcceptCargo, Array, @@ -170,7 +170,7 @@ def get_size(self): # # 'required' (value doesn't matter) if the property is required for the item to be valid. -properties = 0x15 * [None] +properties = 0x16 * [None] # # Some helper functions that are used for multiple features @@ -305,22 +305,18 @@ class VariableListProp(BaseAction0Property): Property value that is a variable-length list of variable sized values, the list length is written before the data. """ - def __init__(self, prop_num, data, size, extended): + def __init__(self, prop_num, data, size, size_size): # data is a list, each element belongs to an item ID # Each element in the list is a list of cargo types self.prop_num = prop_num self.data = data self.size = size - self.extended = extended + self.size_size = size_size def write(self, file): file.print_bytex(self.prop_num) for elem in self.data: - if self.extended: - file.print_bytex(0xFF) - file.print_word(len(elem)) - else: - file.print_byte(len(elem)) + file.print_varx(len(elem), self.size_size) for i, val in enumerate(elem): if i % 8 == 0: file.newline() @@ -330,13 +326,31 @@ def write(self, file): def get_size(self): total_len = 1 # Prop number for elem in self.data: - # For each item ID to set, make space for all values + 3 or 1 for the length - total_len += len(elem) * self.size + (3 if self.extended else 1) + # For each item ID to set, make space for all values + size_size for the length + total_len += len(elem) * self.size + self.size_size return total_len -def VariableByteListProp(prop_num, data, extended=False): - return VariableListProp(prop_num, data, 1, extended) +class StringProp(BaseAction0Property): + """ + Property value that is zero-terminated string. + """ + + def __init__(self, prop_num, string): + self.prop_num = prop_num + self.string = string + + def write(self, file): + file.print_bytex(self.prop_num) + file.print_string(self.string.value, True, True) + file.newline() + + def get_size(self): + return grfstrings.get_string_size(self.string.value) + 1 + + +def VariableByteListProp(prop_num, data, size_size=1): + return VariableListProp(prop_num, data, 1, size_size) def ctt_list(prop_num, *values): @@ -353,8 +367,40 @@ def ctt_list(prop_num, *values): ] -def VariableWordListProp(num_prop, data, extended=False): - return VariableListProp(num_prop, data, 2, extended) +def VariableWordListProp(num_prop, data, size_size=1): + return VariableListProp(num_prop, data, 2, size_size) + + +def badge_list(prop_num, *values): + # values may have multiple entries, if more than one item ID is set (e.g. multitile houses) + # Each value is an expression.Array of cargo types + + table = global_constants.badge_numbers + + for value in values: + if not isinstance(value, Array): + raise generic.ScriptError("Value of badgelist property must be an array", value.pos) + + for badge in value.values: + if not isinstance(badge, StringLiteral) or badge.value not in table: + raise generic.ScriptError( + "Parameter for badges must be a string literal that is also in your badge table" + ) + + return [ + VariableListProp( + prop_num, + [[table[badge.value] for badge in single_item_array.values] for single_item_array in values], + 2, + 2, + ) + ] + + +def string_property(prop_num, value): + if not isinstance(value, StringLiteral): + raise generic.ScriptError("Value of label property must be a StringLiteral", value.pos) + return [StringProp(prop_num, value)] def accepted_cargos(prop_num, *values): @@ -478,6 +524,7 @@ def prop_test(value): "curve_speed_mod": {"size": 2, "num": 0x2E, "unit_conversion": 256}, "variant_group": {"size": 2, "num": 0x2F}, "extra_flags": {"size": 4, "num": 0x30}, + "badges": {"custom_function": lambda value: badge_list(0x33, value)}, } # fmt: on @@ -556,6 +603,7 @@ def prop15_test(value): ], "variant_group": {"size": 2, "num": 0x26}, "extra_flags": {"size": 4, "num": 0x27}, + "badges": {"custom_function": lambda value: badge_list(0x2A, value)}, } # fmt: on @@ -646,6 +694,7 @@ def prop23_test(value): "variant_group": {"size": 2, "num": 0x20}, "extra_flags": {"size": 4, "num": 0x21}, "acceleration": {"size": 1, "num": 0x24}, + "badges": {"custom_function": lambda value: badge_list(0x26, value)}, } # fmt: on @@ -707,6 +756,7 @@ def aircraft_is_large(value): "range": {"size": 2, "num": 0x1F}, "variant_group": {"size": 2, "num": 0x20}, "extra_flags": {"size": 4, "num": 0x21}, + "badges": {"custom_function": lambda value: badge_list(0x24, value)}, } # fmt: on @@ -799,7 +849,7 @@ def station_tile_flags(value): if not isinstance(value, Array) or len(value.values) % 2 != 0: raise generic.ScriptError("Flag list must be an array of even length", value.pos) if len(value.values) > 8: - return [VariableByteListProp(0x1E, [[flags.reduce_constant().value for flags in value.values]], True)] + return [VariableByteListProp(0x1E, [[flags.reduce_constant().value for flags in value.values]], 3)] pylons = 0 wires = 0 blocked = 0 @@ -843,6 +893,7 @@ def station_tile_flags(value): "name": {"size": 2, "num": (256, -1, 0x1C), "string": (256, 0xC5, 0xDC), "required": True}, "classname": {"size": 2, "num": (256, -1, 0x1D), "string": (256, 0xC4, 0xDC)}, "tile_flags": {"custom_function": station_tile_flags}, # = prop 1E + "badges": {"custom_function": lambda value: badge_list(0x1F, value)}, } # fmt: on @@ -1051,6 +1102,7 @@ def mt_house_class(value, num_ids, size_bit): "multitile_function": mt_house_same, "custom_function": lambda *values: accepted_cargos(0x23, *values), }, + "badges": {"custom_function": lambda value: badge_list(0x24, value)}, } # fmt: on @@ -1072,6 +1124,7 @@ def mt_house_class(value, num_ids, size_bit): "animation_triggers": {"size": 1, "num": 0x11}, "special_flags": {"size": 1, "num": 0x12}, "accepted_cargos": {"custom_function": lambda value: accepted_cargos(0x13, value)}, + "badges": {"custom_function": lambda value: badge_list(0x14, value)}, } # fmt: on @@ -1344,6 +1397,7 @@ def check_accept(acp): "nearby_station_name": {"size": 2, "num": 0x24, "string": 0xDC}, # prop 25+26+27+28 combined in one structure "cargo_types": {"custom_function": industry_cargo_types}, + "badges": {"custom_function": lambda value: badge_list(0x29, value)}, } # fmt: on @@ -1447,6 +1501,7 @@ def airport_layouts(value): "noise_level": {"size": 1, "num": 0x0F}, "name": {"size": 2, "num": 0x10, "string": 0xDC}, "maintenance_cost": {"size": 2, "num": 0x11}, + "badges": {"custom_function": lambda value: badge_list(0x12, value)}, } # fmt: on @@ -1487,6 +1542,7 @@ def object_size(value): "height": {"size": 1, "num": 0x16}, "num_views": {"size": 1, "num": 0x17}, "count_per_map256": {"size": 1, "num": 0x18}, + "badges": {"custom_function": lambda value: badge_list(0x19, value)}, } # fmt: on @@ -1533,6 +1589,7 @@ def label_list(value, prop_num, description): "sort_order": {"size": 1, "num": 0x1A}, "name": {"size": 2, "num": 0x1B, "string": 0xDC}, "maintenance_cost": {"size": 2, "num": 0x1C}, + "badges": {"custom_function": lambda value: badge_list(0x1E, value)}, } # @@ -1571,6 +1628,7 @@ def label_list(value, prop_num, description): "animation_info": {"size": 2, "num": 0x0F, "value_function": animation_info}, "animation_speed": {"size": 1, "num": 0x10}, "animation_triggers": {"size": 1, "num": 0x11}, + "badges": {"custom_function": lambda value: badge_list(0x12, value)}, } # @@ -1657,4 +1715,15 @@ def byte_sequence_list(value, prop_num, description, expected_count): # 11 (callback flags) is not set by user "general_flags": {"size": 4, "num": 0x12}, "cost_multipliers": {"custom_function": lambda x: byte_sequence_list(x, 0x15, "Cost multipliers", 2)}, + "badges": {"custom_function": lambda value: badge_list(0x16, value)}, +} + +# +# Feature 0x15 (Badges) +# + +properties[0x15] = { + 'label': {'custom_function': lambda x: string_property(0x08, x), "required": True}, + 'flags': {'size': 4, 'num': 0x09}, + 'name': {'num': -1, 'string': None}, } diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index ba200f45b..6eb670622 100644 --- a/nml/actions/action2var_variables.py +++ b/nml/actions/action2var_variables.py @@ -234,6 +234,9 @@ def vehicle_roadtype(name, args, pos, info): def vehicle_tramtype(name, args, pos, info): return (expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="tramtype"), []) +def badge_parameter(name, args, pos, info): + return [expression.functioncall.builtin_resolve_typelabel(name, args, pos, table_name="badgetype"), []] + varact2vars60x_vehicles = { 'count_veh_id' : {'var': 0x60, 'start': 0, 'size': 8}, 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, @@ -241,6 +244,7 @@ def vehicle_tramtype(name, args, pos, info): 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1, 'param_function':badge_parameter}, } varact2vars60x_trains = { @@ -292,6 +296,7 @@ def vehicle_tramtype(name, args, pos, info): 'cargo_accepted_last_month' : {'var': 0x69, 'start': 1, 'size': 1}, 'cargo_accepted_this_month' : {'var': 0x69, 'start': 2, 'size': 1}, 'cargo_accepted_bigtick' : {'var': 0x69, 'start': 3, 'size': 1}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } varact2vars_stations = { @@ -483,6 +488,7 @@ def nearest_house_matching_criterion(name, args, pos, info): 'nearby_tile_house_id' : {'var': 0x66, 'start': 0, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_class' : {'var': 0x66, 'start': 16, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_grfid' : {'var': 0x67, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } # @@ -514,6 +520,7 @@ def nearest_house_matching_criterion(name, args, pos, info): 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_industrytile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } # @@ -619,6 +626,7 @@ def industry_cargotype(name, args, pos, info): 'incoming_cargo_waiting' : {'var': 0x6F, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'production_rate' : {'var': 0x70, 'start': 0, 'size': 32, 'param_function': industry_cargotype}, 'transported_last_month_pct' : {'var': 0x71, 'start': 0, 'size': 32, 'param_function': industry_cargotype, 'value_function': value_mul_div(101, 256)}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } # @@ -685,6 +693,8 @@ def industry_cargotype(name, args, pos, info): 'object_count' : {'var': 0x64, 'start': 16, 'size': 8, 'param_function': industry_count}, 'object_distance' : {'var': 0x64, 'start': 0, 'size': 16, 'param_function': industry_count}, + + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } # @@ -702,7 +712,10 @@ def industry_cargotype(name, args, pos, info): 'railtype' : {'var': 0x45, 'start': 16, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } -# Railtypes have no 60+x variables + +varact2vars60x_railtype = { + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, +} # # Airport tiles (feature 0x11) @@ -729,6 +742,7 @@ def industry_cargotype(name, args, pos, info): 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_airporttile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, } # @@ -746,7 +760,10 @@ def industry_cargotype(name, args, pos, info): 'railtype' : {'var': 0x45, 'start': 16, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } -# Roadtypes have no 60+x variables + +varact2vars60x_roadtype = { + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, +} # # Tramtypes (feature 0x13) @@ -763,7 +780,10 @@ def industry_cargotype(name, args, pos, info): 'railtype' : {'var': 0x45, 'start': 16, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } -# Tramtypes have no 60+x variables + +varact2vars60x_tramtype = { + 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1}, +} # @@ -881,10 +901,10 @@ def get_scope(self, var_range): scope_soundeffects = VarAct2Scope("SoundEffects", {}, {}) scope_airports = VarAct2Scope("Airports", varact2vars_airports, varact2vars60x_airports, has_persistent_storage=True) scope_objects = VarAct2Scope("Objects", varact2vars_objects, varact2vars60x_objects) -scope_railtypes = VarAct2Scope("RailTypes", varact2vars_railtype, {}) +scope_railtypes = VarAct2Scope("RailTypes", varact2vars_railtype, varact2vars60x_railtype) scope_airporttiles = VarAct2Scope("AirportTiles", varact2vars_airporttiles, varact2vars60x_airporttiles) -scope_roadtypes = VarAct2Scope("RoadTypes", varact2vars_roadtype, {}) -scope_tramtypes = VarAct2Scope("TramTypes", varact2vars_tramtype, {}) +scope_roadtypes = VarAct2Scope("RoadTypes", varact2vars_roadtype, varact2vars60x_roadtype) +scope_tramtypes = VarAct2Scope("TramTypes", varact2vars_tramtype, varact2vars60x_tramtype) scope_roadstops = VarAct2Scope("RoadStops", varact2vars_roadstop, varact2vars60x_roadstop) varact2features = [ diff --git a/nml/actions/action3_callbacks.py b/nml/actions/action3_callbacks.py index bbb6a6704..f19b2baff 100644 --- a/nml/actions/action3_callbacks.py +++ b/nml/actions/action3_callbacks.py @@ -15,7 +15,7 @@ from nml import nmlop -callbacks = 0x15 * [{}] +callbacks = 0x16 * [{}] # Possible values for 'purchase': # 0 (or not set): not called from purchase list @@ -320,3 +320,7 @@ def vehicle_length(value): 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } + +# Badges +callbacks[0x15] = { +} diff --git a/nml/ast/badgetable.py b/nml/ast/badgetable.py new file mode 100644 index 000000000..481689fbe --- /dev/null +++ b/nml/ast/badgetable.py @@ -0,0 +1,55 @@ +__license__ = """ +NML 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; either version 2 of the License, or +(at your option) any later version. + +NML 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 NML; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" + +from nml import expression, generic, global_constants +from nml.actions import action0 +from nml.ast import base_statement + + +class BadgeTable(base_statement.BaseStatement): + def __init__(self, badge_list, pos): + base_statement.BaseStatement.__init__(self, "badge table", pos, False, False) + self.badge_list = badge_list + + def register_names(self): + generic.OnlyOnce.enforce(self, "badge table") + for i, badge in enumerate(self.badge_list): + if isinstance(badge, expression.Identifier): + self.badge_list[i] = expression.StringLiteral(badge.value, badge.pos) + # expression.parse_string_to_dword( + # self.badge_list[i] + # ) # we don't care about the result, only validate the input + if self.badge_list[i].value in global_constants.badge_numbers: + generic.print_warning( + generic.Warning.GENERIC, + "Duplicate entry in badge table: {}".format(self.badge_list[i].value), + badge.pos, + ) + else: + global_constants.badge_numbers[self.badge_list[i].value] = i + + def debug_print(self, indentation): + generic.print_dbg(indentation, "Badge table") + for badge in self.badge_list: + generic.print_dbg(indentation, "Badge:", badge.value) + + def get_action_list(self): + return action0.get_badgelist_action(self.badge_list) + + def __str__(self): + ret = "badgetable {\n" + ret += ", ".join([expression.identifier_to_print(badge.value) for badge in self.badge_list]) + ret += "\n}\n" + return ret diff --git a/nml/ast/general.py b/nml/ast/general.py index 93656c637..757750c32 100644 --- a/nml/ast/general.py +++ b/nml/ast/general.py @@ -38,6 +38,7 @@ "FEAT_ROADTYPES": 0x12, "FEAT_TRAMTYPES": 0x13, "FEAT_ROADSTOPS": 0x14, + "FEAT_BADGES": 0x15, } diff --git a/nml/expression/functioncall.py b/nml/expression/functioncall.py index ea0860f4f..a6bf56481 100644 --- a/nml/expression/functioncall.py +++ b/nml/expression/functioncall.py @@ -470,7 +470,7 @@ def builtin_str2number(name, args, pos): return ConstantNumeric(parse_string_to_dword(args[0])) -@builtins("cargotype", "railtype", "roadtype", "tramtype") +@builtins("badgetype", "cargotype", "railtype", "roadtype", "tramtype") def builtin_resolve_typelabel(name, args, pos, table_name=None): """ {cargo,rail,road,tram}type(label) builtin functions. @@ -478,6 +478,7 @@ def builtin_resolve_typelabel(name, args, pos, table_name=None): Also used from some Action2Var variables to resolve cargo labels. """ tracktype_funcs = { + "badgetype": global_constants.badge_numbers, "cargotype": global_constants.cargo_numbers, "railtype": global_constants.railtype_table, "roadtype": global_constants.roadtype_table, @@ -489,6 +490,8 @@ def builtin_resolve_typelabel(name, args, pos, table_name=None): table = tracktype_funcs[table_name] if table_name == "cargotype": table_name = "cargo" # NML syntax uses "cargotable" and "railtypetable" + if table_name == "badgetype": + table_name = "badge" if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) diff --git a/nml/global_constants.py b/nml/global_constants.py index 10e42bf26..b40f71b10 100644 --- a/nml/global_constants.py +++ b/nml/global_constants.py @@ -1427,6 +1427,7 @@ def create_spritegroup_ref(name, info, pos): cargo_numbers = {} +badge_numbers = {} is_default_railtype_table = True # if no railtype_table is provided, OpenTTD assumes these 3 railtypes @@ -1474,6 +1475,7 @@ def create_spritegroup_ref(name, info, pos): (patch_variables, patch_variable), (named_parameters, param_from_name), cargo_numbers, + badge_numbers, railtype_table, roadtype_table, tramtype_table, @@ -1494,6 +1496,8 @@ def print_stats(): if len(cargo_numbers) > 0: # Ids FE and FF have special meanings in Action3, so we do not consider them valid ids. generic.print_info("Cargo translation table: {}/{}".format(len(cargo_numbers), 0xFE)) + if len(badge_numbers) > 0: + generic.print_info("Badge translation table: {}/{}".format(len(cargo_numbers), 0x10000)) if not is_default_railtype_table: generic.print_info("Railtype translation table: {}/{}".format(len(railtype_table), 0x100)) if not is_default_roadtype_table: diff --git a/nml/parser.py b/nml/parser.py index 9df8670d7..f8818a643 100644 --- a/nml/parser.py +++ b/nml/parser.py @@ -20,6 +20,7 @@ from nml.ast import ( alt_sprites, assignment, + badgetable, base_graphics, basecost, cargotable, @@ -120,6 +121,7 @@ def p_main_block(self, t): | template_declaration | tilelayout | town_names + | badgetable | cargotable | railtype | roadtype @@ -741,6 +743,21 @@ def p_disable_item(self, t): "disable_item : DISABLE_ITEM LPAREN expression_list RPAREN SEMICOLON" t[0] = disable_item.DisableItem(t[3], t.lineno(1)) + def p_badgetable(self, t): + """badgetable : BADGETABLE LBRACE badgetable_list RBRACE + | BADGETABLE LBRACE badgetable_list COMMA RBRACE""" + t[0] = badgetable.BadgeTable(t[3], t.lineno(1)) + + def p_badgetable_list(self, t): + """badgetable_list : ID + | STRING_LITERAL + | badgetable_list COMMA ID + | badgetable_list COMMA STRING_LITERAL""" + if len(t) == 2: + t[0] = [t[1]] + else: + t[0] = t[1] + [t[3]] + def p_cargotable(self, t): """cargotable : CARGOTABLE LBRACE cargotable_list RBRACE | CARGOTABLE LBRACE cargotable_list COMMA RBRACE""" diff --git a/nml/tokens.py b/nml/tokens.py index 56e10f6a9..fb9344c59 100644 --- a/nml/tokens.py +++ b/nml/tokens.py @@ -26,6 +26,7 @@ "var": "VARIABLE", "param": "PARAMETER", "cargotable": "CARGOTABLE", + "badgetable": "BADGETABLE", "railtypetable": "RAILTYPETABLE", "roadtypetable": "ROADTYPETABLE", "tramtypetable": "TRAMTYPETABLE", diff --git a/regression/042_badges.nml b/regression/042_badges.nml new file mode 100644 index 000000000..a5fc14abd --- /dev/null +++ b/regression/042_badges.nml @@ -0,0 +1,63 @@ +grf { + grfid: "NML\42"; + name: string(STR_REGRESSION_NAME); + desc: string(STR_REGRESSION_DESC); + version: 0; + min_compatible_version: 0; +} + +badgetable { + "flag/GB", + "flag/US", + "power/steam", + "power/diesel", + "power/electric", +} + +switch (FEAT_TRAINS, SELF, sw_can_attach_wagon, has_badge("power/electric")) { + 1: return CB_RESULT_ATTACH_ALLOW; + return string(STR_NO_BADGE); +} + +item (FEAT_TRAINS, default_train, 8) { + property { + badges: ["flag/GB", "power/steam"]; + } + graphics { + can_attach_wagon: sw_can_attach_wagon; + } +} + +item (FEAT_ROADVEHS, default_roadveh, 0) { + property { + badges: ["flag/US", "power/diesel"]; + } +} + +item (FEAT_BADGES, power) { + property { + label: "power"; + name: string(STR_POWER); + } +} + +item (FEAT_BADGES, steam) { + property { + label: "power/steam"; + name: string(STR_POWER_STEAM); + } +} + +item (FEAT_BADGES, diesel) { + property { + label: "power/diesel"; + name: string(STR_POWER_DIESEL); + } +} + +item (FEAT_BADGES, electric) { + property { + label: "power/electric"; + name: string(STR_POWER_ELECTRIC); + } +} diff --git a/regression/expected/013_train_callback.nfo b/regression/expected/013_train_callback.nfo index 96ffa9ae1..cf135a89e 100644 --- a/regression/expected/013_train_callback.nfo +++ b/regression/expected/013_train_callback.nfo @@ -233,7 +233,7 @@ FF 1D \dx00000000 29 \wx01CB 1D \dx00000000 -2C \b4 +2C 04 00 01 0C 0D 1D \dx00000000 15 13 diff --git a/regression/expected/017_articulated_tram.nfo b/regression/expected/017_articulated_tram.nfo index 05dc53301..e8257d284 100644 --- a/regression/expected/017_articulated_tram.nfo +++ b/regression/expected/017_articulated_tram.nfo @@ -45,7 +45,7 @@ 16 \dx00000000 1E \wx0000 16 \dx00000000 -24 \b0 +24 00 16 \dx00000000 10 FF 1C 01 diff --git a/regression/expected/030_house.nfo b/regression/expected/030_house.nfo index 7ea103cd7..be316d49b 100644 --- a/regression/expected/030_house.nfo +++ b/regression/expected/030_house.nfo @@ -265,13 +265,13 @@ 19 00 00 00 00 0B 64 00 00 00 0C 19 00 00 00 -23 \b4 +23 04 \wx0802 \wx0803 \wx0200 \wx0101 -\b4 +04 \wx0802 \wx0803 \wx0200 \wx0101 -\b4 +04 \wx0802 \wx0803 \wx0200 \wx0101 -\b4 +04 \wx0802 \wx0803 \wx0200 \wx0101 10 \wx00C8 \wx00C8 \wx00C8 \wx00C8 11 FA FA FA FA @@ -285,13 +285,13 @@ 16 00 00 00 00 1A 94 94 94 94 1B 02 02 02 02 -20 \b2 +20 02 02 03 -\b2 +02 02 03 -\b2 +02 02 03 -\b2 +02 02 03 42 * 9 00 07 \b1 01 FF \wx0000 diff --git a/regression/expected/040_station.nfo b/regression/expected/040_station.nfo index f08681935..72651f105 100644 --- a/regression/expected/040_station.nfo +++ b/regression/expected/040_station.nfo @@ -26,7 +26,7 @@ 13 18 12 \dx00000002 0C F0 -1E FF \w10 +1E FF \wx000A 00 01 02 03 04 05 06 07 02 05 0E \b1 \b1 diff --git a/regression/expected/041_articulated_tram_32bpp.nfo b/regression/expected/041_articulated_tram_32bpp.nfo index 4d733ccd9..3a428e14f 100644 --- a/regression/expected/041_articulated_tram_32bpp.nfo +++ b/regression/expected/041_articulated_tram_32bpp.nfo @@ -45,7 +45,7 @@ 16 \dx00000000 1E \wx0000 16 \dx00000000 -24 \b0 +24 00 16 \dx00000000 10 FF 1C 01 diff --git a/regression/expected/042_badges.grf b/regression/expected/042_badges.grf new file mode 100644 index 0000000000000000000000000000000000000000..bd62f80166e50c71c25d39e90e921582430ff18f GIT binary patch literal 593 zcmZWm!AiqG5Ph3OiM4vNSOj6udgwtBZ<kjW#ehHkD)V96R&{dmfBFaRb$BUE~A}`?RgQsE{WiuVeX~Y#1rzEr>8NRwf z$`@(!%7dZ^!a09lEJfDI3lTi=7cq~+MC7<8K5Vunf%Ys3qE6q1J|BGiQB?6`EOL=l ziAY3PWO0Z#H8CL9Zx+;bz}TlgREX6+6$z6z&d47F+XOK%1@BO3V8cj&!zx=hAxVQ3 zp|m#tY7)l^r@bk=n5v;oSvirZiNJ}Wx|kKz(vYpMgW^vqFKNDcTl=WwdVK>oWsRHw literal 0 HcmV?d00001 diff --git a/regression/expected/042_badges.nfo b/regression/expected/042_badges.nfo new file mode 100644 index 000000000..46559f774 --- /dev/null +++ b/regression/expected/042_badges.nfo @@ -0,0 +1,82 @@ +// Automatically generated by GRFCODEC. Do not modify! +// (Info version 32) +// Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> +// Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C +// Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% +// Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags + +0 * 4 \d20 + +1 * 54 14 "C" "INFO" +"B" "VRSN" \w4 \dx00000000 +"B" "MINV" \w4 \dx00000000 +"B" "NPAR" \w1 00 +"B" "PALS" \w1 "A" +"B" "BLTR" \w1 "8" +00 +00 +2 * 52 08 08 "NML\42" "NML regression test" 00 "A test newgrf testing NML" 00 +3 * 43 04 00 FF 01 \wxD000 "Can only attach power/steam vehicles" 00 + +4 * 64 00 08 \b1 05 FF \wx0000 +18 "flag/GB" 00 "flag/US" 00 "power/steam" 00 "power/diesel" 00 "power/electric" 00 + +// Name: sw_can_attach_wagon +5 * 24 02 00 FF 89 +7A 04 00 \dx00000001 +\b1 +\wx8401 \dx00000001 \dx00000001 // 1 .. 1: return 1025; +\wx8000 // default: return string(STR_NO_BADGE); + +6 * 14 00 00 \b1 01 FF \wx0008 +33 \wx0002 +\wx0000 \wx0002 + +7 * 6 01 00 \b1 FF \wx0000 + +// Name: @CB_FAILED_REAL00 +8 * 9 02 00 FE \b1 \b1 +\w0 +\w0 + +// Name: @CB_FAILED00 +9 * 23 02 00 FE 89 +0C 00 \dx0000FFFF +\b1 +\wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 +\wx00FE // Non-graphics callback, return graphics result + +// Name: @action3_0 +10 * 23 02 00 FE 89 +0C 00 \dx0000FFFF +\b1 +\wx00FF \dx0000001D \dx0000001D // sw_can_attach_wagon; +\wx00FE // @CB_FAILED00; + +11 * 9 03 00 01 FF \wx0008 \b0 +\wx00FE // @action3_0; + +12 * 14 00 01 \b1 01 FF \wx0000 +2A \wx0002 +\wx0001 \wx0003 + +13 * 14 00 15 \b1 01 FF \wx0000 +08 "power" 00 + +14 * 11 04 15 7F 01 00 "Power" 00 + +15 * 20 00 15 \b1 01 FF \wx0001 +08 "power/steam" 00 + +16 * 11 04 15 7F 01 01 "Steam" 00 + +17 * 21 00 15 \b1 01 FF \wx0002 +08 "power/diesel" 00 + +18 * 12 04 15 7F 01 02 "Diesel" 00 + +19 * 23 00 15 \b1 01 FF \wx0003 +08 "power/electric" 00 + +20 * 14 04 15 7F 01 03 "Electric" 00 + diff --git a/regression/expected/example_industry.nfo b/regression/expected/example_industry.nfo index adafcae67..7793e113e 100644 --- a/regression/expected/example_industry.nfo +++ b/regression/expected/example_industry.nfo @@ -86,11 +86,11 @@ 13 * 27 00 0A \b6 01 FF \wx0000 08 06 09 06 -25 \b2 +25 02 09 05 -26 \b3 +26 03 01 08 06 -27 \b2 +27 02 00 00 28 \b0 \b0 14 * 11 00 0A \b2 01 FF \wx0000 @@ -138,9 +138,9 @@ 22 * 26 00 0A \b6 01 FF \wx0001 08 09 09 09 -25 \b3 +25 03 04 06 07 -26 \b0 -27 \b3 +26 00 +27 03 08 0C 04 28 \b0 \b0 diff --git a/regression/expected/example_road_vehicle.nfo b/regression/expected/example_road_vehicle.nfo index 2af0e51db..ce025412b 100644 --- a/regression/expected/example_road_vehicle.nfo +++ b/regression/expected/example_road_vehicle.nfo @@ -196,10 +196,10 @@ FF 16 \dx00000000 1E \wx0081 16 \dx00000000 -24 \b6 +24 06 00 01 02 03 04 05 16 \dx00000000 -25 \b0 +25 00 16 \dx00000000 07 05 11 6C @@ -295,10 +295,10 @@ FF 16 \dx00000000 1E \wx0081 16 \dx00000000 -24 \b6 +24 06 00 01 02 03 04 05 16 \dx00000000 -25 \b0 +25 00 16 \dx00000000 07 05 11 6C @@ -394,10 +394,10 @@ FF 16 \dx00000000 1E \wx0081 16 \dx00000000 -24 \b6 +24 06 00 01 02 03 04 05 16 \dx00000000 -25 \b0 +25 00 16 \dx00000000 07 05 11 6C @@ -493,10 +493,10 @@ FF 16 \dx00000000 1E \wx0081 16 \dx00000000 -24 \b6 +24 06 00 01 02 03 04 05 16 \dx00000000 -25 \b0 +25 00 16 \dx00000000 07 05 11 6C diff --git a/regression/expected/example_train.nfo b/regression/expected/example_train.nfo index 5c36b02a6..6995c5854 100644 --- a/regression/expected/example_train.nfo +++ b/regression/expected/example_train.nfo @@ -305,9 +305,9 @@ F2 00 \dx000000FF 1D \dx00000000 29 \wx0000 1D \dx00000000 -2C \b0 +2C 00 1D \dx00000000 -2D \b0 +2D 00 1D \dx00000000 07 06 17 2D diff --git a/regression/lang/english.lng b/regression/lang/english.lng index 9ffc40bd0..0c14d095b 100644 --- a/regression/lang/english.lng +++ b/regression/lang/english.lng @@ -43,3 +43,9 @@ STR_BREWERY_NAME :Brewery STR_032_HOUSE :Example house STR_JUST_STRING :{STRING} + +STR_NO_BADGE :Can only attach power/steam vehicles +STR_POWER :Power +STR_POWER_STEAM :Steam +STR_POWER_DIESEL :Diesel +STR_POWER_ELECTRIC :Electric From 5d3c813f7fcd9403556510b66949619c0fd5ca3c Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 19 Jan 2025 12:58:59 +0000 Subject: [PATCH 02/11] Add: Support more than 255 badgetable entries. --- nml/actions/action0.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/nml/actions/action0.py b/nml/actions/action0.py index 977060605..344a88131 100644 --- a/nml/actions/action0.py +++ b/nml/actions/action0.py @@ -809,10 +809,21 @@ def get_cargolist_action(cargo_list): def get_badgelist_action(badge_list): - action0 = Action0(0x08, 0) - action0.prop_list.append(StringListProp(0x18, badge_list)) - action0.num_ids = len(badge_list) - return [action0] + index = 0 + actions = [] + while index < len(badge_list): + last = index + len(badge_list) + if last - index > 250: + last = index + 250 + + action0 = Action0(0x08, index) + action0.prop_list.append(StringListProp(0x18, badge_list[index:last])) + action0.num_ids = last - index + actions.append(action0) + + index = last + + return actions def get_tracktypelist_action(table_prop_id, cond_tracktype_not_defined, tracktype_list): From 7cc50a7390f2b785723d2948f0beca86a0986531 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 20 Jan 2025 23:33:53 +0000 Subject: [PATCH 03/11] Add: varact2 variables for badges. --- nml/actions/action2var_variables.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index 6eb670622..4bef24a6f 100644 --- a/nml/actions/action2var_variables.py +++ b/nml/actions/action2var_variables.py @@ -867,6 +867,14 @@ def industry_cargotype(name, args, pos, info): 'nearby_tile_road_stop_id' : {'var': 0x6B, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } +# +# Badges (feature 0x15) +# + +varact2vars_badges = { + 'intro_date' : {'var': 0x40, 'start': 0, 'size': 32}, +} + class VarAct2Scope: def __init__(self, name, vars_normal, vars_60x, has_persistent_storage=False): self.name = name @@ -906,6 +914,7 @@ def get_scope(self, var_range): scope_roadtypes = VarAct2Scope("RoadTypes", varact2vars_roadtype, varact2vars60x_roadtype) scope_tramtypes = VarAct2Scope("TramTypes", varact2vars_tramtype, varact2vars60x_tramtype) scope_roadstops = VarAct2Scope("RoadStops", varact2vars_roadstop, varact2vars60x_roadstop) +scope_badges = VarAct2Scope("Badges", varact2vars_badges, {}) varact2features = [ VarAct2Feature(scope_trains, scope_trains), @@ -929,4 +938,5 @@ def get_scope(self, var_range): VarAct2Feature(scope_roadtypes, None), VarAct2Feature(scope_tramtypes, None), VarAct2Feature(scope_roadstops, scope_towns), + VarAct2Feature(scope_badges, None), ] From fdeb77c1f45bc4ed22c4c857587376d32685a709 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 22 Jan 2025 07:09:34 +0000 Subject: [PATCH 04/11] Add: varact2 vehicle variable 64 to count badge use in consist. --- nml/actions/action2var_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index 4bef24a6f..267a3d36e 100644 --- a/nml/actions/action2var_variables.py +++ b/nml/actions/action2var_variables.py @@ -244,6 +244,7 @@ def badge_parameter(name, args, pos, info): 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, + 'count_has_badge' : {'var': 0x64, 'start': 0, 'size': 8, 'param_function':badge_parameter}, 'has_badge' : {'var': 0x7A, 'start': 0, 'size': 1, 'param_function':badge_parameter}, } From e4648c951c947f3e1f0bda4bf986bdc84cfcfc39 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Fri, 21 Feb 2025 19:18:54 +0000 Subject: [PATCH 05/11] Change: Support badge graphics. --- nml/actions/action2.py | 2 +- nml/actions/action3_callbacks.py | 16 ++++++++++++++++ regression/042_badges.nml | 9 +++++++++ regression/expected/042_badges.grf | Bin 593 -> 1017 bytes regression/expected/042_badges.nfo | 24 ++++++++++++++++++------ 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/nml/actions/action2.py b/nml/actions/action2.py index 38fcecde4..f40413ef6 100644 --- a/nml/actions/action2.py +++ b/nml/actions/action2.py @@ -218,7 +218,7 @@ def free_references(source_action): # Features using sprite groups directly: vehicles, stations, canals, cargos, railtypes, airports, roadtypes, tramtypes -features_sprite_group = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0B, 0x0D, 0x10, 0x12, 0x13] +features_sprite_group = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x0B, 0x0D, 0x10, 0x12, 0x13, 0x15] # Features using sprite layouts: houses, industry tiles, objects, airport tiles, and road stops features_sprite_layout = [0x07, 0x09, 0x0F, 0x11, 0x14] # All features that need sprite sets diff --git a/nml/actions/action3_callbacks.py b/nml/actions/action3_callbacks.py index f19b2baff..675c597e1 100644 --- a/nml/actions/action3_callbacks.py +++ b/nml/actions/action3_callbacks.py @@ -323,4 +323,20 @@ def vehicle_length(value): # Badges callbacks[0x15] = { + 'trains' : {'type': 'cargo', 'num': 0x00}, + 'roadvehs' : {'type': 'cargo', 'num': 0x01}, + 'ships' : {'type': 'cargo', 'num': 0x02}, + 'aircraft' : {'type': 'cargo', 'num': 0x03}, + 'stations' : {'type': 'cargo', 'num': 0x04}, + 'houses' : {'type': 'cargo', 'num': 0x07}, + 'industrytiles' : {'type': 'cargo', 'num': 0x09}, + 'industry' : {'type': 'cargo', 'num': 0x0A}, + 'airports' : {'type': 'cargo', 'num': 0x0D}, + 'objects' : {'type': 'cargo', 'num': 0x0F}, + 'railtypes' : {'type': 'cargo', 'num': 0x10}, + 'airporttiles' : {'type': 'cargo', 'num': 0x11}, + 'roadtypes' : {'type': 'cargo', 'num': 0x12}, + 'tramtypes' : {'type': 'cargo', 'num': 0x13}, + 'roadstops' : {'type': 'cargo', 'num': 0x14}, + 'default' : {'type': 'cargo', 'num': None}, } diff --git a/regression/042_badges.nml b/regression/042_badges.nml index a5fc14abd..c7fe566cb 100644 --- a/regression/042_badges.nml +++ b/regression/042_badges.nml @@ -41,11 +41,20 @@ item (FEAT_BADGES, power) { } } +template tmpl_truck(x) { + [ 96,56, 28,15, -14, -8] +} + +spriteset(sprite_steam, ZOOM_LEVEL_NORMAL, BIT_DEPTH_32BPP, "opengfx_generic_trams1.png") { tmpl_truck(0) } + item (FEAT_BADGES, steam) { property { label: "power/steam"; name: string(STR_POWER_STEAM); } + graphics { + default: sprite_steam; + } } item (FEAT_BADGES, diesel) { diff --git a/regression/expected/042_badges.grf b/regression/expected/042_badges.grf index bd62f80166e50c71c25d39e90e921582430ff18f..090c9e85b01f6590c15ba1ba7c67d49764c180f2 100644 GIT binary patch delta 471 zcmcb}@{?VZfx$hKvPWEejE|M4GW;`$dXEcbz$UT{BPIl$2& z(4o*_&|%Zz(-Cta=RrwFMMq1=l#T@*8#)ejTmbSubo}Y$>6GZy=(Onc;ACL1n|z%? zx9;(d4-a@A2tf7gtgz|aup@#wXyx$-EE_aBDh^C|Fk?YI&jEo48y>hU;5neP;K~7w z2L;UeC%1P5ywG7{*#CfKhtGi-9Wgg-7RUgVg*=D=nk6$M0PGTrju?=wjMqClCQRvA z(y^t(0_c_pGhTE~>6|gKrjw&nVxkH&tLp6sEEjlKbKIbd;+M;&$q+{k%x=WtF}4hw^F;p?s^T`#&8bj5V#Y?gU_gK@!A z%dVc;E!{EQIo%cAD%}IU^!NX{9IpuK9 Date: Fri, 11 Apr 2025 20:37:40 +0100 Subject: [PATCH 06/11] Fix: Make action 4 support extended bytes for badges. --- nml/actions/action4.py | 4 ++-- regression/expected/042_badges.grf | Bin 1017 -> 1025 bytes regression/expected/042_badges.nfo | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nml/actions/action4.py b/nml/actions/action4.py index 77c65e067..f80219b5e 100644 --- a/nml/actions/action4.py +++ b/nml/actions/action4.py @@ -199,7 +199,7 @@ def get_string_action4s(feature, string_range, string, id=None): else: # Not a string range, so we must have an id assert id is not None - size = 3 if feature <= 3 else 1 + size = 3 if (feature <= 3 or feature == 21) else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: @@ -207,7 +207,7 @@ def get_string_action4s(feature, string_range, string, id=None): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later - mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) + mod = (tmp_param, 2 if (feature <= 3 or feature == 21) else 1, 5 if (feature <= 3 or feature == 21) else 4) if write_action4s: strings = [ diff --git a/regression/expected/042_badges.grf b/regression/expected/042_badges.grf index 090c9e85b01f6590c15ba1ba7c67d49764c180f2..e58f4afcb71b05b3a2460f02974989ad52fb6ab8 100644 GIT binary patch delta 88 zcmey#-pIktz~CO_*2K#t#Z|nKdo?3BF9QR^e-_bt#{UeH_cJQ+Aoz?7lV39W@bJME WFfmNFXHw=7fD14)Y|dfoWds1e>lL>E delta 80 zcmZqV_{q-Az~CO_*2K#t#g)C0do?2`P=Mh-i)cOL+Jh0d diff --git a/regression/expected/042_badges.nfo b/regression/expected/042_badges.nfo index 37ae5de65..8fb3d1f25 100644 --- a/regression/expected/042_badges.nfo +++ b/regression/expected/042_badges.nfo @@ -63,12 +63,12 @@ 13 * 14 00 15 \b1 01 FF \wx0000 08 "power" 00 -14 * 11 04 15 7F 01 00 "Power" 00 +14 * 13 04 15 7F 01 FF \wx0000 "Power" 00 15 * 20 00 15 \b1 01 FF \wx0001 08 "power/steam" 00 -16 * 11 04 15 7F 01 01 "Steam" 00 +16 * 13 04 15 7F 01 FF \wx0001 "Steam" 00 17 * 6 01 15 \b1 FF \wx0001 @@ -85,10 +85,10 @@ 21 * 21 00 15 \b1 01 FF \wx0002 08 "power/diesel" 00 -22 * 12 04 15 7F 01 02 "Diesel" 00 +22 * 14 04 15 7F 01 FF \wx0002 "Diesel" 00 23 * 23 00 15 \b1 01 FF \wx0003 08 "power/electric" 00 -24 * 14 04 15 7F 01 03 "Electric" 00 +24 * 16 04 15 7F 01 FF \wx0003 "Electric" 00 From 9ea3bba1af3cc0a7a2cd7ead747286bc1d00a626 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 13 Apr 2025 11:42:48 +0100 Subject: [PATCH 07/11] Fix: Show line number on badge table error. --- nml/actions/action0properties.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nml/actions/action0properties.py b/nml/actions/action0properties.py index c6528b5fb..1329426da 100644 --- a/nml/actions/action0properties.py +++ b/nml/actions/action0properties.py @@ -383,9 +383,7 @@ def badge_list(prop_num, *values): for badge in value.values: if not isinstance(badge, StringLiteral) or badge.value not in table: - raise generic.ScriptError( - "Parameter for badges must be a string literal that is also in your badge table" - ) + raise generic.ScriptError("Parameter for badges must be a string literal that is also in your badge table", value.pos) return [ VariableListProp( From 4a8747f520b1983f70b9b7bd19dbf24a78f4b370 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 13 Apr 2025 11:43:47 +0100 Subject: [PATCH 08/11] Change: Add constants for badge flags. --- nml/global_constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nml/global_constants.py b/nml/global_constants.py index b40f71b10..ccbbd2f05 100644 --- a/nml/global_constants.py +++ b/nml/global_constants.py @@ -149,6 +149,12 @@ def constant_number(name, info, pos): "VEHICLE_FLAG_SYNC_VARIANT_EXCLUSIVE_PREVIEW" : 2, "VEHICLE_FLAG_SYNC_VARIANT_RELIABILITY" : 3, + # badge flags + "BADGE_FLAG_COPY_TO_RELATED_ENTITY" : 0, + "BADGE_FLAG_NAME_LIST_STOP" : 1, + "BADGE_FLAG_NAME_LIST_FIRST_ONLY" : 2, + "BADGE_FLAG_NAME_USE_COMPANY_COLOUR" : 3, + # Graphic flags for waterfeatures "WATERFEATURE_ALTERNATIVE_SPRITES" : 0, From e9ce04b62d0723a94522d8dbb08d8c07cce179f2 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 13 Apr 2025 12:32:04 +0100 Subject: [PATCH 09/11] Codefix: Make black happy. --- nml/actions/action0properties.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nml/actions/action0properties.py b/nml/actions/action0properties.py index 1329426da..68964fc87 100644 --- a/nml/actions/action0properties.py +++ b/nml/actions/action0properties.py @@ -383,7 +383,9 @@ def badge_list(prop_num, *values): for badge in value.values: if not isinstance(badge, StringLiteral) or badge.value not in table: - raise generic.ScriptError("Parameter for badges must be a string literal that is also in your badge table", value.pos) + raise generic.ScriptError( + "Parameter for badges must be a string literal that is also in your badge table", value.pos + ) return [ VariableListProp( From 354d8c045a9f06b22c47adbfc1c7286afc042036 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 14 Apr 2025 11:16:45 +0100 Subject: [PATCH 10/11] Fix: Incorrect value shown for badge translation table usage. --- nml/global_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nml/global_constants.py b/nml/global_constants.py index ccbbd2f05..5c0b531f7 100644 --- a/nml/global_constants.py +++ b/nml/global_constants.py @@ -1503,7 +1503,7 @@ def print_stats(): # Ids FE and FF have special meanings in Action3, so we do not consider them valid ids. generic.print_info("Cargo translation table: {}/{}".format(len(cargo_numbers), 0xFE)) if len(badge_numbers) > 0: - generic.print_info("Badge translation table: {}/{}".format(len(cargo_numbers), 0x10000)) + generic.print_info("Badge translation table: {}/{}".format(len(badge_numbers), 0xFFFF)) if not is_default_railtype_table: generic.print_info("Railtype translation table: {}/{}".format(len(railtype_table), 0x100)) if not is_default_roadtype_table: From aa560ffffbdf865aa7200d03e1c7191ac172ff8a Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 30 May 2025 20:45:14 +0100 Subject: [PATCH 11/11] Change: add support for vehicle var 0x65 (OpenTTD #14312) --- nml/actions/action2var_variables.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index 267a3d36e..1380496e6 100644 --- a/nml/actions/action2var_variables.py +++ b/nml/actions/action2var_variables.py @@ -254,6 +254,7 @@ def badge_parameter(name, args, pos, info): 'tile_supports_railtype' : {'var': 0x63, 'start': 1, 'size': 1, 'param_function':vehicle_railtype}, 'tile_powers_railtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_railtype}, 'tile_is_railtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_railtype}, + 'tile_has_railtype_badge': {'var': 0x65, 'start': 0, 'size': 1, 'param_function':badge_parameter}, } varact2vars60x_roadvehs = { @@ -265,6 +266,8 @@ def badge_parameter(name, args, pos, info): 'tile_powers_tramtype' : {'var': 0x63, 'start': 2, 'size': 1, 'param_function':vehicle_tramtype}, 'tile_is_roadtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_roadtype}, 'tile_is_tramtype' : {'var': 0x63, 'start': 3, 'size': 1, 'param_function':vehicle_tramtype}, + 'tile_has_roadtype_badge': {'var': 0x65, 'start': 0, 'size': 1, 'param_function':badge_parameter}, + 'tile_has_tramtype_badge': {'var': 0x65, 'start': 0, 'size': 1, 'param_function':badge_parameter}, } #