about summary refs log tree commit diff stats
path: root/apworld
diff options
context:
space:
mode:
Diffstat (limited to 'apworld')
-rw-r--r--apworld/__init__.py1
-rw-r--r--apworld/client/client.gd22
-rw-r--r--apworld/client/gamedata.gd20
-rw-r--r--apworld/client/manager.gd34
-rw-r--r--apworld/client/player.gd3
-rw-r--r--apworld/client/settings_screen.gd10
-rw-r--r--apworld/client/textclient.gd186
-rw-r--r--apworld/client/worldportListener.gd2
-rw-r--r--apworld/context.py29
-rw-r--r--apworld/player_logic.py2
-rw-r--r--apworld/regions.py6
-rw-r--r--apworld/tracker.py35
12 files changed, 294 insertions, 56 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 1d12050..9e445c7 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -33,6 +33,7 @@ class Lingo2Settings(Group):
33 is_exe = True 33 is_exe = True
34 34
35 exe_file: ExecutableFile = ExecutableFile() 35 exe_file: ExecutableFile = ExecutableFile()
36 start_game: bool = True
36 37
37 38
38class Lingo2World(World): 39class Lingo2World(World):
diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 62d7fd8..9a4b402 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd
@@ -108,7 +108,7 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
108 108
109 _checked_locations = [] 109 _checked_locations = []
110 for location in message["checked_locations"]: 110 for location in message["checked_locations"]:
111 _checked_locations.append(int(message["checked_locations"])) 111 _checked_locations.append(int(location))
112 112
113 client_connected.emit(_slot_data) 113 client_connected.emit(_slot_data)
114 114
@@ -158,11 +158,7 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
158 elif cmd == "LocationInfo": 158 elif cmd == "LocationInfo":
159 for loc in message["locations"]: 159 for loc in message["locations"]:
160 location_scout_received.emit( 160 location_scout_received.emit(
161 int(loc["id"]), 161 int(loc["id"]), loc["item"], loc["player"], int(loc["flags"]), int(loc["self"])
162 loc["item"],
163 loc["player"],
164 int(loc["flags"]),
165 int(loc["for_self"])
166 ) 162 )
167 163
168 elif cmd == "AccessibleLocations": 164 elif cmd == "AccessibleLocations":
@@ -187,6 +183,12 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
187 183
188 keyboard_update_received.emit(updates) 184 keyboard_update_received.emit(updates)
189 185
186 elif cmd == "PathReply":
187 var textclient = global.get_node("Textclient")
188 textclient.display_logical_path(
189 message["type"], int(message.get("id", null)), message["path"]
190 )
191
190 192
191func connectToServer(server, un, pw): 193func connectToServer(server, un, pw):
192 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) 194 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}])
@@ -253,6 +255,14 @@ func checkWorldport(port_id):
253 sendMessage([{"cmd": "CheckWorldport", "port_id": port_id}]) 255 sendMessage([{"cmd": "CheckWorldport", "port_id": port_id}])
254 256
255 257
258func getLogicalPath(object_type, object_id):
259 var msg = {"cmd": "GetPath", "type": object_type}
260 if object_id != null:
261 msg["id"] = object_id
262
263 sendMessage([msg])
264
265
256func sendQuit(): 266func sendQuit():
257 sendMessage([{"cmd": "Quit"}]) 267 sendMessage([{"cmd": "Quit"}])
258 268
diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 334d42a..9305003 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd
@@ -72,7 +72,13 @@ func load(data_bytes):
72 72
73 if door.has_ap_id(): 73 if door.has_ap_id():
74 door_id_by_ap_id[door.get_ap_id()] = door.get_id() 74 door_id_by_ap_id[door.get_ap_id()] = door.get_id()
75 location_name_by_id[door.get_ap_id()] = _get_door_location_name(door) 75
76 if (
77 door.get_type() == SCRIPT_proto.DoorType.STANDARD
78 or door.get_type() == SCRIPT_proto.DoorType.LOCATION_ONLY
79 or door.get_type() == SCRIPT_proto.DoorType.GRAVESTONE
80 ):
81 location_name_by_id[door.get_ap_id()] = _get_door_location_name(door)
76 82
77 for painting in objects.get_paintings(): 83 for painting in objects.get_paintings():
78 var room = objects.get_rooms()[painting.get_room_id()] 84 var room = objects.get_rooms()[painting.get_room_id()]
@@ -166,8 +172,7 @@ func get_door_ap_id(door_id):
166 172
167func get_door_map_name(door_id): 173func get_door_map_name(door_id):
168 var door = objects.get_doors()[door_id] 174 var door = objects.get_doors()[door_id]
169 var room = objects.get_rooms()[door.get_room_id()] 175 var map = objects.get_maps()[door.get_map_id()]
170 var map = objects.get_maps()[room.get_map_id()]
171 return map.get_name() 176 return map.get_name()
172 177
173 178
@@ -227,8 +232,11 @@ func _get_generated_door_location_name(door):
227 var panel = objects.get_panels()[panel_id.get_panel()] 232 var panel = objects.get_panels()[panel_id.get_panel()]
228 var panel_room = objects.get_rooms()[panel.get_room_id()] 233 var panel_room = objects.get_rooms()[panel.get_room_id()]
229 # It's okay if panel_display_name is not present because then it's coalesced with other unnamed areas. 234 # It's okay if panel_display_name is not present because then it's coalesced with other unnamed areas.
230 if not map_areas.has(panel_room.get_panel_display_name()): 235 var panel_display_name = ""
231 map_areas.append(panel_room.get_panel_display_name()) 236 if panel_room.has_panel_display_name():
237 panel_display_name = panel_room.get_panel_display_name()
238 if not map_areas.has(panel_display_name):
239 map_areas.append(panel_display_name)
232 240
233 if map_areas.size() > 1: 241 if map_areas.size() > 1:
234 return null 242 return null
@@ -264,7 +272,7 @@ func _get_generated_door_location_name(door):
264 272
265 273
266func _get_letter_location_name(letter): 274func _get_letter_location_name(letter):
267 var letter_level = 2 if letter.get_level2() else 1 275 var letter_level = 2 if (letter.has_level2() and letter.get_level2()) else 1
268 var letter_name = "%s%d" % [letter.get_key().to_upper(), letter_level] 276 var letter_name = "%s%d" % [letter.get_key().to_upper(), letter_level]
269 return "%s - %s" % [_get_room_object_map_name(letter), letter_name] 277 return "%s - %s" % [_get_room_object_map_name(letter), letter_name]
270 278
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 9212233..4f5018f 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -237,6 +237,9 @@ func _process_item(item, amount):
237 if player != null: 237 if player != null:
238 player.evaluate_solvability.emit() 238 player.evaluate_solvability.emit()
239 239
240 if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]:
241 update_job_well_done_sign()
242
240 # Show a message about the item if it's new. 243 # Show a message about the item if it's new.
241 if int(item["index"]) > _last_new_item: 244 if int(item["index"]) > _last_new_item:
242 _last_new_item = int(item["index"]) 245 _last_new_item = int(item["index"])
@@ -347,7 +350,7 @@ func _on_accessible_locations_updated():
347func _on_checked_locations_updated(): 350func _on_checked_locations_updated():
348 var textclient_node = global.get_node("Textclient") 351 var textclient_node = global.get_node("Textclient")
349 if textclient_node != null: 352 if textclient_node != null:
350 textclient_node.update_locations() 353 textclient_node.update_locations(false)
351 354
352 355
353func _on_checked_worldports_updated(): 356func _on_checked_worldports_updated():
@@ -503,6 +506,9 @@ func start_batching_locations():
503 506
504 507
505func send_location(loc_id): 508func send_location(loc_id):
509 if client._checked_locations.has(loc_id):
510 return
511
506 if _batch_locations: 512 if _batch_locations:
507 _held_locations.append(loc_id) 513 _held_locations.append(loc_id)
508 else: 514 else:
@@ -601,3 +607,29 @@ func _process_key_item(key, level):
601 level += 1 607 level += 1
602 608
603 keyboard.collect_remote_letter(key, level) 609 keyboard.collect_remote_letter(key, level)
610
611
612func update_job_well_done_sign():
613 if global.map != "daedalus":
614 return
615
616 var gamedata = global.get_node("Gamedata")
617 var job_item = gamedata.objects.get_special_ids()["A Job Well Done"]
618 var jobs_done = client.getItemAmount(job_item)
619
620 var sign2 = get_tree().get_root().get_node_or_null("scene/Meshes/Miscellaneous/sign2")
621 var sign3 = get_tree().get_root().get_node_or_null("scene/Meshes/Miscellaneous/sign3")
622
623 if sign2 != null and sign3 != null:
624 if jobs_done == 0:
625 sign2.text = "what are you doing"
626 sign3.text = "?"
627 elif jobs_done == 1:
628 sign2.text = "a job well done"
629 sign3.text = "is its own reward"
630 else:
631 sign2.text = "%d jobs well done" % jobs_done
632 sign3.text = "are their own reward"
633
634 sign2.get_node("MeshInstance3D").mesh.text = sign2.text
635 sign3.get_node("MeshInstance3D").mesh.text = sign3.text
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 5417a48..b73f61e 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -31,6 +31,7 @@ func _ready():
31 if ( 31 if (
32 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY 32 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
33 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING 33 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
34 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
34 ): 35 ):
35 continue 36 continue
36 37
@@ -312,6 +313,8 @@ func _ready():
312 entry1.get_parent().add_child.call_deferred(entry12) 313 entry1.get_parent().add_child.call_deferred(entry12)
313 entry1.queue_free() 314 entry1.queue_free()
314 315
316 ap.update_job_well_done_sign()
317
315 var minimap = ap.SCRIPT_minimap.new() 318 var minimap = ap.SCRIPT_minimap.new()
316 minimap.name = "Minimap" 319 minimap.name = "Minimap"
317 minimap.visible = ap.show_minimap 320 minimap.visible = ap.show_minimap
diff --git a/apworld/client/settings_screen.gd b/apworld/client/settings_screen.gd index b430b17..89e8b68 100644 --- a/apworld/client/settings_screen.gd +++ b/apworld/client/settings_screen.gd
@@ -100,7 +100,7 @@ func _ready():
100 server_box.offset_top = 295.0 100 server_box.offset_top = 295.0
101 server_box.offset_right = 1144.0 101 server_box.offset_right = 1144.0
102 server_box.offset_bottom = 445.0 102 server_box.offset_bottom = 445.0
103 server_box.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER 103 server_box.alignment = HORIZONTAL_ALIGNMENT_CENTER
104 server_box.caret_blink = true 104 server_box.caret_blink = true
105 panel.add_child(server_box) 105 panel.add_child(server_box)
106 106
@@ -110,7 +110,7 @@ func _ready():
110 player_box.offset_top = 477.0 110 player_box.offset_top = 477.0
111 player_box.offset_right = 1144.0 111 player_box.offset_right = 1144.0
112 player_box.offset_bottom = 627.0 112 player_box.offset_bottom = 627.0
113 player_box.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER 113 player_box.alignment = HORIZONTAL_ALIGNMENT_CENTER
114 player_box.caret_blink = true 114 player_box.caret_blink = true
115 panel.add_child(player_box) 115 panel.add_child(player_box)
116 116
@@ -120,20 +120,16 @@ func _ready():
120 password_box.offset_top = 659.0 120 password_box.offset_top = 659.0
121 password_box.offset_right = 1144.0 121 password_box.offset_right = 1144.0
122 password_box.offset_bottom = 809.0 122 password_box.offset_bottom = 809.0
123 password_box.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER 123 password_box.alignment = HORIZONTAL_ALIGNMENT_CENTER
124 password_box.caret_blink = true 124 password_box.caret_blink = true
125 panel.add_child(password_box) 125 panel.add_child(password_box)
126 126
127 var accept_dialog = AcceptDialog.new() 127 var accept_dialog = AcceptDialog.new()
128 accept_dialog.name = "AcceptDialog" 128 accept_dialog.name = "AcceptDialog"
129 accept_dialog.offset_right = 83.0
130 accept_dialog.offset_bottom = 58.0
131 panel.add_child(accept_dialog) 129 panel.add_child(accept_dialog)
132 130
133 var version_mismatch = ConfirmationDialog.new() 131 var version_mismatch = ConfirmationDialog.new()
134 version_mismatch.name = "VersionMismatch" 132 version_mismatch.name = "VersionMismatch"
135 version_mismatch.offset_right = 83.0
136 version_mismatch.offset_bottom = 58.0
137 panel.add_child(version_mismatch) 133 panel.add_child(version_mismatch)
138 134
139 var connection_history = MenuButton.new() 135 var connection_history = MenuButton.new()
diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd index 530eddb..f785a03 100644 --- a/apworld/client/textclient.gd +++ b/apworld/client/textclient.gd
@@ -4,7 +4,6 @@ var tabs
4var panel 4var panel
5var label 5var label
6var entry 6var entry
7var tracker_label
8var is_open = false 7var is_open = false
9 8
10var locations_overlay 9var locations_overlay
@@ -12,11 +11,21 @@ var location_texture
12var worldport_texture 11var worldport_texture
13var goal_texture 12var goal_texture
14 13
14var tracker_tree
15var tracker_loc_tree_item_by_id = {}
16var tracker_port_tree_item_by_id = {}
17var tracker_goal_tree_item = null
18var tracker_object_by_index = {}
19
15var worldports_tab 20var worldports_tab
16var worldports_tree 21var worldports_tree
17var port_tree_item_by_map = {} 22var port_tree_item_by_map = {}
18var port_tree_item_by_map_port = {} 23var port_tree_item_by_map_port = {}
19 24
25const kLocation = 0
26const kWorldport = 1
27const kGoal = 2
28
20 29
21func _ready(): 30func _ready():
22 process_mode = ProcessMode.PROCESS_MODE_ALWAYS 31 process_mode = ProcessMode.PROCESS_MODE_ALWAYS
@@ -61,7 +70,7 @@ func _ready():
61 label.size_flags_horizontal = Control.SIZE_EXPAND_FILL 70 label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
62 label.size_flags_vertical = Control.SIZE_EXPAND_FILL 71 label.size_flags_vertical = Control.SIZE_EXPAND_FILL
63 label.push_font(preload("res://assets/fonts/Lingo2.ttf")) 72 label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
64 label.push_font_size(36) 73 label.push_font_size(30)
65 74
66 var entry_style = StyleBoxFlat.new() 75 var entry_style = StyleBoxFlat.new()
67 entry_style.bg_color = Color(0.9, 0.9, 0.9, 1) 76 entry_style.bg_color = Color(0.9, 0.9, 0.9, 1)
@@ -89,8 +98,18 @@ func _ready():
89 tracker_margins.add_theme_constant_override("margin_bottom", 60) 98 tracker_margins.add_theme_constant_override("margin_bottom", 60)
90 tabs.add_child(tracker_margins) 99 tabs.add_child(tracker_margins)
91 100
92 tracker_label = RichTextLabel.new() 101 tracker_tree = Tree.new()
93 tracker_margins.add_child(tracker_label) 102 tracker_tree.columns = 3
103 tracker_tree.hide_root = true
104 tracker_tree.add_theme_font_size_override("font_size", 24)
105 tracker_tree.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1))
106 tracker_tree.add_theme_constant_override("v_separation", 1)
107 tracker_tree.item_edited.connect(_on_tracker_button_clicked)
108 tracker_tree.set_column_expand(0, false)
109 tracker_tree.set_column_expand(1, true)
110 tracker_tree.set_column_expand(2, false)
111 tracker_tree.set_column_custom_minimum_width(2, 200)
112 tracker_margins.add_child(tracker_tree)
94 113
95 worldports_tab = MarginContainer.new() 114 worldports_tab = MarginContainer.new()
96 worldports_tab.name = "Worldports" 115 worldports_tab.name = "Worldports"
@@ -167,14 +186,10 @@ func text_entered(text):
167 ap.client.say(cmd) 186 ap.client.say(cmd)
168 187
169 188
170func update_locations(): 189func update_locations(reset_locations = true):
171 var ap = global.get_node("Archipelago") 190 var ap = global.get_node("Archipelago")
172 var gamedata = global.get_node("Gamedata") 191 var gamedata = global.get_node("Gamedata")
173 192
174 tracker_label.clear()
175 tracker_label.push_font(preload("res://assets/fonts/Lingo2.ttf"))
176 tracker_label.push_font_size(24)
177
178 locations_overlay.clear() 193 locations_overlay.clear()
179 locations_overlay.push_font(preload("res://assets/fonts/Lingo2.ttf")) 194 locations_overlay.push_font(preload("res://assets/fonts/Lingo2.ttf"))
180 locations_overlay.push_font_size(24) 195 locations_overlay.push_font_size(24)
@@ -182,45 +197,62 @@ func update_locations():
182 locations_overlay.push_outline_color(Color(0, 0, 0, 1)) 197 locations_overlay.push_outline_color(Color(0, 0, 0, 1))
183 locations_overlay.push_outline_size(2) 198 locations_overlay.push_outline_size(2)
184 199
185 const kLocation = 0 200 var locations = []
186 const kWorldport = 1
187 const kGoal = 2
188
189 var location_names = []
190 var type_by_name = {}
191 for location_id in ap.client._accessible_locations: 201 for location_id in ap.client._accessible_locations:
192 if not ap.client._checked_locations.has(location_id): 202 if not ap.client._checked_locations.has(location_id):
193 var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)") 203 var location_name = gamedata.location_name_by_id.get(location_id, "(Unknown)")
194 location_names.append(location_name) 204 (
195 type_by_name[location_name] = kLocation 205 locations
206 . append(
207 {
208 "name": location_name,
209 "type": kLocation,
210 "id": location_id,
211 }
212 )
213 )
196 214
197 for port_id in ap.client._accessible_worldports: 215 for port_id in ap.client._accessible_worldports:
198 if not ap.client._checked_worldports.has(port_id): 216 if not ap.client._checked_worldports.has(port_id):
199 var port_name = gamedata.get_worldport_display_name(port_id) 217 var port_name = gamedata.get_worldport_display_name(port_id)
200 location_names.append(port_name) 218 (
201 type_by_name[port_name] = kWorldport 219 locations
202 220 . append(
203 location_names.sort() 221 {
222 "name": port_name,
223 "type": kWorldport,
224 "id": port_id,
225 }
226 )
227 )
228
229 locations.sort_custom(func(a, b): return a["name"] < b["name"])
204 230
205 if ap.client._goal_accessible: 231 if ap.client._goal_accessible:
206 var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ 232 var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[
207 ap.victory_condition 233 ap.victory_condition
208 ]] 234 ]]
209 location_names.push_front(location_name) 235 (
210 type_by_name[location_name] = kGoal 236 locations
237 . push_front(
238 {
239 "name": location_name,
240 "type": kGoal,
241 }
242 )
243 )
211 244
212 var count = 0 245 var count = 0
213 for location_name in location_names: 246 for location in locations:
214 tracker_label.append_text("[p]%s[/p]" % location_name)
215 if count < 18: 247 if count < 18:
216 locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT) 248 locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT)
217 locations_overlay.append_text(location_name) 249 locations_overlay.append_text(location["name"])
218 locations_overlay.append_text(" ") 250 locations_overlay.append_text(" ")
219 if type_by_name[location_name] == kLocation: 251 if location["type"] == kLocation:
220 locations_overlay.add_image(location_texture) 252 locations_overlay.add_image(location_texture)
221 elif type_by_name[location_name] == kWorldport: 253 elif location["type"] == kWorldport:
222 locations_overlay.add_image(worldport_texture) 254 locations_overlay.add_image(worldport_texture)
223 elif type_by_name[location_name] == kGoal: 255 elif location["type"] == kGoal:
224 locations_overlay.add_image(goal_texture) 256 locations_overlay.add_image(goal_texture)
225 locations_overlay.pop() 257 locations_overlay.pop()
226 count += 1 258 count += 1
@@ -228,12 +260,99 @@ func update_locations():
228 if count > 18: 260 if count > 18:
229 locations_overlay.append_text("[p align=right][lb]...[rb][/p]") 261 locations_overlay.append_text("[p align=right][lb]...[rb][/p]")
230 262
263 if reset_locations:
264 reset_tracker_tab()
265
266 var root_ti = tracker_tree.create_item(null)
267
268 for location in locations:
269 var loc_row = root_ti.create_child()
270 loc_row.set_cell_mode(0, TreeItem.CELL_MODE_ICON)
271 loc_row.set_selectable(0, false)
272 loc_row.set_text(1, location["name"])
273 loc_row.set_selectable(1, false)
274 loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM)
275 loc_row.set_text(2, "Show Path")
276 loc_row.set_custom_as_button(2, true)
277 loc_row.set_editable(2, true)
278 loc_row.set_selectable(2, false)
279 loc_row.set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER)
280
281 if location["type"] == kLocation:
282 loc_row.set_icon(0, location_texture)
283 tracker_loc_tree_item_by_id[location["id"]] = loc_row
284 elif location["type"] == kWorldport:
285 loc_row.set_icon(0, worldport_texture)
286 tracker_port_tree_item_by_id[location["id"]] = loc_row
287 elif location["type"] == kGoal:
288 loc_row.set_icon(0, goal_texture)
289 tracker_goal_tree_item = loc_row
290
291 tracker_object_by_index[loc_row.get_index()] = location
292 else:
293 for loc_row in tracker_tree.get_root().get_children():
294 loc_row.visible = false
295
296 for location_id in tracker_loc_tree_item_by_id.keys():
297 if (
298 ap.client._accessible_locations.has(location_id)
299 and not ap.client._checked_locations.has(location_id)
300 ):
301 tracker_loc_tree_item_by_id[location_id].visible = true
302
303 for port_id in tracker_port_tree_item_by_id.keys():
304 if (
305 ap.client._accessible_worldports.has(port_id)
306 and not ap.client._checked_worldports.has(port_id)
307 ):
308 tracker_port_tree_item_by_id[port_id].visible = true
309
310 if tracker_goal_tree_item != null and ap.client._goal_accessible:
311 tracker_goal_tree_item.visible = true
312
231 313
232func update_locations_visibility(): 314func update_locations_visibility():
233 var ap = global.get_node("Archipelago") 315 var ap = global.get_node("Archipelago")
234 locations_overlay.visible = ap.show_locations 316 locations_overlay.visible = ap.show_locations
235 317
236 318
319func _on_tracker_button_clicked():
320 var edited_item = tracker_tree.get_edited()
321 var edited_index = edited_item.get_index()
322
323 if tracker_object_by_index.has(edited_index):
324 var tracker_object = tracker_object_by_index[edited_index]
325 var ap = global.get_node("Archipelago")
326 var type_str = ""
327 if tracker_object["type"] == kLocation:
328 type_str = "location"
329 elif tracker_object["type"] == kWorldport:
330 type_str = "worldport"
331 elif tracker_object["type"] == kGoal:
332 type_str = "goal"
333 ap.client.getLogicalPath(type_str, tracker_object.get("id", null))
334
335
336func display_logical_path(object_type, object_id, paths):
337 var ap = global.get_node("Archipelago")
338 var gamedata = global.get_node("Gamedata")
339
340 var location_name = "(Unknown)"
341 if object_type == "location" and object_id != null:
342 location_name = gamedata.location_name_by_id.get(object_id, "(Unknown)")
343 elif object_type == "worldport" and object_id != null:
344 location_name = gamedata.get_worldport_display_name(object_id)
345 elif object_type == "goal":
346 location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[
347 ap.victory_condition
348 ]]
349
350 label.append_text("[p]Path to %s:[/p]" % location_name)
351 label.append_text("[ol]" + "\n".join(paths) + "[/ol]")
352
353 panel.visible = true
354
355
237func setup_worldports(): 356func setup_worldports():
238 tabs.set_tab_hidden(2, false) 357 tabs.set_tab_hidden(2, false)
239 358
@@ -308,3 +427,12 @@ func reset():
308 port_tree_item_by_map.clear() 427 port_tree_item_by_map.clear()
309 port_tree_item_by_map_port.clear() 428 port_tree_item_by_map_port.clear()
310 worldports_tree.clear() 429 worldports_tree.clear()
430 reset_tracker_tab()
431
432
433func reset_tracker_tab():
434 tracker_loc_tree_item_by_id.clear()
435 tracker_port_tree_item_by_id.clear()
436 tracker_goal_tree_item = null
437 tracker_object_by_index.clear()
438 tracker_tree.clear()
diff --git a/apworld/client/worldportListener.gd b/apworld/client/worldportListener.gd index 5c2faff..4cff8e9 100644 --- a/apworld/client/worldportListener.gd +++ b/apworld/client/worldportListener.gd
@@ -2,7 +2,7 @@ extends "res://scripts/nodes/listeners/worldportListener.gd"
2 2
3 3
4func handleTriggered(): 4func handleTriggered():
5 if exit == "menus/credits": 5 if exit.begins_with("menus/credits"):
6 return 6 return
7 7
8 super.handleTriggered() 8 super.handleTriggered()
diff --git a/apworld/context.py b/apworld/context.py index e78ce35..a0ee34d 100644 --- a/apworld/context.py +++ b/apworld/context.py
@@ -229,6 +229,21 @@ class Lingo2GameContext:
229 229
230 async_start(self.send_msgs([msg]), name="update worldports") 230 async_start(self.send_msgs([msg]), name="update worldports")
231 231
232 def send_path_reply(self, object_type: str, object_id: int | None, path: list[str]):
233 if self.server is None:
234 return
235
236 msg = {
237 "cmd": "PathReply",
238 "type": object_type,
239 "path": path,
240 }
241
242 if object_id is not None:
243 msg["id"] = object_id
244
245 async_start(self.send_msgs([msg]), name="path reply")
246
232 async def send_msgs(self, msgs: list[Any]) -> None: 247 async def send_msgs(self, msgs: list[Any]) -> None:
233 """ `msgs` JSON serializable """ 248 """ `msgs` JSON serializable """
234 if not self.server or not self.server.socket.open or self.server.socket.closed: 249 if not self.server or not self.server.socket.open or self.server.socket.closed:
@@ -542,6 +557,17 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict):
542 if len(updates) > 0: 557 if len(updates) > 0:
543 async_start(manager.client_ctx.update_worldports(updates), name="client update worldports") 558 async_start(manager.client_ctx.update_worldports(updates), name="client update worldports")
544 manager.game_ctx.send_update_worldports(updates) 559 manager.game_ctx.send_update_worldports(updates)
560 elif cmd == "GetPath":
561 path = None
562
563 if args["type"] == "location":
564 path = manager.tracker.get_path_to_location(args["id"])
565 elif args["type"] == "worldport":
566 path = manager.tracker.get_path_to_port(args["id"])
567 elif args["type"] == "goal":
568 path = manager.tracker.get_path_to_goal()
569
570 manager.game_ctx.send_path_reply(args["type"], args.get("id", None), path)
545 elif cmd == "Quit": 571 elif cmd == "Quit":
546 manager.client_ctx.exit_event.set() 572 manager.client_ctx.exit_event.set()
547 573
@@ -589,7 +615,8 @@ async def run_game():
589 615
590def client_main(*launch_args: str) -> None: 616def client_main(*launch_args: str) -> None:
591 async def main(args): 617 async def main(args):
592 async_start(run_game()) 618 if settings.get_settings().lingo2_options.start_game:
619 async_start(run_game())
593 620
594 client_ctx = Lingo2ClientContext(args.connect, args.password) 621 client_ctx = Lingo2ClientContext(args.connect, args.password)
595 game_ctx = Lingo2GameContext() 622 game_ctx = Lingo2GameContext()
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 3ed1bb1..84c93c8 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -214,6 +214,7 @@ class Lingo2PlayerLogic:
214 real_items: list[str] 214 real_items: list[str]
215 215
216 double_letter_amount: dict[str, int] 216 double_letter_amount: dict[str, int]
217 goal_room_id: int
217 218
218 def __init__(self, world: "Lingo2World"): 219 def __init__(self, world: "Lingo2World"):
219 self.world = world 220 self.world = world
@@ -327,6 +328,7 @@ class Lingo2PlayerLogic:
327 328
328 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: 329 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name:
329 item_name = "Victory" 330 item_name = "Victory"
331 self.goal_room_id = ending.room_id
330 332
331 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name 333 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name
332 334
diff --git a/apworld/regions.py b/apworld/regions.py index 3735858..1215f5a 100644 --- a/apworld/regions.py +++ b/apworld/regions.py
@@ -97,7 +97,7 @@ def create_regions(world: "Lingo2World"):
97 97
98 if connection.HasField("port"): 98 if connection.HasField("port"):
99 port = world.static_logic.objects.ports[connection.port] 99 port = world.static_logic.objects.ports[connection.port]
100 connection_name = f"{connection_name} (via port {port.name})" 100 connection_name = f"{connection_name} (via {port.display_name})"
101 101
102 if world.options.shuffle_worldports and not port.no_shuffle: 102 if world.options.shuffle_worldports and not port.no_shuffle:
103 continue 103 continue
@@ -157,7 +157,7 @@ def shuffle_entrances(world: "Lingo2World"):
157 port_region_name = world.static_logic.get_room_region_name(port.room_id) 157 port_region_name = world.static_logic.get_room_region_name(port.room_id)
158 port_region = world.multiworld.get_region(port_region_name, world.player) 158 port_region = world.multiworld.get_region(port_region_name, world.player)
159 159
160 connection_name = f"{port_region_name} - {port.name}" 160 connection_name = f"{port_region_name} - {port.display_name}"
161 port_id_by_name[connection_name] = port.id 161 port_id_by_name[connection_name] = port.id
162 162
163 entrance = port_region.create_er_target(connection_name) 163 entrance = port_region.create_er_target(connection_name)
@@ -195,7 +195,7 @@ def connect_ports_from_ut(port_pairings: dict[int, int], world: "Lingo2World"):
195 from_region = world.multiworld.get_region(from_region_name, world.player) 195 from_region = world.multiworld.get_region(from_region_name, world.player)
196 to_region = world.multiworld.get_region(to_region_name, world.player) 196 to_region = world.multiworld.get_region(to_region_name, world.player)
197 197
198 connection = Entrance(world.player, f"{from_region_name} - {from_port.name}", from_region) 198 connection = Entrance(world.player, f"{from_region_name} - {from_port.display_name}", from_region)
199 199
200 reqs = AccessRequirements() 200 reqs = AccessRequirements()
201 if from_port.HasField("required_door"): 201 if from_port.HasField("required_door"):
diff --git a/apworld/tracker.py b/apworld/tracker.py index 7239b65..c65317c 100644 --- a/apworld/tracker.py +++ b/apworld/tracker.py
@@ -1,6 +1,6 @@
1from typing import TYPE_CHECKING 1from typing import TYPE_CHECKING, Iterator
2 2
3from BaseClasses import MultiWorld, CollectionState, ItemClassification 3from BaseClasses import MultiWorld, CollectionState, ItemClassification, Region, Entrance
4from NetUtils import NetworkItem 4from NetUtils import NetworkItem
5from . import Lingo2World, Lingo2Item 5from . import Lingo2World, Lingo2Item
6from .regions import connect_ports_from_ut 6from .regions import connect_ports_from_ut
@@ -110,3 +110,34 @@ class Tracker:
110 elif hasattr(location, "goal") and location.goal: 110 elif hasattr(location, "goal") and location.goal:
111 if not self.manager.goaled: 111 if not self.manager.goaled:
112 self.goal_accessible = True 112 self.goal_accessible = True
113
114 def get_path_to_location(self, location_id: int) -> list[str] | None:
115 location_name = self.world.location_id_to_name.get(location_id)
116 location = self.multiworld.get_location(location_name, PLAYER_NUM)
117 return self.get_logical_path(location.parent_region)
118
119 def get_path_to_port(self, port_id: int) -> list[str] | None:
120 port = self.world.static_logic.objects.ports[port_id]
121 region_name = self.world.static_logic.get_room_region_name(port.room_id)
122 region = self.multiworld.get_region(region_name, PLAYER_NUM)
123 return self.get_logical_path(region)
124
125 def get_path_to_goal(self):
126 room_id = self.world.player_logic.goal_room_id
127 region_name = self.world.static_logic.get_room_region_name(room_id)
128 region = self.multiworld.get_region(region_name, PLAYER_NUM)
129 return self.get_logical_path(region)
130
131 def get_logical_path(self, region: Region) -> list[str] | None:
132 if region not in self.state.path:
133 return None
134
135 def flist_to_iter(path_value) -> Iterator[str]:
136 while path_value:
137 region_or_entrance, path_value = path_value
138 yield region_or_entrance
139
140 reversed_path = self.state.path.get(region)
141 flat_path = reversed(list(map(str, flist_to_iter(reversed_path))))
142
143 return list(flat_path)[1::2]