summary refs log tree commit diff stats
path: root/static_logic.py
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-03-15 04:26:00 -0400
committerGitHub <noreply@github.com>2024-03-15 09:26:00 +0100
commite016228d2b76fa367889c9d98d6eb9e783f98cb4 (patch)
tree5c4fd5427ee1c89a832b3327eef6d39d4ce82d82 /static_logic.py
parentfcb3b36f44a7d475aa49c5c44971a2b7cabc4ca8 (diff)
downloadlingo-apworld-e016228d2b76fa367889c9d98d6eb9e783f98cb4.tar.gz
lingo-apworld-e016228d2b76fa367889c9d98d6eb9e783f98cb4.tar.bz2
lingo-apworld-e016228d2b76fa367889c9d98d6eb9e783f98cb4.zip
Lingo: Pre-compile datafile to improve loading time (#2829)
Diffstat (limited to 'static_logic.py')
-rw-r--r--static_logic.py530
1 files changed, 41 insertions, 489 deletions
diff --git a/static_logic.py b/static_logic.py index e9f82fb..1da265d 100644 --- a/static_logic.py +++ b/static_logic.py
@@ -1,86 +1,16 @@
1from typing import Dict, List, NamedTuple, Optional, Set 1import os
2import pkgutil
3from io import BytesIO
4from typing import Dict, List, Set
2 5
3import Utils 6import pickle
4 7
5 8from .datatypes import Door, Painting, Panel, Progression, Room
6class RoomAndDoor(NamedTuple):
7 room: Optional[str]
8 door: str
9
10
11class RoomAndPanel(NamedTuple):
12 room: Optional[str]
13 panel: str
14
15
16class RoomEntrance(NamedTuple):
17 room: str # source room
18 door: Optional[RoomAndDoor]
19 painting: bool
20
21
22class Room(NamedTuple):
23 name: str
24 entrances: List[RoomEntrance]
25
26
27class Door(NamedTuple):
28 name: str
29 item_name: str
30 location_name: Optional[str]
31 panels: Optional[List[RoomAndPanel]]
32 skip_location: bool
33 skip_item: bool
34 door_ids: List[str]
35 painting_ids: List[str]
36 event: bool
37 group: Optional[str]
38 include_reduce: bool
39 junk_item: bool
40
41
42class Panel(NamedTuple):
43 required_rooms: List[str]
44 required_doors: List[RoomAndDoor]
45 required_panels: List[RoomAndPanel]
46 colors: List[str]
47 check: bool
48 event: bool
49 internal_ids: List[str]
50 exclude_reduce: bool
51 achievement: bool
52 non_counting: bool
53
54
55class Painting(NamedTuple):
56 id: str
57 room: str
58 enter_only: bool
59 exit_only: bool
60 orientation: str
61 required: bool
62 required_when_no_doors: bool
63 required_door: Optional[RoomAndDoor]
64 disable: bool
65 move: bool
66 req_blocked: bool
67 req_blocked_when_no_doors: bool
68
69
70class Progression(NamedTuple):
71 item_name: str
72 index: int
73
74
75ROOMS: Dict[str, Room] = {}
76PANELS: Dict[str, Panel] = {}
77DOORS: Dict[str, Door] = {}
78PAINTINGS: Dict[str, Painting] = {}
79 9
80ALL_ROOMS: List[Room] = [] 10ALL_ROOMS: List[Room] = []
81DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {} 11DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
82PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {} 12PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
83PAINTINGS_BY_ROOM: Dict[str, List[Painting]] = {} 13PAINTINGS: Dict[str, Painting] = {}
84 14
85PROGRESSIVE_ITEMS: List[str] = [] 15PROGRESSIVE_ITEMS: List[str] = []
86PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {} 16PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
@@ -98,61 +28,7 @@ DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
98DOOR_GROUP_ITEM_IDS: Dict[str, int] = {} 28DOOR_GROUP_ITEM_IDS: Dict[str, int] = {}
99PROGRESSIVE_ITEM_IDS: Dict[str, int] = {} 29PROGRESSIVE_ITEM_IDS: Dict[str, int] = {}
100 30
101 31HASHES: Dict[str, str] = {}
102def load_static_data():
103 global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \
104 DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS
105
106 try:
107 from importlib.resources import files
108 except ImportError:
109 from importlib_resources import files
110
111 from . import data
112
113 # Load in all item and location IDs. These are broken up into groups based on the type of item/location.
114 with files(data).joinpath("ids.yaml").open() as file:
115 config = Utils.parse_yaml(file)
116
117 if "special_items" in config:
118 for item_name, item_id in config["special_items"].items():
119 SPECIAL_ITEM_IDS[item_name] = item_id
120
121 if "panels" in config:
122 for room_name in config["panels"].keys():
123 PANEL_LOCATION_IDS[room_name] = {}
124
125 for panel_name, location_id in config["panels"][room_name].items():
126 PANEL_LOCATION_IDS[room_name][panel_name] = location_id
127
128 if "doors" in config:
129 for room_name in config["doors"].keys():
130 DOOR_LOCATION_IDS[room_name] = {}
131 DOOR_ITEM_IDS[room_name] = {}
132
133 for door_name, door_data in config["doors"][room_name].items():
134 if "location" in door_data:
135 DOOR_LOCATION_IDS[room_name][door_name] = door_data["location"]
136
137 if "item" in door_data:
138 DOOR_ITEM_IDS[room_name][door_name] = door_data["item"]
139
140 if "door_groups" in config:
141 for item_name, item_id in config["door_groups"].items():
142 DOOR_GROUP_ITEM_IDS[item_name] = item_id
143
144 if "progression" in config:
145 for item_name, item_id in config["progression"].items():
146 PROGRESSIVE_ITEM_IDS[item_name] = item_id
147
148 # Process the main world file.
149 with files(data).joinpath("LL1.yaml").open() as file:
150 config = Utils.parse_yaml(file)
151
152 for room_name, room_data in config.items():
153 process_room(room_name, room_data)
154
155 PAINTING_EXITS = len(PAINTING_EXIT_ROOMS)
156 32
157 33
158def get_special_item_id(name: str): 34def get_special_item_id(name: str):
@@ -197,363 +73,39 @@ def get_progressive_item_id(name: str):
197 return PROGRESSIVE_ITEM_IDS[name] 73 return PROGRESSIVE_ITEM_IDS[name]
198 74
199 75
200def process_entrance(source_room, doors, room_obj): 76def load_static_data_from_file():
201 global PAINTING_ENTRANCES, PAINTING_EXIT_ROOMS 77 global PAINTING_ENTRANCES, PAINTING_EXITS
202 78
203 # If the value of an entrance is just True, that means that the entrance is always accessible. 79 class RenameUnpickler(pickle.Unpickler):
204 if doors is True: 80 def find_class(self, module, name):
205 room_obj.entrances.append(RoomEntrance(source_room, None, False)) 81 renamed_module = module
206 elif isinstance(doors, dict): 82 if module == "datatypes":
207 # If the value of an entrance is a dictionary, that means the entrance requires a door to be accessible, is a 83 renamed_module = "worlds.lingo.datatypes"
208 # painting-based entrance, or both. 84
209 if "painting" in doors and "door" not in doors: 85 return super(RenameUnpickler, self).find_class(renamed_module, name)
210 PAINTING_EXIT_ROOMS.add(room_obj.name) 86
211 PAINTING_ENTRANCES += 1 87 file = pkgutil.get_data(__name__, os.path.join("data", "generated.dat"))
212 88 pickdata = RenameUnpickler(BytesIO(file)).load()
213 room_obj.entrances.append(RoomEntrance(source_room, None, True)) 89
214 else: 90 HASHES.update(pickdata["HASHES"])
215 if "painting" in doors and doors["painting"]: 91 PAINTINGS.update(pickdata["PAINTINGS"])
216 PAINTING_EXIT_ROOMS.add(room_obj.name) 92 ALL_ROOMS.extend(pickdata["ALL_ROOMS"])
217 PAINTING_ENTRANCES += 1 93 DOORS_BY_ROOM.update(pickdata["DOORS_BY_ROOM"])
218 94 PANELS_BY_ROOM.update(pickdata["PANELS_BY_ROOM"])
219 room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( 95 PROGRESSIVE_ITEMS.extend(pickdata["PROGRESSIVE_ITEMS"])
220 doors["room"] if "room" in doors else None, 96 PROGRESSION_BY_ROOM.update(pickdata["PROGRESSION_BY_ROOM"])
221 doors["door"] 97 PAINTING_ENTRANCES = pickdata["PAINTING_ENTRANCES"]
222 ), doors["painting"] if "painting" in doors else False)) 98 PAINTING_EXIT_ROOMS.update(pickdata["PAINTING_EXIT_ROOMS"])
223 else: 99 PAINTING_EXITS = pickdata["PAINTING_EXITS"]
224 # If the value of an entrance is a list, then there are multiple possible doors that can give access to the 100 REQUIRED_PAINTING_ROOMS.extend(pickdata["REQUIRED_PAINTING_ROOMS"])
225 # entrance. 101 REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.extend(pickdata["REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS"])
226 for door in doors: 102 SPECIAL_ITEM_IDS.update(pickdata["SPECIAL_ITEM_IDS"])
227 if "painting" in door and door["painting"]: 103 PANEL_LOCATION_IDS.update(pickdata["PANEL_LOCATION_IDS"])
228 PAINTING_EXIT_ROOMS.add(room_obj.name) 104 DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"])
229 PAINTING_ENTRANCES += 1 105 DOOR_ITEM_IDS.update(pickdata["DOOR_ITEM_IDS"])
230 106 DOOR_GROUP_ITEM_IDS.update(pickdata["DOOR_GROUP_ITEM_IDS"])
231 room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( 107 PROGRESSIVE_ITEM_IDS.update(pickdata["PROGRESSIVE_ITEM_IDS"])
232 door["room"] if "room" in door else None,
233 door["door"]
234 ), door["painting"] if "painting" in door else False))
235
236
237def process_panel(room_name, panel_name, panel_data):
238 global PANELS, PANELS_BY_ROOM
239
240 full_name = f"{room_name} - {panel_name}"
241
242 # required_room can either be a single room or a list of rooms.
243 if "required_room" in panel_data:
244 if isinstance(panel_data["required_room"], list):
245 required_rooms = panel_data["required_room"]
246 else:
247 required_rooms = [panel_data["required_room"]]
248 else:
249 required_rooms = []
250
251 # required_door can either be a single door or a list of doors. For convenience, the room key for each door does not
252 # need to be specified if the door is in this room.
253 required_doors = list()
254 if "required_door" in panel_data:
255 if isinstance(panel_data["required_door"], dict):
256 door = panel_data["required_door"]
257 required_doors.append(RoomAndDoor(
258 door["room"] if "room" in door else None,
259 door["door"]
260 ))
261 else:
262 for door in panel_data["required_door"]:
263 required_doors.append(RoomAndDoor(
264 door["room"] if "room" in door else None,
265 door["door"]
266 ))
267
268 # required_panel can either be a single panel or a list of panels. For convenience, the room key for each panel does
269 # not need to be specified if the panel is in this room.
270 required_panels = list()
271 if "required_panel" in panel_data:
272 if isinstance(panel_data["required_panel"], dict):
273 other_panel = panel_data["required_panel"]
274 required_panels.append(RoomAndPanel(
275 other_panel["room"] if "room" in other_panel else None,
276 other_panel["panel"]
277 ))
278 else:
279 for other_panel in panel_data["required_panel"]:
280 required_panels.append(RoomAndPanel(
281 other_panel["room"] if "room" in other_panel else None,
282 other_panel["panel"]
283 ))
284
285 # colors can either be a single color or a list of colors.
286 if "colors" in panel_data:
287 if isinstance(panel_data["colors"], list):
288 colors = panel_data["colors"]
289 else:
290 colors = [panel_data["colors"]]
291 else:
292 colors = []
293
294 if "check" in panel_data:
295 check = panel_data["check"]
296 else:
297 check = False
298
299 if "event" in panel_data:
300 event = panel_data["event"]
301 else:
302 event = False
303
304 if "achievement" in panel_data:
305 achievement = True
306 else:
307 achievement = False
308
309 if "exclude_reduce" in panel_data:
310 exclude_reduce = panel_data["exclude_reduce"]
311 else:
312 exclude_reduce = False
313
314 if "non_counting" in panel_data:
315 non_counting = panel_data["non_counting"]
316 else:
317 non_counting = False
318
319 if "id" in panel_data:
320 if isinstance(panel_data["id"], list):
321 internal_ids = panel_data["id"]
322 else:
323 internal_ids = [panel_data["id"]]
324 else:
325 internal_ids = []
326
327 panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, internal_ids,
328 exclude_reduce, achievement, non_counting)
329 PANELS[full_name] = panel_obj
330 PANELS_BY_ROOM[room_name][panel_name] = panel_obj
331
332
333def process_door(room_name, door_name, door_data):
334 global DOORS, DOORS_BY_ROOM
335
336 # The item name associated with a door can be explicitly specified in the configuration. If it is not, it is
337 # generated from the room and door name.
338 if "item_name" in door_data:
339 item_name = door_data["item_name"]
340 else:
341 item_name = f"{room_name} - {door_name}"
342
343 if "skip_location" in door_data:
344 skip_location = door_data["skip_location"]
345 else:
346 skip_location = False
347
348 if "skip_item" in door_data:
349 skip_item = door_data["skip_item"]
350 else:
351 skip_item = False
352
353 if "event" in door_data:
354 event = door_data["event"]
355 else:
356 event = False
357
358 if "include_reduce" in door_data:
359 include_reduce = door_data["include_reduce"]
360 else:
361 include_reduce = False
362
363 if "junk_item" in door_data:
364 junk_item = door_data["junk_item"]
365 else:
366 junk_item = False
367
368 if "group" in door_data:
369 group = door_data["group"]
370 else:
371 group = None
372
373 # panels is a list of panels. Each panel can either be a simple string (the name of a panel in the current room) or
374 # a dictionary specifying a panel in a different room.
375 if "panels" in door_data:
376 panels = list()
377 for panel in door_data["panels"]:
378 if isinstance(panel, dict):
379 panels.append(RoomAndPanel(panel["room"], panel["panel"]))
380 else:
381 panels.append(RoomAndPanel(None, panel))
382 else:
383 skip_location = True
384 panels = None
385
386 # The location name associated with a door can be explicitly specified in the configuration. If it is not, then the
387 # name is generated using a combination of all of the panels that would ordinarily open the door. This can get quite
388 # messy if there are a lot of panels, especially if panels from multiple rooms are involved, so in these cases it
389 # would be better to specify a name.
390 if "location_name" in door_data:
391 location_name = door_data["location_name"]
392 elif skip_location is False:
393 panel_per_room = dict()
394 for panel in panels:
395 panel_room_name = room_name if panel.room is None else panel.room
396 panel_per_room.setdefault(panel_room_name, []).append(panel.panel)
397
398 room_strs = list()
399 for door_room_str, door_panels_str in panel_per_room.items():
400 room_strs.append(door_room_str + " - " + ", ".join(door_panels_str))
401
402 location_name = " and ".join(room_strs)
403 else:
404 location_name = None
405
406 # The id field can be a single item, or a list of door IDs, in the event that the item for this logical door should
407 # open more than one actual in-game door.
408 if "id" in door_data:
409 if isinstance(door_data["id"], list):
410 door_ids = door_data["id"]
411 else:
412 door_ids = [door_data["id"]]
413 else:
414 door_ids = []
415
416 # The painting_id field can be a single item, or a list of painting IDs, in the event that the item for this logical
417 # door should move more than one actual in-game painting.
418 if "painting_id" in door_data:
419 if isinstance(door_data["painting_id"], list):
420 painting_ids = door_data["painting_id"]
421 else:
422 painting_ids = [door_data["painting_id"]]
423 else:
424 painting_ids = []
425
426 door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, door_ids,
427 painting_ids, event, group, include_reduce, junk_item)
428
429 DOORS[door_obj.item_name] = door_obj
430 DOORS_BY_ROOM[room_name][door_name] = door_obj
431
432
433def process_painting(room_name, painting_data):
434 global PAINTINGS, PAINTINGS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
435
436 # Read in information about this painting and store it in an object.
437 painting_id = painting_data["id"]
438
439 if "orientation" in painting_data:
440 orientation = painting_data["orientation"]
441 else:
442 orientation = ""
443
444 if "disable" in painting_data:
445 disable_painting = painting_data["disable"]
446 else:
447 disable_painting = False
448
449 if "required" in painting_data:
450 required_painting = painting_data["required"]
451 if required_painting:
452 REQUIRED_PAINTING_ROOMS.append(room_name)
453 else:
454 required_painting = False
455
456 if "move" in painting_data:
457 move_painting = painting_data["move"]
458 else:
459 move_painting = False
460
461 if "required_when_no_doors" in painting_data:
462 rwnd = painting_data["required_when_no_doors"]
463 if rwnd:
464 REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.append(room_name)
465 else:
466 rwnd = False
467
468 if "exit_only" in painting_data:
469 exit_only = painting_data["exit_only"]
470 else:
471 exit_only = False
472
473 if "enter_only" in painting_data:
474 enter_only = painting_data["enter_only"]
475 else:
476 enter_only = False
477
478 if "req_blocked" in painting_data:
479 req_blocked = painting_data["req_blocked"]
480 else:
481 req_blocked = False
482
483 if "req_blocked_when_no_doors" in painting_data:
484 req_blocked_when_no_doors = painting_data["req_blocked_when_no_doors"]
485 else:
486 req_blocked_when_no_doors = False
487
488 required_door = None
489 if "required_door" in painting_data:
490 door = painting_data["required_door"]
491 required_door = RoomAndDoor(
492 door["room"] if "room" in door else room_name,
493 door["door"]
494 )
495
496 painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation,
497 required_painting, rwnd, required_door, disable_painting, move_painting, req_blocked,
498 req_blocked_when_no_doors)
499 PAINTINGS[painting_id] = painting_obj
500 PAINTINGS_BY_ROOM[room_name].append(painting_obj)
501
502
503def process_progression(room_name, progression_name, progression_doors):
504 global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM
505
506 # Progressive items are configured as a list of doors.
507 PROGRESSIVE_ITEMS.append(progression_name)
508
509 progression_index = 1
510 for door in progression_doors:
511 if isinstance(door, Dict):
512 door_room = door["room"]
513 door_door = door["door"]
514 else:
515 door_room = room_name
516 door_door = door
517
518 room_progressions = PROGRESSION_BY_ROOM.setdefault(door_room, {})
519 room_progressions[door_door] = Progression(progression_name, progression_index)
520 progression_index += 1
521
522
523def process_room(room_name, room_data):
524 global ROOMS, ALL_ROOMS
525
526 room_obj = Room(room_name, [])
527
528 if "entrances" in room_data:
529 for source_room, doors in room_data["entrances"].items():
530 process_entrance(source_room, doors, room_obj)
531
532 if "panels" in room_data:
533 PANELS_BY_ROOM[room_name] = dict()
534
535 for panel_name, panel_data in room_data["panels"].items():
536 process_panel(room_name, panel_name, panel_data)
537
538 if "doors" in room_data:
539 DOORS_BY_ROOM[room_name] = dict()
540
541 for door_name, door_data in room_data["doors"].items():
542 process_door(room_name, door_name, door_data)
543
544 if "paintings" in room_data:
545 PAINTINGS_BY_ROOM[room_name] = []
546
547 for painting_data in room_data["paintings"]:
548 process_painting(room_name, painting_data)
549
550 if "progression" in room_data:
551 for progression_name, progression_doors in room_data["progression"].items():
552 process_progression(room_name, progression_name, progression_doors)
553
554 ROOMS[room_name] = room_obj
555 ALL_ROOMS.append(room_obj)
556 108
557 109
558# Initialize the static data at module scope. 110# Initialize the static data at module scope.
559load_static_data() 111load_static_data_from_file()