diff --git a/nml/actions/action0.py b/nml/actions/action0.py index ac801144..4db083a1 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 5d931858..b12b6255 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,14 @@ 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}, } diff --git a/nml/actions/action2var_variables.py b/nml/actions/action2var_variables.py index 110564f5..81c41077 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}, } # @@ -699,7 +709,10 @@ def industry_cargotype(name, args, pos, info): 'town_zone' : {'var': 0x44, 'start': 0, '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) @@ -726,6 +739,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}, } # @@ -740,7 +754,10 @@ def industry_cargotype(name, args, pos, info): 'town_zone' : {'var': 0x44, 'start': 0, '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) @@ -754,7 +771,10 @@ def industry_cargotype(name, args, pos, info): 'town_zone' : {'var': 0x44, 'start': 0, '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}, +} # @@ -872,10 +892,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 bbb6a670..f19b2baf 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 00000000..481689fb --- /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 93656c63..757750c3 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 ea0860f4..a6bf5648 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 89baf075..7e34e7d3 100644 --- a/nml/global_constants.py +++ b/nml/global_constants.py @@ -1405,6 +1405,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 @@ -1452,6 +1453,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, @@ -1472,6 +1474,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 088b830e..2d4f3f4f 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, @@ -130,6 +131,7 @@ def p_main_block(self, t): | template_declaration | tilelayout | town_names + | badgetable | cargotable | railtype | roadtype @@ -751,6 +753,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 821e2dd2..9508dddb 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 00000000..4214d71f --- /dev/null +++ b/regression/042_badges.nml @@ -0,0 +1,53 @@ +grf { + grfid: "NML\42"; + name: string(STR_REGRESSION_NAME); + desc: string(STR_REGRESSION_DESC); + version: 0; + min_compatible_version: 0; +} + +switch (FEAT_TRAINS, SELF, sw_can_attach_wagon, has_badge("power/steam")) { + 1: return CB_RESULT_ATTACH_ALLOW; + return string(STR_NO_BADGE); +} + +badgetable { + "flag/GB", + "flag/US", + "power/steam", + "power/diesel", + "power/electric", +} + +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, steam) { + property { + label: "power/steam"; + } +} + +item (FEAT_BADGES, diesel) { + property { + label: "power/diesel"; + } +} + +item (FEAT_BADGES, electric) { + property { + label: "power/electric"; + } +} diff --git a/regression/expected/013_train_callback.nfo b/regression/expected/013_train_callback.nfo index 96ffa9ae..cf135a89 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 05dc5330..e8257d28 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 7ea103cd..be316d49 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 f0868193..72651f10 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 4d733ccd..3a428e14 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 00000000..e5cd8fc4 Binary files /dev/null and b/regression/expected/042_badges.grf differ diff --git a/regression/expected/042_badges.nfo b/regression/expected/042_badges.nfo new file mode 100644 index 00000000..c3e5a791 --- /dev/null +++ b/regression/expected/042_badges.nfo @@ -0,0 +1,71 @@ +// 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 \d15 + +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 + +// Name: sw_can_attach_wagon +4 * 24 02 00 FF 89 +7A 02 00 \dx00000001 +\b1 +\wx8401 \dx00000001 \dx00000001 // 1 .. 1: return 1025; +\wx8000 // default: return string(STR_NO_BADGE); + +5 * 64 00 08 \b1 05 FF \wx0000 +18 "flag/GB" 00 "flag/US" 00 "power/steam" 00 "power/diesel" 00 "power/electric" 00 + +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 * 20 00 15 \b1 01 FF \wx0000 +08 "power/steam" 00 + +14 * 21 00 15 \b1 01 FF \wx0001 +08 "power/diesel" 00 + +15 * 23 00 15 \b1 01 FF \wx0002 +08 "power/electric" 00 + diff --git a/regression/expected/example_industry.nfo b/regression/expected/example_industry.nfo index adafcae6..7793e113 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 2af0e51d..ce025412 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 5c36b02a..6995c585 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 9ffc40bd..1dac8565 100644 --- a/regression/lang/english.lng +++ b/regression/lang/english.lng @@ -43,3 +43,5 @@ STR_BREWERY_NAME :Brewery STR_032_HOUSE :Example house STR_JUST_STRING :{STRING} + +STR_NO_BADGE :Can only attach power/steam vehicles