1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
from typing import TYPE_CHECKING, Iterator
from BaseClasses import MultiWorld, CollectionState, ItemClassification, Region, Entrance
from NetUtils import NetworkItem
from . import Lingo2World, Lingo2Item
from .regions import connect_ports_from_ut
from .options import Lingo2Options, ShuffleLetters
if TYPE_CHECKING:
from .context import Lingo2Manager
PLAYER_NUM = 1
class Tracker:
manager: "Lingo2Manager"
multiworld: MultiWorld
world: Lingo2World
collected_items: dict[int, int]
checked_locations: set[int]
accessible_locations: set[int]
accessible_worldports: set[int]
goal_accessible: bool
state: CollectionState
def __init__(self, manager: "Lingo2Manager"):
self.manager = manager
self.collected_items = {}
self.checked_locations = set()
self.accessible_locations = set()
self.accessible_worldports = set()
self.goal_accessible = False
def setup_slot(self, slot_data):
Lingo2World.for_tracker = True
self.multiworld = MultiWorld(players=PLAYER_NUM)
self.world = Lingo2World(self.multiworld, PLAYER_NUM)
self.multiworld.worlds[1] = self.world
self.world.options = Lingo2Options(**{k: t(slot_data.get(k, t.default))
for k, t in Lingo2Options.type_hints.items()})
self.world.generate_early()
self.world.create_regions()
if self.world.options.shuffle_worldports:
port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()}
connect_ports_from_ut(port_pairings, self.world)
self.refresh_state()
def set_checked_locations(self, checked_locations: set[int]):
self.checked_locations = checked_locations.copy()
def set_collected_items(self, network_items: list[NetworkItem]):
self.collected_items = {}
for item in network_items:
self.collected_items[item.item] = self.collected_items.get(item.item, 0) + 1
self.refresh_state()
def refresh_state(self):
self.state = CollectionState(self.multiworld)
for item_id, item_amount in self.collected_items.items():
for i in range(item_amount):
self.state.collect(Lingo2Item(Lingo2World.static_logic.item_id_to_name.get(item_id),
ItemClassification.progression, item_id, PLAYER_NUM), prevent_sweep=True)
for k, v in self.manager.keyboard.items():
# Unless all level 1 letters are pre-unlocked, H1 I1 N1 and T1 act differently between the generator and
# game. The generator considers them to be unlocked, which means they are not included in logic
# requirements, and only one item/event is needed to unlock their level 2 forms. The game considers them to
# be vanilla, which means you still have to pick them up in the Starting Room in order for them to appear on
# your keyboard. This also means that whether or not you have the level 1 forms should be synced to the
# multiworld. The tracker specifically should collect one fewer item for these letters in this scenario.
tv = v
if k in "hint" and self.world.options.shuffle_letters in [ShuffleLetters.option_vanilla,
ShuffleLetters.option_progressive]:
tv = max(0, v - 1)
if tv > 0:
for i in range(tv):
self.state.collect(Lingo2Item(k.upper(), ItemClassification.progression, None, PLAYER_NUM),
prevent_sweep=True)
for port_id in self.manager.worldports:
self.state.collect(Lingo2Item(f"Worldport {port_id} Entered", ItemClassification.progression, None,
PLAYER_NUM), prevent_sweep=True)
self.state.sweep_for_advancements()
self.accessible_locations = set()
self.accessible_worldports = set()
self.goal_accessible = False
for region in self.state.reachable_regions[PLAYER_NUM]:
for location in region.locations:
if location.access_rule(self.state):
if location.address is not None:
if location.address not in self.checked_locations:
self.accessible_locations.add(location.address)
elif hasattr(location, "port_id"):
if location.port_id not in self.manager.worldports:
self.accessible_worldports.add(location.port_id)
elif hasattr(location, "goal") and location.goal:
if not self.manager.goaled:
self.goal_accessible = True
def get_path_to_location(self, location_id: int) -> list[str] | None:
location_name = self.world.location_id_to_name.get(location_id)
location = self.multiworld.get_location(location_name, PLAYER_NUM)
return self.get_logical_path(location.parent_region)
def get_path_to_port(self, port_id: int) -> list[str] | None:
port = self.world.static_logic.objects.ports[port_id]
region_name = self.world.static_logic.get_room_region_name(port.room_id)
region = self.multiworld.get_region(region_name, PLAYER_NUM)
return self.get_logical_path(region)
def get_path_to_goal(self):
room_id = self.world.player_logic.goal_room_id
region_name = self.world.static_logic.get_room_region_name(room_id)
region = self.multiworld.get_region(region_name, PLAYER_NUM)
return self.get_logical_path(region)
def get_logical_path(self, region: Region) -> list[str] | None:
if region not in self.state.path:
return None
def flist_to_iter(path_value) -> Iterator[str]:
while path_value:
region_or_entrance, path_value = path_value
yield region_or_entrance
reversed_path = self.state.path.get(region)
flat_path = reversed(list(map(str, flist_to_iter(reversed_path))))
return list(flat_path)[1::2]
|