Skip to content

Commit

Permalink
Add: Support for NewGRF badges.
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterN committed Jan 18, 2025
1 parent f2b775c commit 227635d
Show file tree
Hide file tree
Showing 22 changed files with 380 additions and 52 deletions.
28 changes: 28 additions & 0 deletions nml/actions/action0.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
]


Expand Down Expand Up @@ -780,13 +781,40 @@ 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))
action0.num_ids = len(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()
Expand Down
101 changes: 85 additions & 16 deletions nml/actions/action0properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

import itertools

from nml import generic, nmlop, global_constants
from nml import generic, grfstrings, nmlop, global_constants
from nml.expression import (

Check failure on line 19 in nml/actions/action0properties.py

View workflow job for this annotation

GitHub Actions / Flake8 / Flake8

'nml.expression.functioncall' imported but unused
functioncall,
AcceptCargo,
Array,
BitMask,
Expand Down Expand Up @@ -170,7 +171,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
Expand Down Expand Up @@ -305,22 +306,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()
Expand All @@ -330,13 +327,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):
Expand All @@ -353,8 +368,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):
Expand Down Expand Up @@ -478,6 +525,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

Expand Down Expand Up @@ -556,6 +604,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

Expand Down Expand Up @@ -646,6 +695,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

Expand Down Expand Up @@ -707,6 +757,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

Expand Down Expand Up @@ -799,7 +850,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
Expand Down Expand Up @@ -843,6 +894,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

Expand Down Expand Up @@ -1051,6 +1103,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

Expand All @@ -1072,6 +1125,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

Expand Down Expand Up @@ -1344,6 +1398,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

Expand Down Expand Up @@ -1447,6 +1502,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

Expand Down Expand Up @@ -1487,6 +1543,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

Expand Down Expand Up @@ -1533,6 +1590,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)},
}

#
Expand Down Expand Up @@ -1571,6 +1629,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)},
}

#
Expand Down Expand Up @@ -1657,4 +1716,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},
}
Loading

0 comments on commit 227635d

Please sign in to comment.