diff options
| -rw-r--r-- | apworld/client/client.gd | 14 | ||||
| -rw-r--r-- | apworld/client/manager.gd | 2 | ||||
| -rw-r--r-- | apworld/client/textclient.gd | 186 | ||||
| -rw-r--r-- | apworld/context.py | 26 | ||||
| -rw-r--r-- | apworld/player_logic.py | 2 | ||||
| -rw-r--r-- | apworld/tracker.py | 35 |
6 files changed, 233 insertions, 32 deletions
| diff --git a/apworld/client/client.gd b/apworld/client/client.gd index 62d7fd8..e25ad4b 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd | |||
| @@ -187,6 +187,12 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo | |||
| 187 | 187 | ||
| 188 | keyboard_update_received.emit(updates) | 188 | keyboard_update_received.emit(updates) |
| 189 | 189 | ||
| 190 | elif cmd == "PathReply": | ||
| 191 | var textclient = global.get_node("Textclient") | ||
| 192 | textclient.display_logical_path( | ||
| 193 | message["type"], int(message.get("id", null)), message["path"] | ||
| 194 | ) | ||
| 195 | |||
| 190 | 196 | ||
| 191 | func connectToServer(server, un, pw): | 197 | func connectToServer(server, un, pw): |
| 192 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) | 198 | sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) |
| @@ -253,6 +259,14 @@ func checkWorldport(port_id): | |||
| 253 | sendMessage([{"cmd": "CheckWorldport", "port_id": port_id}]) | 259 | sendMessage([{"cmd": "CheckWorldport", "port_id": port_id}]) |
| 254 | 260 | ||
| 255 | 261 | ||
| 262 | func getLogicalPath(object_type, object_id): | ||
| 263 | var msg = {"cmd": "GetPath", "type": object_type} | ||
| 264 | if object_id != null: | ||
| 265 | msg["id"] = object_id | ||
| 266 | |||
| 267 | sendMessage([msg]) | ||
| 268 | |||
| 269 | |||
| 256 | func sendQuit(): | 270 | func sendQuit(): |
| 257 | sendMessage([{"cmd": "Quit"}]) | 271 | sendMessage([{"cmd": "Quit"}]) |
| 258 | 272 | ||
| diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 9212233..0d5a5aa 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd | |||
| @@ -347,7 +347,7 @@ func _on_accessible_locations_updated(): | |||
| 347 | func _on_checked_locations_updated(): | 347 | func _on_checked_locations_updated(): |
| 348 | var textclient_node = global.get_node("Textclient") | 348 | var textclient_node = global.get_node("Textclient") |
| 349 | if textclient_node != null: | 349 | if textclient_node != null: |
| 350 | textclient_node.update_locations() | 350 | textclient_node.update_locations(false) |
| 351 | 351 | ||
| 352 | 352 | ||
| 353 | func _on_checked_worldports_updated(): | 353 | func _on_checked_worldports_updated(): |
| 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 | |||
| 4 | var panel | 4 | var panel |
| 5 | var label | 5 | var label |
| 6 | var entry | 6 | var entry |
| 7 | var tracker_label | ||
| 8 | var is_open = false | 7 | var is_open = false |
| 9 | 8 | ||
| 10 | var locations_overlay | 9 | var locations_overlay |
| @@ -12,11 +11,21 @@ var location_texture | |||
| 12 | var worldport_texture | 11 | var worldport_texture |
| 13 | var goal_texture | 12 | var goal_texture |
| 14 | 13 | ||
| 14 | var tracker_tree | ||
| 15 | var tracker_loc_tree_item_by_id = {} | ||
| 16 | var tracker_port_tree_item_by_id = {} | ||
| 17 | var tracker_goal_tree_item = null | ||
| 18 | var tracker_object_by_index = {} | ||
| 19 | |||
| 15 | var worldports_tab | 20 | var worldports_tab |
| 16 | var worldports_tree | 21 | var worldports_tree |
| 17 | var port_tree_item_by_map = {} | 22 | var port_tree_item_by_map = {} |
| 18 | var port_tree_item_by_map_port = {} | 23 | var port_tree_item_by_map_port = {} |
| 19 | 24 | ||
| 25 | const kLocation = 0 | ||
| 26 | const kWorldport = 1 | ||
| 27 | const kGoal = 2 | ||
| 28 | |||
| 20 | 29 | ||
| 21 | func _ready(): | 30 | func _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 | ||
| 170 | func update_locations(): | 189 | func 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 | ||
| 232 | func update_locations_visibility(): | 314 | func 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 | ||
| 319 | func _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 | |||
| 336 | func 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 | |||
| 237 | func setup_worldports(): | 356 | func 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 | |||
| 433 | func 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/context.py b/apworld/context.py index e78ce35..7b5f0bc 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 | ||
| 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/tracker.py b/apworld/tracker.py index 7239b65..c65317c 100644 --- a/apworld/tracker.py +++ b/apworld/tracker.py | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | from typing import TYPE_CHECKING | 1 | from typing import TYPE_CHECKING, Iterator |
| 2 | 2 | ||
| 3 | from BaseClasses import MultiWorld, CollectionState, ItemClassification | 3 | from BaseClasses import MultiWorld, CollectionState, ItemClassification, Region, Entrance |
| 4 | from NetUtils import NetworkItem | 4 | from NetUtils import NetworkItem |
| 5 | from . import Lingo2World, Lingo2Item | 5 | from . import Lingo2World, Lingo2Item |
| 6 | from .regions import connect_ports_from_ut | 6 | from .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] | ||
