diff options
-rw-r--r-- | apworld/client/client.gd | 25 | ||||
-rw-r--r-- | apworld/client/gamedata.gd | 120 | ||||
-rw-r--r-- | apworld/client/manager.gd | 14 | ||||
-rw-r--r-- | apworld/client/textclient.gd | 86 | ||||
-rw-r--r-- | apworld/context.py | 42 | ||||
-rw-r--r-- | apworld/tracker.py | 67 |
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 = [] | |||
21 | var _received_indexes = [] | 21 | var _received_indexes = [] |
22 | var _received_items = {} | 22 | var _received_items = {} |
23 | var _slot_data = {} | 23 | var _slot_data = {} |
24 | var _accessible_locations = [] | ||
24 | 25 | ||
25 | signal could_not_connect | 26 | signal could_not_connect |
26 | signal connect_status | 27 | signal connect_status |
@@ -30,6 +31,8 @@ signal location_scout_received(location_id, item_name, player_name, flags, for_s | |||
30 | signal text_message_received(message) | 31 | signal text_message_received(message) |
31 | signal item_sent_notification(message) | 32 | signal item_sent_notification(message) |
32 | signal hint_received(message) | 33 | signal hint_received(message) |
34 | signal accessible_locations_updated | ||
35 | signal checked_locations_updated | ||
33 | 36 | ||
34 | 37 | ||
35 | func _init(): | 38 | func _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 | ||
56 | func disconnect_from_ap(): | 60 | func 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 | ||
138 | func connectToServer(server, un, pw): | 161 | func 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 = {} | |||
13 | var letter_id_by_ap_id = {} | 13 | var letter_id_by_ap_id = {} |
14 | var symbol_item_ids = [] | 14 | var symbol_item_ids = [] |
15 | var anti_trap_ids = {} | 15 | var anti_trap_ids = {} |
16 | var location_name_by_id = {} | ||
16 | 17 | ||
17 | var kSYMBOL_ITEMS | 18 | var 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 | ||
156 | func get_door_map_name(door_id): | 169 | func _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 | |
173 | func _get_room_object_map_name(obj): | ||
174 | return _get_map_object_map_name(objects.get_rooms()[obj.get_room_id()]) | ||
175 | |||
176 | |||
177 | func _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 | |||
187 | func _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 | |||
200 | func _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 | |||
251 | func _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 | |||
257 | func _get_mastery_location_name(mastery): | ||
258 | return "%s - Mastery" % _get_room_object_map_name(mastery) | ||
259 | |||
260 | |||
261 | func _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 | |||
267 | func _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 | ||
307 | func _on_accessible_locations_updated(): | ||
308 | var textclient_node = global.get_node("Textclient") | ||
309 | if textclient_node != null: | ||
310 | textclient_node.update_locations() | ||
311 | |||
312 | |||
313 | func _on_checked_locations_updated(): | ||
314 | var textclient_node = global.get_node("Textclient") | ||
315 | if textclient_node != null: | ||
316 | textclient_node.update_locations() | ||
317 | |||
318 | |||
305 | func _client_could_not_connect(message): | 319 | func _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 @@ | |||
1 | extends CanvasLayer | 1 | extends CanvasLayer |
2 | 2 | ||
3 | var tabs | ||
3 | var panel | 4 | var panel |
4 | var label | 5 | var label |
5 | var entry | 6 | var entry |
7 | var tracker_label | ||
6 | var is_open = false | 8 | var 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 | ||
53 | func _input(event): | 73 | func _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 | |||
118 | func 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 | ||
9 | import Utils | 9 | import Utils |
10 | import settings | 10 | import settings |
11 | from BaseClasses import ItemClassification | ||
11 | from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg | 12 | from CommonClient import CommonContext, server_loop, gui_enabled, logger, get_base_parser, handle_url_arg |
12 | from NetUtils import Endpoint, decode, encode | 13 | from NetUtils import Endpoint, decode, encode |
13 | from Utils import async_start | 14 | from Utils import async_start |
15 | from . import Lingo2World | ||
16 | from .tracker import Tracker | ||
14 | 17 | ||
15 | PORT = 43182 | 18 | PORT = 43182 |
16 | MESSAGE_MAX_SIZE = 16*1024*1024 | 19 | MESSAGE_MAX_SIZE = 16*1024*1024 |
@@ -19,9 +22,11 @@ MESSAGE_MAX_SIZE = 16*1024*1024 | |||
19 | class Lingo2GameContext: | 22 | class 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 | ||
199 | async def pipe_loop(ctx: Lingo2GameContext): | 233 | async 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): | |||
237 | async def run_game(): | 272 | async 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 @@ | |||
1 | from BaseClasses import MultiWorld, CollectionState, ItemClassification | ||
2 | from NetUtils import NetworkItem | ||
3 | from . import Lingo2World, Lingo2Item | ||
4 | from .regions import connect_ports_from_ut | ||
5 | from .options import Lingo2Options | ||
6 | |||
7 | PLAYER_NUM = 1 | ||
8 | |||
9 | |||
10 | class 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) | ||