diff options
Diffstat (limited to 'apworld/tracker.py')
-rw-r--r-- | apworld/tracker.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/apworld/tracker.py b/apworld/tracker.py new file mode 100644 index 0000000..cf2dbe1 --- /dev/null +++ b/apworld/tracker.py | |||
@@ -0,0 +1,106 @@ | |||
1 | from typing import TYPE_CHECKING | ||
2 | |||
3 | from BaseClasses import MultiWorld, CollectionState, ItemClassification | ||
4 | from NetUtils import NetworkItem | ||
5 | from . import Lingo2World, Lingo2Item | ||
6 | from .regions import connect_ports_from_ut | ||
7 | from .options import Lingo2Options, ShuffleLetters | ||
8 | |||
9 | if TYPE_CHECKING: | ||
10 | from .context import Lingo2Manager | ||
11 | |||
12 | PLAYER_NUM = 1 | ||
13 | |||
14 | |||
15 | class Tracker: | ||
16 | manager: "Lingo2Manager" | ||
17 | |||
18 | multiworld: MultiWorld | ||
19 | world: Lingo2World | ||
20 | |||
21 | collected_items: dict[int, int] | ||
22 | checked_locations: set[int] | ||
23 | accessible_locations: set[int] | ||
24 | accessible_worldports: set[int] | ||
25 | |||
26 | state: CollectionState | ||
27 | |||
28 | def __init__(self, manager: "Lingo2Manager"): | ||
29 | self.manager = manager | ||
30 | self.collected_items = {} | ||
31 | self.checked_locations = set() | ||
32 | self.accessible_locations = set() | ||
33 | self.accessible_worldports = set() | ||
34 | |||
35 | def setup_slot(self, slot_data): | ||
36 | Lingo2World.for_tracker = True | ||
37 | |||
38 | self.multiworld = MultiWorld(players=PLAYER_NUM) | ||
39 | self.world = Lingo2World(self.multiworld, PLAYER_NUM) | ||
40 | self.multiworld.worlds[1] = self.world | ||
41 | self.world.options = Lingo2Options(**{k: t(slot_data.get(k, t.default)) | ||
42 | for k, t in Lingo2Options.type_hints.items()}) | ||
43 | |||
44 | self.world.generate_early() | ||
45 | self.world.create_regions() | ||
46 | |||
47 | if self.world.options.shuffle_worldports: | ||
48 | port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()} | ||
49 | connect_ports_from_ut(port_pairings, self.world) | ||
50 | |||
51 | self.refresh_state() | ||
52 | |||
53 | def set_checked_locations(self, checked_locations: set[int]): | ||
54 | self.checked_locations = checked_locations.copy() | ||
55 | |||
56 | def set_collected_items(self, network_items: list[NetworkItem]): | ||
57 | self.collected_items = {} | ||
58 | |||
59 | for item in network_items: | ||
60 | self.collected_items[item.item] = self.collected_items.get(item.item, 0) + 1 | ||
61 | |||
62 | self.refresh_state() | ||
63 | |||
64 | def refresh_state(self): | ||
65 | self.state = CollectionState(self.multiworld) | ||
66 | |||
67 | for item_id, item_amount in self.collected_items.items(): | ||
68 | for i in range(item_amount): | ||
69 | self.state.collect(Lingo2Item(Lingo2World.static_logic.item_id_to_name.get(item_id), | ||
70 | ItemClassification.progression, item_id, PLAYER_NUM), prevent_sweep=True) | ||
71 | |||
72 | for k, v in self.manager.keyboard.items(): | ||
73 | # Unless all level 1 letters are pre-unlocked, H1 I1 N1 and T1 act differently between the generator and | ||
74 | # game. The generator considers them to be unlocked, which means they are not included in logic | ||
75 | # requirements, and only one item/event is needed to unlock their level 2 forms. The game considers them to | ||
76 | # be vanilla, which means you still have to pick them up in the Starting Room in order for them to appear on | ||
77 | # your keyboard. This also means that whether or not you have the level 1 forms should be synced to the | ||
78 | # multiworld. The tracker specifically should collect one fewer item for these letters in this scenario. | ||
79 | tv = v | ||
80 | if k in "hint" and self.world.options.shuffle_letters in [ShuffleLetters.option_vanilla, | ||
81 | ShuffleLetters.option_progressive]: | ||
82 | tv = max(0, v - 1) | ||
83 | |||
84 | if tv > 0: | ||
85 | for i in range(tv): | ||
86 | self.state.collect(Lingo2Item(k.upper(), ItemClassification.progression, None, PLAYER_NUM), | ||
87 | prevent_sweep=True) | ||
88 | |||
89 | for port_id in self.manager.worldports: | ||
90 | self.state.collect(Lingo2Item(f"Worldport {port_id} Entered", ItemClassification.progression, None, | ||
91 | PLAYER_NUM), prevent_sweep=True) | ||
92 | |||
93 | self.state.sweep_for_advancements() | ||
94 | |||
95 | self.accessible_locations = set() | ||
96 | self.accessible_worldports = set() | ||
97 | |||
98 | for region in self.state.reachable_regions[PLAYER_NUM]: | ||
99 | for location in region.locations: | ||
100 | if location.access_rule(self.state): | ||
101 | if location.address is not None: | ||
102 | if location.address not in self.checked_locations: | ||
103 | self.accessible_locations.add(location.address) | ||
104 | elif hasattr(location, "port_id"): | ||
105 | if location.port_id not in self.manager.worldports: | ||
106 | self.accessible_worldports.add(location.port_id) | ||