Skip to content

Commit a7267f2

Browse files
committed
(building/station) move AStation into agrf
1 parent 6d8d7c6 commit a7267f2

File tree

3 files changed

+478
-0
lines changed

3 files changed

+478
-0
lines changed

agrf/lib/building/station.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import grf
2+
from agrf.actions import FakeReferencingAction, FakeReferencedAction
3+
from agrf.lib.building.registers import code, default_code
4+
from agrf.lib.building.station_tile_switch import StationTileSwitch
5+
from agrf.strings import label_printable
6+
from agrf.utils import unique
7+
8+
9+
class AStation(grf.SpriteGenerator):
10+
def __init__(
11+
self,
12+
*,
13+
id,
14+
translation_name,
15+
layouts,
16+
callbacks=None,
17+
non_traversable_tiles=0x0,
18+
is_waypoint=False,
19+
doc_layout=None,
20+
enable_if=None,
21+
make_foundation=False,
22+
foundation_object=None,
23+
extra_code="",
24+
action2_pool=None,
25+
station_tile_switch_class=None,
26+
**props,
27+
):
28+
super().__init__()
29+
self.id = id
30+
self.translation_name = translation_name
31+
self.layouts = layouts
32+
if callbacks is None:
33+
callbacks = {}
34+
self.callbacks = grf.make_callback_manager(grf.STATION, callbacks)
35+
self.is_waypoint = is_waypoint
36+
self.doc_layout = doc_layout
37+
self.enable_if = enable_if
38+
self.make_foundation = make_foundation
39+
self.foundation_object = foundation_object
40+
self.extra_code = extra_code
41+
self.action2_pool = action2_pool
42+
self._station_tile_switch_class = station_tile_switch_class
43+
self._props = {
44+
**props,
45+
"non_traversable_tiles": non_traversable_tiles,
46+
"draw_pylon_tiles": 0xFF ^ non_traversable_tiles,
47+
"hide_wire_tiles": non_traversable_tiles,
48+
}
49+
50+
@property
51+
def class_label_plain(self):
52+
return label_printable(self._props["class_label"])
53+
54+
def _is_station_tile_switch(self, obj):
55+
return self._station_tile_switch_class is not None and isinstance(obj, self._station_tile_switch_class)
56+
57+
def _map_foundation(self, obj):
58+
if self._is_station_tile_switch(obj):
59+
return obj.to_index(None)
60+
return obj
61+
62+
def get_sprites(self, g, sprites=None):
63+
is_managed_by_metastation = sprites is not None
64+
if isinstance(self.translation_name, str):
65+
translated_name = g.strings[f"STR_STATION_{self.translation_name}"]
66+
elif callable(self.translation_name):
67+
translated_name = self.translation_name(g.strings)
68+
else:
69+
raise TypeError(f"translation_name must be a string or callable, got {type(self.translation_name)}")
70+
71+
extra_props = {"station_name": g.strings.add(translated_name).get_persistent_id()}
72+
if not self.is_waypoint:
73+
extra_props["station_class_name"] = g.strings.add(
74+
g.strings[f"STR_STATION_CLASS_{self.class_label_plain}"]
75+
).get_persistent_id()
76+
77+
res = []
78+
79+
if self.action2_pool is not None:
80+
graphics = self.action2_pool.get_action_2_zero()
81+
else:
82+
graphics = grf.GenericSpriteLayout(ent1=[0], ent2=[0], feature=grf.STATION)
83+
84+
props = self._props.copy()
85+
86+
use_foundation = self.make_foundation or self.foundation_object is not None
87+
88+
if use_foundation and self.action2_pool is not None:
89+
if self.make_foundation:
90+
cb14 = self.callbacks.select_sprite_layout.default
91+
self.callbacks.select_sprite_layout.default = cb14.to_index(self.layouts)
92+
foundations = self.action2_pool.map_switch(cb14)
93+
foundations = self._map_foundation(foundations)
94+
else: # foundation_object
95+
if self.foundation_object is self.foundation_object.M:
96+
foundation_object_list = [self.foundation_object]
97+
else:
98+
foundation_object_list = [self.foundation_object, self.foundation_object.M]
99+
100+
foundation_list = [
101+
self.action2_pool.map_foundation_switch(x.to_switch()) for x in foundation_object_list
102+
]
103+
foundation_list = [self._map_foundation(x) for x in foundation_list]
104+
105+
if len(foundation_list) == 1:
106+
foundations = foundation_list[0]
107+
else:
108+
foundations = grf.Switch(
109+
ranges={1: foundation_list[1]}, code="extra_callback_info2 % 2", default=foundation_list[0]
110+
)
111+
112+
self.callbacks.graphics = grf.GraphicsCallback(
113+
default=grf.Switch(
114+
ranges={2: foundations},
115+
code=code + self.extra_code + default_code + "\nextra_callback_info1_byte",
116+
default=graphics,
117+
),
118+
purchase=grf.Switch(ranges={0: graphics}, code=code + self.extra_code, default=graphics),
119+
)
120+
props["general_flags"] = props.get("general_flags", 0) | 0b1000
121+
else:
122+
self.callbacks.graphics = grf.Switch(
123+
ranges={0: graphics}, code=code + self.extra_code + default_code, default=graphics
124+
)
125+
126+
cb_props = {}
127+
self.callbacks.set_flag_props(cb_props)
128+
129+
if not is_managed_by_metastation:
130+
sprites = self.sprites
131+
res.append(grf.Action1(feature=grf.STATION, set_count=1, sprite_count=len(self.sprites)))
132+
133+
for s in self.sprites:
134+
res.append(s)
135+
136+
if self.id >= 0xFF:
137+
res.append(grf.If(is_static=False, variable=0xA1, condition=0x04, value=0x1E000000, skip=255, varsize=4))
138+
if self.enable_if:
139+
for cond in self.enable_if:
140+
res.append(cond.make_if(is_static=False, skip=255))
141+
res.append(
142+
definition := grf.Define(
143+
feature=grf.STATION,
144+
id=self.id,
145+
props={
146+
"class_label": (b"WAYP" if self.is_waypoint else props["class_label"]),
147+
"advanced_layout": grf.SpriteLayoutList([l.to_grf(sprites) for l in self.layouts]),
148+
**{k: v for k, v in props.items() if k != "class_label"},
149+
**cb_props,
150+
**(extra_props if self.id >= 0xFF else {}),
151+
},
152+
)
153+
)
154+
if self.id >= 0xFF or self.enable_if:
155+
res.append(grf.Label(255, bytes()))
156+
157+
if self.is_waypoint:
158+
openttd_15_props = {
159+
"class_label": b"\xff" + self._props["class_label"][1:],
160+
"station_class_name": g.strings.add(
161+
g.strings[f"STR_STATION_CLASS_{self.class_label_plain}"]
162+
).get_persistent_id(),
163+
}
164+
res.append(grf.If(is_static=False, variable=0xA1, condition=0x04, value=0x1F000000, skip=1, varsize=4))
165+
res.append(grf.Define(feature=grf.STATION, id=self.id, props=openttd_15_props))
166+
167+
[map_action] = self.callbacks.make_map_action(definition)
168+
if self.id >= 0xFF:
169+
if_action = FakeReferencedAction(
170+
grf.If(is_static=False, variable=0xA1, condition=0x04, value=0x1E000000, skip=1, varsize=4), grf.STATION
171+
)
172+
map_action = FakeReferencingAction(map_action, [if_action])
173+
res.append(map_action)
174+
175+
if self.id < 0xFF:
176+
class_name = g.strings[f"STR_STATION_CLASS_{self.class_label_plain}"]
177+
res.extend(class_name.get_actions(grf.STATION, 0xC400 + self.id, is_generic_offset=True))
178+
res.extend(translated_name.get_actions(grf.STATION, 0xC500 + self.id, is_generic_offset=True))
179+
180+
return res
181+
182+
@property
183+
def sprites(self):
184+
return unique(sub for l in self.layouts for sub in l.sprites)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import functools
2+
from agrf.global_cache import make_switch
3+
from agrf.lib.building.layout import ALayout
4+
5+
6+
def _lookup(thing, w, h, x, y, t):
7+
if isinstance(thing, (ALayout, int)):
8+
return thing
9+
return thing.lookup(w, h, x, y, t)
10+
11+
12+
def _find_default_element(d):
13+
reverse_lookup = {}
14+
value_count = {}
15+
16+
prev_k = prev_v = None
17+
for k, v in sorted(d.items()):
18+
if isinstance(v, int):
19+
key = v
20+
else:
21+
key = id(v)
22+
reverse_lookup[key] = v
23+
24+
if prev_k is None or k != prev_k + 1 or v != prev_v:
25+
value_count[key] = value_count.get(key, 0) + 1
26+
27+
prev_k = k
28+
prev_v = v
29+
30+
max_count = max(value_count.values())
31+
return [reverse_lookup.get(v, v) for v, c in value_count.items() if c == max_count][0]
32+
33+
34+
class StationTileSwitch:
35+
def __init__(self, var, ranges, cb24=False, comment=None):
36+
self.var = var
37+
self.ranges = {k: v for k, v in ranges.items() if v is not None}
38+
self.cb24 = cb24
39+
self.to_index_cache = {}
40+
self.comment = comment
41+
42+
@property
43+
def code(self):
44+
nibble = {"T": 24, "d": 12, "t": 8, "l": 4, "r": 0}[self.var]
45+
46+
if self.cb24:
47+
return (f"(extra_callback_info1 >> {nibble}) & 0xf",)
48+
else:
49+
return (f"var(0x41, shift={nibble}, and=0x0000000f)",)
50+
51+
def fmap(self, f, special_property=None):
52+
new_var = (
53+
{"T": {"t": "d", "d": "t"}, "R": {"l": "r", "r": "l"}}.get(special_property, {}).get(self.var, self.var)
54+
)
55+
return StationTileSwitch(new_var, {k: f(v) for k, v in self.ranges.items()}, cb24=self.cb24)
56+
57+
@property
58+
@functools.cache
59+
def T(self):
60+
return self.fmap(lambda x: x.T, special_property="T")
61+
62+
@property
63+
@functools.cache
64+
def R(self):
65+
return self.fmap(lambda x: x.R, special_property="R")
66+
67+
def to_index(self, sprite_list=None):
68+
if id(sprite_list) in self.to_index_cache:
69+
return self.to_index_cache[id(sprite_list)]
70+
71+
f = lambda v: (
72+
v
73+
if isinstance(v, int) or (sprite_list is None and not isinstance(v, StationTileSwitch))
74+
else v.to_index(sprite_list)
75+
)
76+
ranges = {k: f(v) for k, v in self.ranges.items()}
77+
default = _find_default_element(ranges)
78+
79+
new_ranges = {}
80+
l = None
81+
for k, v in sorted(ranges.items()):
82+
if v is default:
83+
continue
84+
if l is not None and k == h + 1 and r is v:
85+
h += 1
86+
else:
87+
if l is not None:
88+
new_ranges[(l, h)] = r
89+
l, h, r = k, k, v
90+
if l is not None:
91+
new_ranges[(l, h)] = r
92+
93+
if len(new_ranges) == 0:
94+
ret = default
95+
else:
96+
ret = make_switch(ranges=new_ranges, default=default, code=self.code)
97+
self.to_index_cache[id(sprite_list)] = ret
98+
return ret
99+
100+
def __repr__(self, context=None):
101+
if context is None:
102+
context = {}
103+
104+
rev_lookup = {}
105+
for k, v in self.ranges.items():
106+
i = id(v)
107+
if i not in rev_lookup:
108+
rev_lookup[i] = []
109+
rev_lookup[i].append(k)
110+
111+
rangedesc = []
112+
for i, ks in rev_lookup.items():
113+
if isinstance(self.ranges[ks[0]], StationTileSwitch):
114+
v = self.ranges[ks[0]].__repr__(context)
115+
elif i in context:
116+
v = context[i]
117+
else:
118+
v = context[i] = f"X{len(context)}"
119+
120+
kdesc = []
121+
start = None
122+
prev = None
123+
for k in sorted(ks):
124+
if start is not None and k == prev + 1:
125+
prev += 1
126+
elif start is not None:
127+
if start == prev:
128+
kdesc.append(str(start))
129+
else:
130+
kdesc.append(f"{start}-{prev}")
131+
start = k
132+
prev = k
133+
else:
134+
start = k
135+
prev = k
136+
if start == prev:
137+
kdesc.append(str(start))
138+
else:
139+
kdesc.append(f"{start}-{prev}")
140+
141+
rangedesc.append(self.var + "=" + ",".join(kdesc) + ":" + v)
142+
rangedesc = ", ".join(str(x) for x in rangedesc)
143+
144+
return f"<Switch:{{{rangedesc}}}:{self.cb24}:{self.comment}>"
145+
146+
def lookup(self, w, h, x, y, t=0):
147+
if self.var == "T":
148+
return _lookup(self.ranges[t], w, h, x, y, t)
149+
elif self.var == "l":
150+
return _lookup(self.ranges[min(x, 15)], w, h, x, y, t)
151+
elif self.var == "r":
152+
return _lookup(self.ranges[min(w - x - 1, 15)], w, h, x, y, t)
153+
elif self.var == "t":
154+
return _lookup(self.ranges[min(y, 15)], w, h, x, y, t)
155+
elif self.var == "d":
156+
return _lookup(self.ranges[min(h - y - 1, 15)], w, h, x, y, t)
157+
else:
158+
raise NotImplementedError()
159+
160+
def demo(self, w, h, preswitch=None):
161+
return [
162+
[self.lookup(w, h, x, y, preswitch and preswitch.lookup(w, h, x, y)) for x in range(w)] for y in range(h)
163+
]
164+
165+
166+
def make_horizontal_switch(f):
167+
return StationTileSwitch("l", {l: StationTileSwitch("r", {r: f(l, r) for r in range(16)}) for l in range(16)})
168+
169+
170+
def make_vertical_switch(f, cb24=False):
171+
return StationTileSwitch(
172+
"t", {t: StationTileSwitch("d", {d: f(t, d) for d in range(16)}, cb24) for t in range(16)}, cb24
173+
)

0 commit comments

Comments
 (0)