about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--apworld/client/client.gd25
-rw-r--r--apworld/client/gamedata.gd120
-rw-r--r--apworld/client/manager.gd14
-rw-r--r--apworld/client/textclient.gd86
-rw-r--r--apworld/context.py42
-rw-r--r--apworld/tracker.py67
6 files changed, 321 insertions, 33 deletions
diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 67edf29..05b2b6c 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd
@@ -21,6 +21,7 @@ var _checked_locations = []
21var _received_indexes = [] 21var _received_indexes = []
22var _received_items = {} 22var _received_items = {}
23var _slot_data = {} 23var _slot_data = {}
24var _accessible_locations = []
24 25
25signal could_not_connect 26signal could_not_connect
26signal connect_status 27signal connect_status
@@ -30,6 +31,8 @@ signal location_scout_received(location_id, item_name, player_name, flags, for_s
30signal text_message_received(message) 31signal text_message_received(message)
31signal item_sent_notification(message) 32signal item_sent_notification(message)
32signal hint_received(message) 33signal hint_received(message)
34signal accessible_locations_updated
35signal checked_locations_updated
33 36
34 37
35func _init(): 38func _init():
@@ -51,6 +54,7 @@ func _reset_state():
51 _should_process = false 54 _should_process = false
52 _received_items = {} 55 _received_items = {}
53 _received_indexes = [] 56 _received_indexes = []
57 _accessible_locations = []
54 58
55 59
56func disconnect_from_ap(): 60func disconnect_from_ap():
@@ -92,15 +96,26 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
92 _gen_version = message["generator_version"] 96 _gen_version = message["generator_version"]
93 _team = message["team"] 97 _team = message["team"]
94 _slot = message["slot"] 98 _slot = message["slot"]
95 _checked_locations = message["checked_locations"]
96 _slot_data = message["slot_data"] 99 _slot_data = message["slot_data"]
97 100
101 _checked_locations = []
102 for location in message["checked_locations"]:
103 _checked_locations.append(int(message["checked_locations"]))
104
98 client_connected.emit(_slot_data) 105 client_connected.emit(_slot_data)
99 106
100 elif cmd == "ConnectionRefused": 107 elif cmd == "ConnectionRefused":
101 could_not_connect.emit(message["text"]) 108 could_not_connect.emit(message["text"])
102 global._print("Connection to AP refused") 109 global._print("Connection to AP refused")
103 110
111 elif cmd == "UpdateLocations":
112 for location in message["locations"]:
113 var lint = int(location)
114 if not _checked_locations.has(lint):
115 _checked_locations.append(lint)
116
117 checked_locations_updated.emit()
118
104 elif cmd == "ItemReceived": 119 elif cmd == "ItemReceived":
105 for item in message["items"]: 120 for item in message["items"]:
106 var index = int(item["index"]) 121 var index = int(item["index"])
@@ -134,6 +149,14 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
134 int(loc["for_self"]) 149 int(loc["for_self"])
135 ) 150 )
136 151
152 elif cmd == "AccessibleLocations":
153 _accessible_locations.clear()
154
155 for loc in message["locations"]:
156 _accessible_locations.append(int(loc))
157
158 accessible_locations_updated.emit()
159
137 160
138func connectToServer(server, un, pw): 161func connectToServer(server, un, pw):
139 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) 162 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}])
diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 9eeec3b..39e0583 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd
@@ -13,6 +13,7 @@ var progressive_id_by_ap_id = {}
13var letter_id_by_ap_id = {} 13var letter_id_by_ap_id = {}
14var symbol_item_ids = [] 14var symbol_item_ids = []
15var anti_trap_ids = {} 15var anti_trap_ids = {}
16var location_name_by_id = {}
16 17
17var kSYMBOL_ITEMS 18var kSYMBOL_ITEMS
18 19
@@ -70,6 +71,7 @@ func load(data_bytes):
70 71
71 if door.has_ap_id(): 72 if door.has_ap_id():
72 door_id_by_ap_id[door.get_ap_id()] = door.get_id() 73 door_id_by_ap_id[door.get_ap_id()] = door.get_id()
74 location_name_by_id[door.get_ap_id()] = _get_door_location_name(door)
73 75
74 for painting in objects.get_paintings(): 76 for painting in objects.get_paintings():
75 var room = objects.get_rooms()[painting.get_room_id()] 77 var room = objects.get_rooms()[painting.get_room_id()]
@@ -95,6 +97,17 @@ func load(data_bytes):
95 97
96 for letter in objects.get_letters(): 98 for letter in objects.get_letters():
97 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() 99 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
100 location_name_by_id[letter.get_ap_id()] = _get_letter_location_name(letter)
101
102 for mastery in objects.get_masteries():
103 location_name_by_id[mastery.get_ap_id()] = _get_mastery_location_name(mastery)
104
105 for ending in objects.get_endings():
106 location_name_by_id[ending.get_ap_id()] = _get_ending_location_name(ending)
107
108 for keyholder in objects.get_keyholders():
109 if keyholder.has_key():
110 location_name_by_id[keyholder.get_ap_id()] = _get_keyholder_location_name(keyholder)
98 111
99 for panel in objects.get_panels(): 112 for panel in objects.get_panels():
100 var room = objects.get_rooms()[panel.get_room_id()] 113 var room = objects.get_rooms()[panel.get_room_id()]
@@ -153,7 +166,106 @@ func get_door_receivers(door_id):
153 return door.get_receivers() 166 return door.get_receivers()
154 167
155 168
156func get_door_map_name(door_id): 169func _get_map_object_map_name(obj):
157 var door = objects.get_doors()[door_id] 170 return objects.get_maps()[obj.get_map_id()].get_display_name()
158 var map = objects.get_maps()[door.get_map_id()] 171
159 return map.get_name() 172
173func _get_room_object_map_name(obj):
174 return _get_map_object_map_name(objects.get_rooms()[obj.get_room_id()])
175
176
177func _get_room_object_location_prefix(obj):
178 var room = objects.get_rooms()[obj.get_room_id()]
179 var game_map = objects.get_maps()[room.get_map_id()]
180
181 if room.has_panel_display_name():
182 return "%s (%s)" % [game_map.get_display_name(), room.get_panel_display_name()]
183 else:
184 return game_map.get_display_name()
185
186
187func _get_door_location_name(door):
188 var map_part = _get_room_object_location_prefix(door)
189
190 if door.has_location_name():
191 return "%s - %s" % [map_part, door.get_location_name()]
192
193 var generated_location_name = _get_generated_door_location_name(door)
194 if generated_location_name != null:
195 return generated_location_name
196
197 return "%s - %s" % [map_part, door.get_name()]
198
199
200func _get_generated_door_location_name(door):
201 if door.get_type() != SCRIPT_proto.DoorType.STANDARD:
202 return null
203
204 if door.get_keyholders().size() > 0 or door.get_endings().size() > 0 or door.has_complete_at():
205 return null
206
207 if door.get_panels().size() > 4:
208 return null
209
210 var map_areas = []
211 for panel_id in door.get_panels():
212 var panel = objects.get_panels()[panel_id.get_panel()]
213 var panel_room = objects.get_rooms()[panel.get_room_id()]
214 # It's okay if panel_display_name is not present because then it's coalesced with other unnamed areas.
215 if not map_areas.has(panel_room.get_panel_display_name()):
216 map_areas.append(panel_room.get_panel_display_name())
217
218 if map_areas.size() > 1:
219 return null
220
221 var game_map = objects.get_maps()[door.get_map_id()]
222 var map_area = map_areas[0]
223 var map_part
224 if map_area == "":
225 map_part = game_map.get_display_name()
226 else:
227 map_part = "%s (%s)" % [game_map.get_display_name(), map_area]
228
229 var panel_names = []
230 for panel_id in door.get_panels():
231 var panel_data = objects.get_panels()[panel_id.get_panel()]
232 var panel_name
233 if panel_data.has_display_name():
234 panel_name = panel_data.get_display_name()
235 else:
236 panel_name = panel_data.get_name()
237
238 var location_part
239 if panel_id.has_answer():
240 location_part = "%s/%s" % [panel_name, panel_id.get_answer().to_upper()]
241 else:
242 location_part = panel_name
243
244 panel_names.append(location_part)
245
246 panel_names.sort()
247
248 return map_part + " - " + ", ".join(panel_names)
249
250
251func _get_letter_location_name(letter):
252 var letter_level = 2 if letter.get_level2() else 1
253 var letter_name = "%s%d" % [letter.get_key().to_upper(), letter_level]
254 return "%s - %s" % [_get_room_object_map_name(letter), letter_name]
255
256
257func _get_mastery_location_name(mastery):
258 return "%s - Mastery" % _get_room_object_map_name(mastery)
259
260
261func _get_ending_location_name(ending):
262 return (
263 "%s - %s Ending" % [_get_room_object_map_name(ending), ending.get_name().to_pascal_case()]
264 )
265
266
267func _get_keyholder_location_name(keyholder):
268 return (
269 "%s - %s Keyholder"
270 % [_get_room_object_location_prefix(keyholder), keyholder.get_key().to_upper()]
271 )
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 955d470..7f4a8a6 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -99,6 +99,8 @@ func _ready():
99 client.text_message_received.connect(_process_text_message) 99 client.text_message_received.connect(_process_text_message)
100 client.item_sent_notification.connect(_process_item_sent_notification) 100 client.item_sent_notification.connect(_process_item_sent_notification)
101 client.hint_received.connect(_process_hint_received) 101 client.hint_received.connect(_process_hint_received)
102 client.accessible_locations_updated.connect(_on_accessible_locations_updated)
103 client.checked_locations_updated.connect(_on_checked_locations_updated)
102 104
103 client.could_not_connect.connect(_client_could_not_connect) 105 client.could_not_connect.connect(_client_could_not_connect)
104 client.connect_status.connect(_client_connect_status) 106 client.connect_status.connect(_client_connect_status)
@@ -302,6 +304,18 @@ func _process_location_scout(location_id, item_name, player_name, flags, for_sel
302 collectable.setScoutedText(item_name) 304 collectable.setScoutedText(item_name)
303 305
304 306
307func _on_accessible_locations_updated():
308 var textclient_node = global.get_node("Textclient")
309 if textclient_node != null:
310 textclient_node.update_locations()
311
312
313func _on_checked_locations_updated():
314 var textclient_node = global.get_node("Textclient")
315 if textclient_node != null:
316 textclient_node.update_locations()
317
318
305func _client_could_not_connect(message): 319func _client_could_not_connect(message):
306 could_not_connect.emit(message) 320 could_not_connect.emit(message)
307 321
diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd index 9841063..e345489 100644 --- a/apworld/client/textclient.gd +++ b/apworld/client/textclient.gd
@@ -1,8 +1,10 @@
1extends CanvasLayer 1extends CanvasLayer
2 2
3var tabs
3var panel 4var panel
4var label 5var label
5var entry 6var entry
7var tracker_label
6var is_open = false 8var is_open = false
7 9
8 10
@@ -10,26 +12,32 @@ func _ready():
10 process_mode = ProcessMode.PROCESS_MODE_ALWAYS 12 process_mode = ProcessMode.PROCESS_MODE_ALWAYS
11 layer = 2 13 layer = 2
12 14
13 panel = Panel.new() 15 tabs = TabContainer.new()
14 panel.set_name("Panel") 16 tabs.name = "Tabs"
15 panel.offset_left = 100 17 tabs.offset_left = 100
16 panel.offset_right = 1820 18 tabs.offset_right = 1820
17 panel.offset_top = 100 19 tabs.offset_top = 100
18 panel.offset_bottom = 980 20 tabs.offset_bottom = 980
19 panel.visible = false 21 tabs.visible = false
20 add_child(panel) 22 tabs.theme = preload("res://assets/themes/baseUI.tres")
23 tabs.add_theme_font_size_override("font_size", 36)
24 add_child(tabs)
25
26 panel = MarginContainer.new()
27 panel.name = "Text Client"
28 panel.add_theme_constant_override("margin_top", 60)
29 panel.add_theme_constant_override("margin_left", 60)
30 panel.add_theme_constant_override("margin_right", 60)
31 panel.add_theme_constant_override("margin_bottom", 60)
32 tabs.add_child(panel)
21 33
22 label = RichTextLabel.new() 34 label = RichTextLabel.new()
23 label.set_name("Label") 35 label.set_name("Label")
24 label.offset_left = 80
25 label.offset_right = 1640
26 label.offset_top = 80
27 label.offset_bottom = 720
28 label.scroll_following = true 36 label.scroll_following = true
29 label.selection_enabled = true 37 label.selection_enabled = true
30 panel.add_child(label) 38 label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
31 39 label.size_flags_vertical = Control.SIZE_EXPAND_FILL
32 label.push_font(load("res://assets/fonts/Lingo2.ttf")) 40 label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
33 label.push_font_size(36) 41 label.push_font_size(36)
34 42
35 var entry_style = StyleBoxFlat.new() 43 var entry_style = StyleBoxFlat.new()
@@ -37,18 +45,30 @@ func _ready():
37 45
38 entry = LineEdit.new() 46 entry = LineEdit.new()
39 entry.set_name("Entry") 47 entry.set_name("Entry")
40 entry.offset_left = 80 48 entry.add_theme_font_override("font", preload("res://assets/fonts/Lingo2.ttf"))
41 entry.offset_right = 1640
42 entry.offset_top = 760
43 entry.offset_bottom = 840
44 entry.add_theme_font_override("font", load("res://assets/fonts/Lingo2.ttf"))
45 entry.add_theme_font_size_override("font_size", 36) 49 entry.add_theme_font_size_override("font_size", 36)
46 entry.add_theme_color_override("font_color", Color(0, 0, 0, 1)) 50 entry.add_theme_color_override("font_color", Color(0, 0, 0, 1))
47 entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1)) 51 entry.add_theme_color_override("cursor_color", Color(0, 0, 0, 1))
48 entry.add_theme_stylebox_override("focus", entry_style) 52 entry.add_theme_stylebox_override("focus", entry_style)
49 panel.add_child(entry)
50 entry.text_submitted.connect(text_entered) 53 entry.text_submitted.connect(text_entered)
51 54
55 var tc_arranger = VBoxContainer.new()
56 tc_arranger.add_child(label)
57 tc_arranger.add_child(entry)
58 tc_arranger.add_theme_constant_override("separation", 40)
59 panel.add_child(tc_arranger)
60
61 var tracker_margins = MarginContainer.new()
62 tracker_margins.name = "Locations"
63 tracker_margins.add_theme_constant_override("margin_top", 60)
64 tracker_margins.add_theme_constant_override("margin_left", 60)
65 tracker_margins.add_theme_constant_override("margin_right", 60)
66 tracker_margins.add_theme_constant_override("margin_bottom", 60)
67 tabs.add_child(tracker_margins)
68
69 tracker_label = RichTextLabel.new()
70 tracker_margins.add_child(tracker_label)
71
52 72
53func _input(event): 73func _input(event):
54 if global.loaded and event is InputEventKey and event.pressed: 74 if global.loaded and event is InputEventKey and event.pressed:
@@ -57,7 +77,7 @@ func _input(event):
57 is_open = true 77 is_open = true
58 get_tree().paused = true 78 get_tree().paused = true
59 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) 79 Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
60 panel.visible = true 80 tabs.visible = true
61 entry.grab_focus() 81 entry.grab_focus()
62 get_viewport().set_input_as_handled() 82 get_viewport().set_input_as_handled()
63 else: 83 else:
@@ -72,7 +92,7 @@ func dismiss():
72 if is_open: 92 if is_open:
73 get_tree().paused = false 93 get_tree().paused = false
74 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) 94 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
75 panel.visible = false 95 tabs.visible = false
76 is_open = false 96 is_open = false
77 97
78 98
@@ -93,3 +113,23 @@ func text_entered(text):
93 return 113 return
94 114
95 ap.client.say(cmd) 115 ap.client.say(cmd)
116
117
118func update_locations():
119 var ap = global.get_node("Archipelago")
120 var gamedata = global.get_node("Gamedata")
121
122 tracker_label.clear()
123 tracker_label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
124 tracker_label.push_font_size(24)
125
126 var location_names = []
127 for location_id in ap.client._accessible_locations:
128 if not ap.client._checked_locations.has(location_id):
129 var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)")
130 location_names.append(location_name)
131
132 location_names.sort()
133
134 for location_name in location_names:
135 tracker_label.append_text("[p]%s[/p]" % location_name)
diff --git a/apworld/context.py b/apworld/context.py index 05f75a3..2a2149f 100644 --- a/apworld/context.py +++ b/apworld/context.py
@@ -8,9 +8,12 @@ import websockets
8 8
9import Utils 9import Utils
10import settings 10import settings
11from BaseClasses import ItemClassification
11from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg 12from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg
12from NetUtils import Endpoint, decode, encode 13from NetUtils import Endpoint, decode, encode
13from Utils import async_start 14from Utils import async_start
15from . import Lingo2World
16from .tracker import Tracker
14 17
15PORT = 43182 18PORT = 43182
16MESSAGE_MAX_SIZE = 16*1024*1024 19MESSAGE_MAX_SIZE = 16*1024*1024
@@ -19,9 +22,11 @@ MESSAGE_MAX_SIZE = 16*1024*1024
19class Lingo2GameContext: 22class Lingo2GameContext:
20 server: Endpoint | None 23 server: Endpoint | None
21 client: "Lingo2ClientContext" 24 client: "Lingo2ClientContext"
25 tracker: Tracker
22 26
23 def __init__(self): 27 def __init__(self):
24 self.server = None 28 self.server = None
29 self.tracker = Tracker()
25 30
26 def send_connected(self): 31 def send_connected(self):
27 msg = { 32 msg = {
@@ -84,6 +89,22 @@ class Lingo2GameContext:
84 89
85 async_start(self.send_msgs([msg]), name="notif") 90 async_start(self.send_msgs([msg]), name="notif")
86 91
92 def send_accessible_locations(self):
93 msg = {
94 "cmd": "AccessibleLocations",
95 "locations": list(self.tracker.accessible_locations),
96 }
97
98 async_start(self.send_msgs([msg]), name="accessible locations")
99
100 def send_update_locations(self, locations):
101 msg = {
102 "cmd": "UpdateLocations",
103 "locations": locations,
104 }
105
106 async_start(self.send_msgs([msg]), name="update locations")
107
87 async def send_msgs(self, msgs: list[Any]) -> None: 108 async def send_msgs(self, msgs: list[Any]) -> None:
88 """ `msgs` JSON serializable """ 109 """ `msgs` JSON serializable """
89 if not self.server or not self.server.socket.open or self.server.socket.closed: 110 if not self.server or not self.server.socket.open or self.server.socket.closed:
@@ -119,7 +140,14 @@ class Lingo2ClientContext(CommonContext):
119 140
120 if self.game_ctx.server is not None: 141 if self.game_ctx.server is not None:
121 self.game_ctx.send_connected() 142 self.game_ctx.send_connected()
143
144 self.game_ctx.tracker.setup_slot(self.slot_data)
145 elif cmd == "RoomUpdate":
146 if self.game_ctx.server is not None:
147 self.game_ctx.send_update_locations(args["checked_locations"])
122 elif cmd == "ReceivedItems": 148 elif cmd == "ReceivedItems":
149 self.game_ctx.tracker.set_collected_items(self.items_received)
150
123 if self.game_ctx.server is not None: 151 if self.game_ctx.server is not None:
124 cur_index = 0 152 cur_index = 0
125 items = [] 153 items = []
@@ -141,6 +169,9 @@ class Lingo2ClientContext(CommonContext):
141 items.append(item_msg) 169 items.append(item_msg)
142 170
143 self.game_ctx.send_item_received(items) 171 self.game_ctx.send_item_received(items)
172
173 if any(ItemClassification.progression in ItemClassification(item.flags) for item in args["items"]):
174 self.game_ctx.send_accessible_locations()
144 elif cmd == "PrintJSON": 175 elif cmd == "PrintJSON":
145 if self.game_ctx.server is not None: 176 if self.game_ctx.server is not None:
146 if "receiving" in args and "item" in args and args["item"].player == self.slot: 177 if "receiving" in args and "item" in args and args["item"].player == self.slot:
@@ -195,6 +226,9 @@ class Lingo2ClientContext(CommonContext):
195 226
196 self.game_ctx.send_location_info(locations) 227 self.game_ctx.send_location_info(locations)
197 228
229 if cmd in ["Connected", "RoomUpdate"]:
230 self.game_ctx.tracker.set_checked_locations(self.checked_locations)
231
198 232
199async def pipe_loop(ctx: Lingo2GameContext): 233async def pipe_loop(ctx: Lingo2GameContext):
200 while not ctx.client.exit_event.is_set(): 234 while not ctx.client.exit_event.is_set():
@@ -205,6 +239,7 @@ async def pipe_loop(ctx: Lingo2GameContext):
205 logger.info("Connected to Lingo 2!") 239 logger.info("Connected to Lingo 2!")
206 if ctx.client.auth is not None: 240 if ctx.client.auth is not None:
207 ctx.send_connected() 241 ctx.send_connected()
242 ctx.send_accessible_locations()
208 async for data in ctx.server.socket: 243 async for data in ctx.server.socket:
209 for msg in decode(data): 244 for msg in decode(data):
210 await process_game_cmd(ctx, msg) 245 await process_game_cmd(ctx, msg)
@@ -237,10 +272,7 @@ async def process_game_cmd(ctx: Lingo2GameContext, args: dict):
237async def run_game(): 272async def run_game():
238 exe_file = settings.get_settings().lingo2_options.exe_file 273 exe_file = settings.get_settings().lingo2_options.exe_file
239 274
240 from worlds import AutoWorldRegister 275 if Lingo2World.zip_path is not None:
241 world = AutoWorldRegister.world_types["Lingo 2"]
242
243 if world.zip_path is not None:
244 # This is a packaged apworld. 276 # This is a packaged apworld.
245 init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn") 277 init_scene = pkgutil.get_data(__name__, "client/run_from_apworld.tscn")
246 init_path = Utils.local_path("data", "lingo2_init.tscn") 278 init_path = Utils.local_path("data", "lingo2_init.tscn")
@@ -254,7 +286,7 @@ async def run_game():
254 "--scene", 286 "--scene",
255 init_path, 287 init_path,
256 "--", 288 "--",
257 str(world.zip_path.absolute()), 289 str(Lingo2World.zip_path.absolute()),
258 ], 290 ],
259 cwd=os.path.dirname(exe_file), 291 cwd=os.path.dirname(exe_file),
260 ) 292 )
diff --git a/apworld/tracker.py b/apworld/tracker.py new file mode 100644 index 0000000..721e9b3 --- /dev/null +++ b/apworld/tracker.py
@@ -0,0 +1,67 @@
1from BaseClasses import MultiWorld, CollectionState, ItemClassification
2from NetUtils import NetworkItem
3from . import Lingo2World, Lingo2Item
4from .regions import connect_ports_from_ut
5from .options import Lingo2Options
6
7PLAYER_NUM = 1
8
9
10class Tracker:
11 multiworld: MultiWorld
12
13 collected_items: dict[int, int]
14 checked_locations: set[int]
15 accessible_locations: set[int]
16
17 state: CollectionState
18
19 def __init__(self):
20 self.collected_items = {}
21 self.checked_locations = set()
22 self.accessible_locations = set()
23
24 def setup_slot(self, slot_data):
25 self.multiworld = MultiWorld(players=PLAYER_NUM)
26 world = Lingo2World(self.multiworld, PLAYER_NUM)
27 self.multiworld.worlds[1] = world
28 world.options = Lingo2Options(**{k: t(slot_data.get(k, t.default))
29 for k, t in Lingo2Options.type_hints.items()})
30
31 world.generate_early()
32 world.create_regions()
33
34 if world.options.shuffle_worldports:
35 port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()}
36 connect_ports_from_ut(port_pairings, world)
37
38 self.state = CollectionState(self.multiworld)
39
40 def set_checked_locations(self, checked_locations: set[int]):
41 self.checked_locations = checked_locations.copy()
42
43 def set_collected_items(self, network_items: list[NetworkItem]):
44 self.collected_items = {}
45
46 for item in network_items:
47 self.collected_items[item.item] = self.collected_items.get(item.item, 0) + 1
48
49 self.refresh_state()
50
51 def refresh_state(self):
52 self.state = CollectionState(self.multiworld)
53
54 for item_id, item_amount in self.collected_items.items():
55 for i in range(item_amount):
56 self.state.collect(Lingo2Item(Lingo2World.static_logic.item_id_to_name.get(item_id),
57 ItemClassification.progression, item_id, PLAYER_NUM), prevent_sweep=True)
58
59 self.state.sweep_for_advancements()
60
61 self.accessible_locations = set()
62
63 for region in self.state.reachable_regions[PLAYER_NUM]:
64 for location in region.locations:
65 if location.address not in self.checked_locations and location.access_rule(self.state):
66 if location.address is not None:
67 self.accessible_locations.add(location.address)