Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nml for openSUSE:Factory checked in at 2026-04-16 17:26:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nml (Old) and /work/SRC/openSUSE:Factory/.nml.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nml" Thu Apr 16 17:26:13 2026 rev:31 rq:1347253 version:0.9.0 Changes: -------- --- /work/SRC/openSUSE:Factory/nml/nml.changes 2025-12-04 11:27:28.840042282 +0100 +++ /work/SRC/openSUSE:Factory/.nml.new.11940/nml.changes 2026-04-16 17:26:56.511362250 +0200 @@ -0,0 +1,16 @@ +------------------------------------------------------------------- +Thu Apr 16 09:24:22 UTC 2026 - Jan Baier <[email protected]> + +- update to 0.9.0: + Support for NewGRF additions of OpenTTD 16: + - Add: Flag for allow unpowered wagons to lead a train when backing up (#420) + - Add: Variable for when a train is driving backwards (#421) + - Change: distinguish perimeter from area in station distributed cargo flag. (#422) + Support for NewGRF additions of OpenTTD 15: + - Add: Support for NewGRF badges. (#359) + - Change: add support for vehicle var 0x65 (#378) + Other changes and fixes: + - Fix: Feature 0x14 missing from extract tables. (#403) + - Fix #407: missing position for some action0 errors (#408) + +------------------------------------------------------------------- Old: ---- nml-0.8.1.tar.gz New: ---- nml-0.9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nml.spec ++++++ --- /var/tmp/diff_new_pack.uslz1h/_old 2026-04-16 17:26:58.355438124 +0200 +++ /var/tmp/diff_new_pack.uslz1h/_new 2026-04-16 17:26:58.367438618 +0200 @@ -1,8 +1,7 @@ # # spec file for package nml # -# Copyright (c) 2025 SUSE LLC -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +17,7 @@ Name: nml -Version: 0.8.1 +Version: 0.9.0 Release: 0 Summary: NewGRF Meta Language License: GPL-2.0-or-later @@ -27,15 +26,13 @@ Source: https://github.com/OpenTTD/%{name}/releases/download/%{version}/%{name}-%{version}.tar.gz BuildRequires: fdupes BuildRequires: gcc -BuildRequires: python3-devel +BuildRequires: python3-devel >= 3.10 BuildRequires: python3-pip BuildRequires: python3-setuptools BuildRequires: python3-wheel # We need the required packages also on building for regression tests: BuildRequires: python3-Pillow >= 3.4 -BuildRequires: python3-ply Requires: python3-Pillow >= 3.4 -Requires: python3-ply Provides: nmlc = %{version} %description ++++++ nml-0.8.1.tar.gz -> nml-0.9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/PKG-INFO new/nml-0.9.0/PKG-INFO --- old/nml-0.8.1/PKG-INFO 2025-12-02 20:17:28.717712000 +0100 +++ new/nml-0.9.0/PKG-INFO 2026-04-16 00:54:57.798354400 +0200 @@ -1,10 +1,10 @@ Metadata-Version: 2.4 Name: nml -Version: 0.8.1 +Version: 0.9.0 Summary: An OpenTTD NewGRF compiler for the nml language Home-page: https://github.com/OpenTTD/nml Author: NML Development Team -Author-email: [email protected] +Author-email: [email protected] License: GPL-2.0+ Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console @@ -12,12 +12,13 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Compilers -Requires-Python: >=3.5 +Requires-Python: >=3.10 License-File: LICENSE Requires-Dist: Pillow>=3.4 Dynamic: author diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/README.md new/nml-0.9.0/README.md --- old/nml-0.8.1/README.md 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/README.md 2026-04-16 00:54:52.000000000 +0200 @@ -26,7 +26,8 @@ ## 1) Contact - [issue tracker / source repository](https://github.com/OpenTTD/nml) -- IRC chat using #openttd on irc.oftc.net [more info about our irc channel](https://wiki.openttd.org/Irc) +- IRC chat using #openttd on irc.oftc.net [more info about our IRC channel](https://wiki.openttd.org/en/Development/IRC%20channel) +- The Official OpenTTD [Discord](https://discord.gg/openttd) ## 2) Dependencies @@ -35,12 +36,13 @@ NML requires the following 3rd party packages to run: - `python` - Minimal version is 3.5. Python 2 is not supported. + Minimum version is 3.10*. Python 2 is not supported. + <sub><sup>Python versions before 3.10 might still work, but for developing NML, Python version 3.10 or newer is recommended.</sub></sup> - `python image library` - For install options see https://pillow.readthedocs.io/en/stable/installation.html - Minimal version is 3.4. Older versions are not supported. + For install options see [Pillow: Basic Installation](https://pillow.readthedocs.io/en/stable/installation/basic-installation.html) + <sub><sup>Minimum version is 3.4. Older versions are not supported.</sub></sup> - `ply` - Downloadable from http://www.dabeaz.com/ply/ + NML comes bundled with ply version 2022.10.27, located in the `ply` folder inside the `nml` folder. ### 2.2) Optional dependencies @@ -57,16 +59,26 @@ ## 3) Installation -The easiest way to install NML is by using pip: +The easiest way to install NML is by using pip or [uv](https://docs.astral.sh/uv/): ```bash -pip3 install nml +python3 -m pip install nml +``` +or +```bash +uv tool install nml ``` In order to install NML from a source checkout run: ```bash -python setup.py install +python3 -m pip install . +``` + +If you want to install it in editable mode, so that changes to the code are instantly applied without reinstalling NML, run: + +```bash +python3 -m pip install -e . ``` If you want to install the package manually copy 'nmlc' to any directory diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/docs/changelog.txt new/nml-0.9.0/docs/changelog.txt --- old/nml-0.8.1/docs/changelog.txt 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/docs/changelog.txt 2026-04-16 00:54:52.000000000 +0200 @@ -1,3 +1,21 @@ +0.9.0 (2026-04-15) +------------------------------------------------------------------------ +This release sets python 3.10 as minimum version, and adds support for badges and push-pull trains. + +Support for NewGRF additions of OpenTTD 16: + - Add: Flag for allow unpowered wagons to lead a train when backing up (#420) + - Add: Variable for when a train is driving backwards (#421) + - Change: distinguish perimeter from area in station distributed cargo flag. (#422) + +Support for NewGRF additions of OpenTTD 15: + - Add: Support for NewGRF badges. (#359) + - Change: add support for vehicle var 0x65 (#378) + +Other changes and fixes: + - Fix: Feature 0x14 missing from extract tables. (#403) + - Fix #407: missing position for some action0 errors (#408) + + 0.8.1 (2025-12-02) ------------------------------------------------------------------------ Support for NewGRF additions of OpenTTD 15: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/__version__.py new/nml-0.9.0/nml/__version__.py --- old/nml-0.8.1/nml/__version__.py 2025-12-02 20:17:28.000000000 +0100 +++ new/nml-0.9.0/nml/__version__.py 2026-04-16 00:54:57.000000000 +0200 @@ -1,2 +1,2 @@ # this file is autogenerated by setup.py -version = "0.8.1" +version = "0.9.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action0.py new/nml-0.9.0/nml/actions/action0.py --- old/nml-0.8.1/nml/actions/action0.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action0.py 2026-04-16 00:54:52.000000000 +0200 @@ -211,6 +211,7 @@ BlockAllocation(0, 62, "Roadtype"), BlockAllocation(0, 62, "Tramtype"), BlockAllocation(0, 0xFFFE, "RoadStop"), # UINT16_MAX - 1 + BlockAllocation(0, 64000, "Badge"), ] @@ -780,6 +781,26 @@ 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,22 @@ return [action0] +def get_badgelist_action(badge_list): + index = 0 + actions = [] + while index < len(badge_list): + last = min(index + 250, len(badge_list)) + + 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): action6.free_parameters.save() act6 = action6.Action6() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action0properties.py new/nml-0.9.0/nml/actions/action0properties.py --- old/nml-0.8.1/nml/actions/action0properties.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action0properties.py 2026-04-16 00:54:52.000000000 +0200 @@ -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 @@ # # '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,38 +305,52 @@ 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, data_size, len_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.data_size = data_size + self.len_size = len_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.len_size) for i, val in enumerate(elem): if i % 8 == 0: file.newline() - file.print_varx(val, self.size) + file.print_varx(val, self.data_size) file.newline() 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 + len_size for the length + total_len += len(elem) * self.data_size + self.len_size return total_len -def VariableByteListProp(prop_num, data, extended=False): - return VariableListProp(prop_num, data, 1, extended) +def VariableByteListProp(prop_num, data, len_size=1): + return VariableListProp(prop_num, data, 1, len_size) + + +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 ctt_list(prop_num, *values): @@ -353,8 +367,40 @@ ] -def VariableWordListProp(num_prop, data, extended=False): - return VariableListProp(num_prop, data, 2, extended) +def VariableWordListProp(num_prop, data, len_size=1): + return VariableListProp(num_prop, data, 2, len_size) + + +def badge_list(prop_num, *values): + # values may have multiple entries, if more than one item ID is set + # Each value is an expression.Array of badge labels + + 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", value.pos + ) + + 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): @@ -395,7 +441,7 @@ return value if index == 0 else None if isinstance(value, Array) and len(value.values) == 2 and isinstance(value.values[index], ConstantNumeric): return value.values[index] - raise generic.ScriptError("refittable_cargo_classes must be a constant or an array of 2 constants") + raise generic.ScriptError("refittable_cargo_classes must be a constant or an array of 2 constants", value.pos) def prop_test(value): return value is not None @@ -419,7 +465,7 @@ if len(value.values) == 1: return [Action0Property(single_num, value.values[0].reduce_constant(), 1)] return [VariableByteListProp(multiple_num, [[type.reduce_constant().value for type in value.values]])] - raise generic.ScriptError("'{}' must be a constant or an array of constants".format(prop_name)) + raise generic.ScriptError("'{}' must be a constant or an array of constants".format(prop_name), value.pos) # @@ -490,6 +536,7 @@ "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 @@ -568,6 +615,7 @@ ], "variant_group": {"size": 2, "num": 0x26}, "extra_flags": {"size": 4, "num": 0x27}, + "badges": {"custom_function": lambda value: badge_list(0x2A, value)}, } # fmt: on @@ -658,6 +706,7 @@ "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 @@ -719,6 +768,7 @@ "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 @@ -782,6 +832,8 @@ for layout in value.values: if not isinstance(layout, Array) or len(layout.values) == 0: raise generic.ScriptError("A station layout must be an array of platforms", layout.pos) + if not isinstance(layout.values[0], Array): + raise generic.ScriptError("A platform must be an array of tile types", layout.values[0].pos) length = len(layout.values[0].values) number = len(layout.values) if (length, number) in layouts: @@ -791,7 +843,7 @@ layouts[(length, number)] = [] for platform in layout.values: if not isinstance(platform, Array) or len(platform.values) == 0: - raise generic.ScriptError("A platform must be an array of tile types") + raise generic.ScriptError("A platform must be an array of tile types", platform.pos) if len(platform.values) != length: raise generic.ScriptError("All platforms in a station layout must have the same length", platform.pos) for type in platform.values: @@ -811,7 +863,7 @@ 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 @@ -833,7 +885,7 @@ def station_tile_list(value, prop_num, description): if not isinstance(value, Array) or len(value.values) % 2 != 0: raise generic.ScriptError(f"{description} list must be an array of even length", value.pos) - return [VariableByteListProp(prop_num, [[x.reduce_constant().value for x in value.values]], True)] + return [VariableByteListProp(prop_num, [[x.reduce_constant().value for x in value.values]], 3)] # fmt: off @@ -861,6 +913,7 @@ "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)}, "heights": {"custom_function": lambda x: station_tile_list(x, 0x20, "Station height")}, "blocked_pillars": {"custom_function": lambda x: station_tile_list(x, 0x21, "Station blocked pillar")}, } @@ -1071,6 +1124,7 @@ "multitile_function": mt_house_same, "custom_function": lambda *values: accepted_cargos(0x23, *values), }, + "badges": {"custom_function": lambda value: badge_list(0x24, value)}, } # fmt: on @@ -1092,6 +1146,7 @@ "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 @@ -1364,6 +1419,7 @@ "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 @@ -1467,6 +1523,7 @@ "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 @@ -1507,6 +1564,7 @@ "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 @@ -1553,6 +1611,7 @@ "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)}, } # @@ -1591,6 +1650,7 @@ "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)}, } # @@ -1679,4 +1739,15 @@ "heights": {"custom_function": lambda x: station_tile_list(x, 0x13, "Station height")}, "blocked_pillars": {"custom_function": lambda x: station_tile_list(x, 0x14, "Station blocked pillar")}, "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action2.py new/nml-0.9.0/nml/actions/action2.py --- old/nml-0.8.1/nml/actions/action2.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action2.py 2026-04-16 00:54:52.000000000 +0200 @@ -217,8 +217,9 @@ free_action2_ids.append(act2.id) -# 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 using sprite groups directly: vehicles, stations, canals, cargos, railtypes, airports, roadtypes, tramtypes, +# badges +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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action2var_variables.py new/nml-0.9.0/nml/actions/action2var_variables.py --- old/nml-0.8.1/nml/actions/action2var_variables.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action2var_variables.py 2026-04-16 00:54:52.000000000 +0200 @@ -167,6 +167,7 @@ 'vehicle_is_not_powered' : {'var': 0xFE, 'start': 6, 'size': 1}, 'vehicle_is_reversed' : {'var': 0xFE, 'start': 8, 'size': 1}, 'built_during_preview' : {'var': 0xFE, 'start': 10, 'size': 1}, + 'train_is_driving_backwards' : {'var': 0xFE, 'start': 11, 'size': 1}, } # Vehicle-type-specific variables @@ -235,6 +236,9 @@ 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}, @@ -242,6 +246,8 @@ '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}, } varact2vars60x_trains = { @@ -250,6 +256,7 @@ '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 = { @@ -261,6 +268,8 @@ '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}, } # @@ -293,6 +302,7 @@ '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, 'param_function': badge_parameter}, } varact2vars_stations = { @@ -484,6 +494,7 @@ '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, 'param_function': badge_parameter}, } # @@ -515,6 +526,7 @@ '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, 'param_function': badge_parameter}, } # @@ -620,6 +632,7 @@ '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, 'param_function': badge_parameter}, } # @@ -686,6 +699,8 @@ '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, 'param_function': badge_parameter}, } # @@ -703,7 +718,10 @@ '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, 'param_function': badge_parameter}, +} # # Airport tiles (feature 0x11) @@ -730,6 +748,7 @@ '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, 'param_function': badge_parameter}, } # @@ -747,7 +766,10 @@ '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, 'param_function': badge_parameter}, +} # # Tramtypes (feature 0x13) @@ -764,7 +786,10 @@ '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, 'param_function': badge_parameter}, +} # @@ -848,6 +873,14 @@ '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 @@ -882,11 +915,12 @@ 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) +scope_badges = VarAct2Scope("Badges", varact2vars_badges, {}) varact2features = [ VarAct2Feature(scope_trains, scope_trains), @@ -910,4 +944,5 @@ VarAct2Feature(scope_roadtypes, None), VarAct2Feature(scope_tramtypes, None), VarAct2Feature(scope_roadstops, scope_towns), + VarAct2Feature(scope_badges, None), ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action3_callbacks.py new/nml-0.9.0/nml/actions/action3_callbacks.py --- old/nml-0.8.1/nml/actions/action3_callbacks.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action3_callbacks.py 2026-04-16 00:54:52.000000000 +0200 @@ -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,23 @@ 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } + +# 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}, + 'industries' : {'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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/actions/action4.py new/nml-0.9.0/nml/actions/action4.py --- old/nml-0.8.1/nml/actions/action4.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/actions/action4.py 2026-04-16 00:54:52.000000000 +0200 @@ -199,7 +199,7 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/ast/badgetable.py new/nml-0.9.0/nml/ast/badgetable.py --- old/nml-0.8.1/nml/ast/badgetable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/nml-0.9.0/nml/ast/badgetable.py 2026-04-16 00:54:52.000000000 +0200 @@ -0,0 +1,52 @@ +__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) + 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/ast/general.py new/nml-0.9.0/nml/ast/general.py --- old/nml-0.8.1/nml/ast/general.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/ast/general.py 2026-04-16 00:54:52.000000000 +0200 @@ -38,6 +38,7 @@ "FEAT_ROADTYPES": 0x12, "FEAT_TRAMTYPES": 0x13, "FEAT_ROADSTOPS": 0x14, + "FEAT_BADGES": 0x15, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/editors/extract_tables.py new/nml-0.9.0/nml/editors/extract_tables.py --- old/nml-0.8.1/nml/editors/extract_tables.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/editors/extract_tables.py 2026-04-16 00:54:52.000000000 +0200 @@ -92,6 +92,7 @@ action0properties.properties[0x11], action0properties.properties[0x12], action0properties.properties[0x13], + action0properties.properties[0x14], ] properties = set() @@ -120,6 +121,7 @@ action3_callbacks.callbacks[0x11], action3_callbacks.callbacks[0x12], action3_callbacks.callbacks[0x13], + action3_callbacks.callbacks[0x14], ] callbacks = set() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/expression/functioncall.py new/nml-0.9.0/nml/expression/functioncall.py --- old/nml-0.8.1/nml/expression/functioncall.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/expression/functioncall.py 2026-04-16 00:54:52.000000000 +0200 @@ -470,7 +470,7 @@ 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 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/global_constants.py new/nml-0.9.0/nml/global_constants.py --- old/nml-0.8.1/nml/global_constants.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/global_constants.py 2026-04-16 00:54:52.000000000 +0200 @@ -144,10 +144,18 @@ "VEHICLE_FLAG_NO_BREAKDOWN_SMOKE": 6, # vehicle extra flags - "VEHICLE_FLAG_DISABLE_NEW_VEHICLE_MESSAGE" : 0, - "VEHICLE_FLAG_DISABLE_EXCLUSIVE_PREVIEW" : 1, + "VEHICLE_FLAG_DISABLE_NEW_VEHICLE_MESSAGE" : 0, + "VEHICLE_FLAG_DISABLE_EXCLUSIVE_PREVIEW" : 1, "VEHICLE_FLAG_SYNC_VARIANT_EXCLUSIVE_PREVIEW" : 2, - "VEHICLE_FLAG_SYNC_VARIANT_RELIABILITY" : 3, + "VEHICLE_FLAG_SYNC_VARIANT_RELIABILITY" : 3, + "VEHICLE_FLAG_TRAIN_HAS_CAB" : 4, + + # badge flags + "BADGE_FLAG_COPY_TO_RELATED_ENTITY" : 0, + "BADGE_FLAG_NAME_LIST_STOP" : 1, + "BADGE_FLAG_NAME_LIST_FIRST_ONLY" : 2, + "BADGE_FLAG_USE_COMPANY_COLOUR" : 3, + "BADGE_FLAG_NAME_SKIP" : 4, # Graphic flags for waterfeatures "WATERFEATURE_ALTERNATIVE_SPRITES" : 0, @@ -372,10 +380,12 @@ "CB_RANDOM_TRIGGER" : 0x01, # station general flags - "STAT_FLAG_DISTRIBUTED_CARGO" : 1, + "STAT_FLAG_DISTRIBUTED_CARGO_BY_PERIMETER" : 1, + "STAT_FLAG_DISTRIBUTED_CARGO" : "STAT_FLAG_DISTRIBUTED_CARGO_BY_PERIMETER", "STAT_FLAG_RANDOM_ANIMATION" : 2, "STAT_FLAG_CUSTOM_FOUNDATIONS" : 3, "STAT_FLAG_EXTENDED_FOUNDATIONS" : 4, + "STAT_FLAG_DISTRIBUTED_CARGO_BY_AREA" : 5, # station tile flags "STAT_TILE_PYLON" : 0, @@ -1437,6 +1447,7 @@ cargo_numbers = {} +badge_numbers = {} is_default_railtype_table = True # if no railtype_table is provided, OpenTTD assumes these 3 railtypes @@ -1484,6 +1495,7 @@ (patch_variables, patch_variable), (named_parameters, param_from_name), cargo_numbers, + badge_numbers, railtype_table, roadtype_table, tramtype_table, @@ -1504,6 +1516,8 @@ 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(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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/parser.py new/nml-0.9.0/nml/parser.py --- old/nml-0.8.1/nml/parser.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/parser.py 2026-04-16 00:54:52.000000000 +0200 @@ -13,13 +13,14 @@ with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" -from .ply import yacc +from nml.ply import yacc from nml import expression, generic, nmlop, tokens, unit from nml.actions import actionD, real_sprite from nml.ast import ( alt_sprites, assignment, + badgetable, base_graphics, basecost, cargotable, @@ -120,6 +121,7 @@ | template_declaration | tilelayout | town_names + | badgetable | cargotable | railtype | roadtype @@ -741,6 +743,21 @@ "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/spriteencoder.py new/nml-0.9.0/nml/spriteencoder.py --- old/nml-0.8.1/nml/spriteencoder.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/spriteencoder.py 2026-04-16 00:54:52.000000000 +0200 @@ -297,7 +297,7 @@ if im.mode == "RGBA": info_byte |= INFO_ALPHA - (im_width, im_height) = im.size + im_width, im_height = im.size if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos) @@ -320,7 +320,7 @@ im_mask_pal = palette.validate_palette(mask_im, filename_8bpp.value) info_byte |= INFO_PAL - (im_width, im_height) = mask_im.size + im_width, im_height = mask_im.size if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/tokens.py new/nml-0.9.0/nml/tokens.py --- old/nml-0.8.1/nml/tokens.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/tokens.py 2026-04-16 00:54:52.000000000 +0200 @@ -16,7 +16,7 @@ import re import sys -from .ply import lex +from nml.ply import lex from nml import expression, generic @@ -26,6 +26,7 @@ "var": "VARIABLE", "param": "PARAMETER", "cargotable": "CARGOTABLE", + "badgetable": "BADGETABLE", "railtypetable": "RAILTYPETABLE", "roadtypetable": "ROADTYPETABLE", "tramtypetable": "TRAMTYPETABLE", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml/version_info.py new/nml-0.9.0/nml/version_info.py --- old/nml-0.8.1/nml/version_info.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/nml/version_info.py 2026-04-16 00:54:52.000000000 +0200 @@ -21,19 +21,16 @@ versions = {} # PIL try: - import PIL + from PIL import __version__ as pil_version - versions["PIL"] = PIL.__version__ + versions["PIL"] = pil_version except ImportError: versions["PIL"] = "Not found!" # PLY - try: - from ply import lex + from nml.ply import __version__ as ply_version - versions["PLY"] = lex.__version__ - except ImportError: - versions["PLY"] = "Not found!" + versions["PLY"] = ply_version return versions diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml.egg-info/PKG-INFO new/nml-0.9.0/nml.egg-info/PKG-INFO --- old/nml-0.8.1/nml.egg-info/PKG-INFO 2025-12-02 20:17:28.000000000 +0100 +++ new/nml-0.9.0/nml.egg-info/PKG-INFO 2026-04-16 00:54:57.000000000 +0200 @@ -1,10 +1,10 @@ Metadata-Version: 2.4 Name: nml -Version: 0.8.1 +Version: 0.9.0 Summary: An OpenTTD NewGRF compiler for the nml language Home-page: https://github.com/OpenTTD/nml Author: NML Development Team -Author-email: [email protected] +Author-email: [email protected] License: GPL-2.0+ Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console @@ -12,12 +12,13 @@ Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Compilers -Requires-Python: >=3.5 +Requires-Python: >=3.10 License-File: LICENSE Requires-Dist: Pillow>=3.4 Dynamic: author diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/nml.egg-info/SOURCES.txt new/nml-0.9.0/nml.egg-info/SOURCES.txt --- old/nml-0.8.1/nml.egg-info/SOURCES.txt 2025-12-02 20:17:28.000000000 +0100 +++ new/nml-0.9.0/nml.egg-info/SOURCES.txt 2026-04-16 00:54:57.000000000 +0200 @@ -118,6 +118,7 @@ nml/ast/__init__.py nml/ast/alt_sprites.py nml/ast/assignment.py +nml/ast/badgetable.py nml/ast/base_graphics.py nml/ast/base_statement.py nml/ast/basecost.py @@ -214,6 +215,7 @@ regression/039_storage.nml regression/040_station.nml regression/041_articulated_tram_32bpp.nml +regression/042_badges.nml regression/Makefile regression/arctic_railwagons.pcx regression/beef.wav @@ -313,6 +315,8 @@ regression/expected/040_station.nfo regression/expected/041_articulated_tram_32bpp.grf regression/expected/041_articulated_tram_32bpp.nfo +regression/expected/042_badges.grf +regression/expected/042_badges.nfo regression/expected/example_industry.grf regression/expected/example_industry.nfo regression/expected/example_object.grf diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/042_badges.nml new/nml-0.9.0/regression/042_badges.nml --- old/nml-0.8.1/regression/042_badges.nml 1970-01-01 01:00:00.000000000 +0100 +++ new/nml-0.9.0/regression/042_badges.nml 2026-04-16 00:54:52.000000000 +0200 @@ -0,0 +1,72 @@ +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); + } +} + +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) { + property { + label: "power/diesel"; + name: string(STR_POWER_DIESEL); + } +} + +item (FEAT_BADGES, electric) { + property { + label: "power/electric"; + name: string(STR_POWER_ELECTRIC); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/013_train_callback.nfo new/nml-0.9.0/regression/expected/013_train_callback.nfo --- old/nml-0.8.1/regression/expected/013_train_callback.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/013_train_callback.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -233,7 +233,7 @@ 1D \dx00000000 29 \wx01CB 1D \dx00000000 -2C \b4 +2C 04 00 01 0C 0D 1D \dx00000000 15 13 @@ -250,7 +250,7 @@ 1C 28 05 00 05 00 -34 \b2 +34 02 00 01 0B \wx0000 0E \dx00004C30 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/017_articulated_tram.nfo new/nml-0.9.0/regression/expected/017_articulated_tram.nfo --- old/nml-0.8.1/regression/expected/017_articulated_tram.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/017_articulated_tram.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -45,7 +45,7 @@ 16 \dx00000000 1E \wx0000 16 \dx00000000 -24 \b0 +24 00 16 \dx00000000 10 FF 1C 01 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/030_house.nfo new/nml-0.9.0/regression/expected/030_house.nfo --- old/nml-0.8.1/regression/expected/030_house.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/030_house.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/040_station.nfo new/nml-0.9.0/regression/expected/040_station.nfo --- old/nml-0.8.1/regression/expected/040_station.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/040_station.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -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 @@ -35,9 +35,9 @@ 04 04 06 06 \b0 \b0 -20 FF \w8 +20 FF \wx0008 01 01 02 02 03 03 04 04 -21 FF \w8 +21 FF \wx0008 0C 03 0C 03 9C 63 9C 63 6 * 11 04 04 FF 01 \wxC4FF "Test" 00 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/041_articulated_tram_32bpp.nfo new/nml-0.9.0/regression/expected/041_articulated_tram_32bpp.nfo --- old/nml-0.8.1/regression/expected/041_articulated_tram_32bpp.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/041_articulated_tram_32bpp.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -45,7 +45,7 @@ 16 \dx00000000 1E \wx0000 16 \dx00000000 -24 \b0 +24 00 16 \dx00000000 10 FF 1C 01 Binary files old/nml-0.8.1/regression/expected/042_badges.grf and new/nml-0.9.0/regression/expected/042_badges.grf differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/042_badges.nfo new/nml-0.9.0/regression/expected/042_badges.nfo --- old/nml-0.8.1/regression/expected/042_badges.nfo 1970-01-01 01:00:00.000000000 +0100 +++ new/nml-0.9.0/regression/expected/042_badges.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -0,0 +1,94 @@ +// 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 \d24 + +1 * 54 14 "C" "INFO" +"B" "VRSN" \w4 \dx00000000 +"B" "MINV" \w4 \dx00000000 +"B" "NPAR" \w1 00 +"B" "PALS" \w1 "A" +"B" "BLTR" \w1 "3" +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 * 13 04 15 7F 01 FF \wx0000 "Power" 00 + +15 * 20 00 15 \b1 01 FF \wx0001 +08 "power/steam" 00 + +16 * 13 04 15 7F 01 FF \wx0001 "Steam" 00 + +17 * 6 01 15 \b1 FF \wx0001 + +18 opengfx_generic_trams1.png 32bpp 96 56 28 15 -14 -8 normal + +// Name: sprite_steam - feature 15 +19 * 7 02 15 FE \b1 \b0 +\w0 + + +20 * 7 03 15 01 01 \b0 +\wx00FE // sprite_steam; + +21 * 21 00 15 \b1 01 FF \wx0002 +08 "power/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 * 16 04 15 7F 01 FF \wx0003 "Electric" 00 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/example_industry.nfo new/nml-0.9.0/regression/expected/example_industry.nfo --- old/nml-0.8.1/regression/expected/example_industry.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/example_industry.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/example_road_vehicle.nfo new/nml-0.9.0/regression/expected/example_road_vehicle.nfo --- old/nml-0.8.1/regression/expected/example_road_vehicle.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/example_road_vehicle.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -196,10 +196,10 @@ 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 @@ 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 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/expected/example_train.nfo new/nml-0.9.0/regression/expected/example_train.nfo --- old/nml-0.8.1/regression/expected/example_train.nfo 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/expected/example_train.nfo 2026-04-16 00:54:52.000000000 +0200 @@ -305,9 +305,9 @@ 1D \dx00000000 29 \wx0000 1D \dx00000000 -2C \b0 +2C 00 1D \dx00000000 -2D \b0 +2D 00 1D \dx00000000 07 06 17 2D diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/regression/lang/english.lng new/nml-0.9.0/regression/lang/english.lng --- old/nml-0.8.1/regression/lang/english.lng 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/regression/lang/english.lng 2026-04-16 00:54:52.000000000 +0200 @@ -43,3 +43,9 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nml-0.8.1/setup.py new/nml-0.9.0/setup.py --- old/nml-0.8.1/setup.py 2025-12-02 20:17:25.000000000 +0100 +++ new/nml-0.9.0/setup.py 2026-04-16 00:54:52.000000000 +0200 @@ -39,18 +39,19 @@ "License :: OSI Approved :: GNU General Public License (GPL)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Compilers", ], url="https://github.com/OpenTTD/nml", author="NML Development Team", - author_email="[email protected]", + author_email="[email protected]", entry_points={"console_scripts": ["nmlc = nml.main:run"]}, ext_modules=[Extension("nml_lz77", ["nml/_lz77.c"], optional=True)], - python_requires=">=3.5", + python_requires=">=3.10", install_requires=[ "Pillow>=3.4", ],
