Skip to content

Commit 8132947

Browse files
committed
Add: Support for NewGRF badges.
1 parent f2b775c commit 8132947

22 files changed

+379
-52
lines changed

nml/actions/action0.py

+28
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def find_unused(self, length):
211211
BlockAllocation(0, 62, "Roadtype"),
212212
BlockAllocation(0, 62, "Tramtype"),
213213
BlockAllocation(0, 0xFFFE, "RoadStop"), # UINT16_MAX - 1
214+
BlockAllocation(0, 64000, "Badge"),
214215
]
215216

216217

@@ -780,13 +781,40 @@ def get_size(self):
780781
return len(self.id_list) * 4 + 1
781782

782783

784+
class StringListProp(BaseAction0Property):
785+
def __init__(self, prop_num, string_list):
786+
self.prop_num = prop_num
787+
self.string_list = string_list
788+
789+
def write(self, file):
790+
file.print_bytex(self.prop_num)
791+
for i, string_val in enumerate(self.string_list):
792+
if i > 0 and i % 5 == 0:
793+
file.newline()
794+
file.print_string(string_val.value, True, True)
795+
file.newline()
796+
797+
def get_size(self):
798+
size = 1
799+
for i, string_val in enumerate(self.string_list):
800+
size += grfstrings.get_string_size(string_val.value, True, True)
801+
return size
802+
803+
783804
def get_cargolist_action(cargo_list):
784805
action0 = Action0(0x08, 0)
785806
action0.prop_list.append(IDListProp(0x09, cargo_list))
786807
action0.num_ids = len(cargo_list)
787808
return [action0]
788809

789810

811+
def get_badgelist_action(badge_list):
812+
action0 = Action0(0x08, 0)
813+
action0.prop_list.append(StringListProp(0x18, badge_list))
814+
action0.num_ids = len(badge_list)
815+
return [action0]
816+
817+
790818
def get_tracktypelist_action(table_prop_id, cond_tracktype_not_defined, tracktype_list):
791819
action6.free_parameters.save()
792820
act6 = action6.Action6()

nml/actions/action0properties.py

+84-16
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import itertools
1717

18-
from nml import generic, nmlop, global_constants
18+
from nml import generic, grfstrings, nmlop, global_constants
1919
from nml.expression import (
2020
AcceptCargo,
2121
Array,
@@ -170,7 +170,7 @@ def get_size(self):
170170
#
171171
# 'required' (value doesn't matter) if the property is required for the item to be valid.
172172

173-
properties = 0x15 * [None]
173+
properties = 0x16 * [None]
174174

175175
#
176176
# Some helper functions that are used for multiple features
@@ -305,22 +305,18 @@ class VariableListProp(BaseAction0Property):
305305
Property value that is a variable-length list of variable sized values, the list length is written before the data.
306306
"""
307307

308-
def __init__(self, prop_num, data, size, extended):
308+
def __init__(self, prop_num, data, size, size_size):
309309
# data is a list, each element belongs to an item ID
310310
# Each element in the list is a list of cargo types
311311
self.prop_num = prop_num
312312
self.data = data
313313
self.size = size
314-
self.extended = extended
314+
self.size_size = size_size
315315

316316
def write(self, file):
317317
file.print_bytex(self.prop_num)
318318
for elem in self.data:
319-
if self.extended:
320-
file.print_bytex(0xFF)
321-
file.print_word(len(elem))
322-
else:
323-
file.print_byte(len(elem))
319+
file.print_varx(len(elem), self.size_size)
324320
for i, val in enumerate(elem):
325321
if i % 8 == 0:
326322
file.newline()
@@ -330,13 +326,31 @@ def write(self, file):
330326
def get_size(self):
331327
total_len = 1 # Prop number
332328
for elem in self.data:
333-
# For each item ID to set, make space for all values + 3 or 1 for the length
334-
total_len += len(elem) * self.size + (3 if self.extended else 1)
329+
# For each item ID to set, make space for all values + size_size for the length
330+
total_len += len(elem) * self.size + self.size_size
335331
return total_len
336332

337333

338-
def VariableByteListProp(prop_num, data, extended=False):
339-
return VariableListProp(prop_num, data, 1, extended)
334+
class StringProp(BaseAction0Property):
335+
"""
336+
Property value that is zero-terminated string.
337+
"""
338+
339+
def __init__(self, prop_num, string):
340+
self.prop_num = prop_num
341+
self.string = string
342+
343+
def write(self, file):
344+
file.print_bytex(self.prop_num)
345+
file.print_string(self.string.value, True, True)
346+
file.newline()
347+
348+
def get_size(self):
349+
return grfstrings.get_string_size(self.string.value) + 1
350+
351+
352+
def VariableByteListProp(prop_num, data, size_size=1):
353+
return VariableListProp(prop_num, data, 1, size_size)
340354

341355

342356
def ctt_list(prop_num, *values):
@@ -353,8 +367,40 @@ def ctt_list(prop_num, *values):
353367
]
354368

355369

356-
def VariableWordListProp(num_prop, data, extended=False):
357-
return VariableListProp(num_prop, data, 2, extended)
370+
def VariableWordListProp(num_prop, data, size_size=1):
371+
return VariableListProp(num_prop, data, 2, size_size)
372+
373+
374+
def badge_list(prop_num, *values):
375+
# values may have multiple entries, if more than one item ID is set (e.g. multitile houses)
376+
# Each value is an expression.Array of cargo types
377+
378+
table = global_constants.badge_numbers
379+
380+
for value in values:
381+
if not isinstance(value, Array):
382+
raise generic.ScriptError("Value of badgelist property must be an array", value.pos)
383+
384+
for badge in value.values:
385+
if not isinstance(badge, StringLiteral) or badge.value not in table:
386+
raise generic.ScriptError(
387+
"Parameter for badges must be a string literal that is also in your badge table"
388+
)
389+
390+
return [
391+
VariableListProp(
392+
prop_num,
393+
[[table[badge.value] for badge in single_item_array.values] for single_item_array in values],
394+
2,
395+
2,
396+
)
397+
]
398+
399+
400+
def string_property(prop_num, value):
401+
if not isinstance(value, StringLiteral):
402+
raise generic.ScriptError("Value of label property must be a StringLiteral", value.pos)
403+
return [StringProp(prop_num, value)]
358404

359405

360406
def accepted_cargos(prop_num, *values):
@@ -478,6 +524,7 @@ def prop_test(value):
478524
"curve_speed_mod": {"size": 2, "num": 0x2E, "unit_conversion": 256},
479525
"variant_group": {"size": 2, "num": 0x2F},
480526
"extra_flags": {"size": 4, "num": 0x30},
527+
"badges": {"custom_function": lambda value: badge_list(0x33, value)},
481528
}
482529
# fmt: on
483530

@@ -556,6 +603,7 @@ def prop15_test(value):
556603
],
557604
"variant_group": {"size": 2, "num": 0x26},
558605
"extra_flags": {"size": 4, "num": 0x27},
606+
"badges": {"custom_function": lambda value: badge_list(0x2A, value)},
559607
}
560608
# fmt: on
561609

@@ -646,6 +694,7 @@ def prop23_test(value):
646694
"variant_group": {"size": 2, "num": 0x20},
647695
"extra_flags": {"size": 4, "num": 0x21},
648696
"acceleration": {"size": 1, "num": 0x24},
697+
"badges": {"custom_function": lambda value: badge_list(0x26, value)},
649698
}
650699
# fmt: on
651700

@@ -707,6 +756,7 @@ def aircraft_is_large(value):
707756
"range": {"size": 2, "num": 0x1F},
708757
"variant_group": {"size": 2, "num": 0x20},
709758
"extra_flags": {"size": 4, "num": 0x21},
759+
"badges": {"custom_function": lambda value: badge_list(0x24, value)},
710760
}
711761
# fmt: on
712762

@@ -799,7 +849,7 @@ def station_tile_flags(value):
799849
if not isinstance(value, Array) or len(value.values) % 2 != 0:
800850
raise generic.ScriptError("Flag list must be an array of even length", value.pos)
801851
if len(value.values) > 8:
802-
return [VariableByteListProp(0x1E, [[flags.reduce_constant().value for flags in value.values]], True)]
852+
return [VariableByteListProp(0x1E, [[flags.reduce_constant().value for flags in value.values]], 3)]
803853
pylons = 0
804854
wires = 0
805855
blocked = 0
@@ -843,6 +893,7 @@ def station_tile_flags(value):
843893
"name": {"size": 2, "num": (256, -1, 0x1C), "string": (256, 0xC5, 0xDC), "required": True},
844894
"classname": {"size": 2, "num": (256, -1, 0x1D), "string": (256, 0xC4, 0xDC)},
845895
"tile_flags": {"custom_function": station_tile_flags}, # = prop 1E
896+
"badges": {"custom_function": lambda value: badge_list(0x1F, value)},
846897
}
847898
# fmt: on
848899

@@ -1051,6 +1102,7 @@ def mt_house_class(value, num_ids, size_bit):
10511102
"multitile_function": mt_house_same,
10521103
"custom_function": lambda *values: accepted_cargos(0x23, *values),
10531104
},
1105+
"badges": {"custom_function": lambda value: badge_list(0x24, value)},
10541106
}
10551107
# fmt: on
10561108

@@ -1072,6 +1124,7 @@ def mt_house_class(value, num_ids, size_bit):
10721124
"animation_triggers": {"size": 1, "num": 0x11},
10731125
"special_flags": {"size": 1, "num": 0x12},
10741126
"accepted_cargos": {"custom_function": lambda value: accepted_cargos(0x13, value)},
1127+
"badges": {"custom_function": lambda value: badge_list(0x14, value)},
10751128
}
10761129
# fmt: on
10771130

@@ -1344,6 +1397,7 @@ def check_accept(acp):
13441397
"nearby_station_name": {"size": 2, "num": 0x24, "string": 0xDC},
13451398
# prop 25+26+27+28 combined in one structure
13461399
"cargo_types": {"custom_function": industry_cargo_types},
1400+
"badges": {"custom_function": lambda value: badge_list(0x29, value)},
13471401
}
13481402
# fmt: on
13491403

@@ -1447,6 +1501,7 @@ def airport_layouts(value):
14471501
"noise_level": {"size": 1, "num": 0x0F},
14481502
"name": {"size": 2, "num": 0x10, "string": 0xDC},
14491503
"maintenance_cost": {"size": 2, "num": 0x11},
1504+
"badges": {"custom_function": lambda value: badge_list(0x12, value)},
14501505
}
14511506
# fmt: on
14521507

@@ -1487,6 +1542,7 @@ def object_size(value):
14871542
"height": {"size": 1, "num": 0x16},
14881543
"num_views": {"size": 1, "num": 0x17},
14891544
"count_per_map256": {"size": 1, "num": 0x18},
1545+
"badges": {"custom_function": lambda value: badge_list(0x19, value)},
14901546
}
14911547
# fmt: on
14921548

@@ -1533,6 +1589,7 @@ def label_list(value, prop_num, description):
15331589
"sort_order": {"size": 1, "num": 0x1A},
15341590
"name": {"size": 2, "num": 0x1B, "string": 0xDC},
15351591
"maintenance_cost": {"size": 2, "num": 0x1C},
1592+
"badges": {"custom_function": lambda value: badge_list(0x1E, value)},
15361593
}
15371594

15381595
#
@@ -1571,6 +1628,7 @@ def label_list(value, prop_num, description):
15711628
"animation_info": {"size": 2, "num": 0x0F, "value_function": animation_info},
15721629
"animation_speed": {"size": 1, "num": 0x10},
15731630
"animation_triggers": {"size": 1, "num": 0x11},
1631+
"badges": {"custom_function": lambda value: badge_list(0x12, value)},
15741632
}
15751633

15761634
#
@@ -1657,4 +1715,14 @@ def byte_sequence_list(value, prop_num, description, expected_count):
16571715
# 11 (callback flags) is not set by user
16581716
"general_flags": {"size": 4, "num": 0x12},
16591717
"cost_multipliers": {"custom_function": lambda x: byte_sequence_list(x, 0x15, "Cost multipliers", 2)},
1718+
"badges": {"custom_function": lambda value: badge_list(0x16, value)},
1719+
}
1720+
1721+
#
1722+
# Feature 0x15 (Badges)
1723+
#
1724+
1725+
properties[0x15] = {
1726+
'label': {'custom_function': lambda x: string_property(0x08, x), "required": True},
1727+
'flags': {'size': 4, 'num': 0x09},
16601728
}

0 commit comments

Comments
 (0)