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__.py11
-rw-r--r--apworld/client/client.gd26
-rw-r--r--apworld/client/gamedata.gd4
-rw-r--r--apworld/client/main.gd2
-rw-r--r--apworld/client/manager.gd28
-rw-r--r--apworld/client/player.gd107
-rw-r--r--apworld/client/textclient.gd102
-rw-r--r--apworld/client/unlockReaderListener.gd46
-rw-r--r--apworld/context.py114
-rw-r--r--apworld/player_logic.py3
-rw-r--r--apworld/static_logic.py9
-rw-r--r--apworld/tracker.py5
12 files changed, 389 insertions, 68 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index f5774c6..3d2f075 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -77,7 +77,10 @@ class Lingo2World(World):
77 if self.options.shuffle_worldports: 77 if self.options.shuffle_worldports:
78 if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: 78 if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough:
79 slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"] 79 slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"]
80 self.port_pairings = {int(fp): int(tp) for fp, tp in slot_value.items()} 80 self.port_pairings = {
81 self.static_logic.port_id_by_ap_id[int(fp)]: self.static_logic.port_id_by_ap_id[int(tp)]
82 for fp, tp in slot_value.items()
83 }
81 84
82 connect_ports_from_ut(self.port_pairings, self) 85 connect_ports_from_ut(self.port_pairings, self)
83 else: 86 else:
@@ -152,7 +155,11 @@ class Lingo2World(World):
152 } 155 }
153 156
154 if self.options.shuffle_worldports: 157 if self.options.shuffle_worldports:
155 slot_data["port_pairings"] = self.port_pairings 158 def get_port_ap_id(port_id):
159 return self.static_logic.objects.ports[port_id].ap_id
160
161 slot_data["port_pairings"] = {get_port_ap_id(from_id): get_port_ap_id(to_id)
162 for from_id, to_id in self.port_pairings.items()}
156 163
157 return slot_data 164 return slot_data
158 165
diff --git a/apworld/client/client.gd b/apworld/client/client.gd index ce5ac7e..c149482 100644 --- a/apworld/client/client.gd +++ b/apworld/client/client.gd
@@ -26,6 +26,7 @@ var _accessible_locations = []
26var _accessible_worldports = [] 26var _accessible_worldports = []
27var _goal_accessible = false 27var _goal_accessible = false
28var _latched_doors = [] 28var _latched_doors = []
29var _hinted_locations = []
29 30
30signal could_not_connect 31signal could_not_connect
31signal connect_status 32signal connect_status
@@ -38,8 +39,10 @@ signal hint_received(message)
38signal door_latched(id) 39signal door_latched(id)
39signal accessible_locations_updated 40signal accessible_locations_updated
40signal checked_locations_updated 41signal checked_locations_updated
42signal ignored_locations_updated(locations)
41signal checked_worldports_updated 43signal checked_worldports_updated
42signal keyboard_update_received 44signal keyboard_update_received
45signal hinted_locations_updated
43 46
44 47
45func _init(): 48func _init():
@@ -199,6 +202,21 @@ func _on_web_socket_server_message_received(_peer_id: int, packet: String) -> vo
199 202
200 door_latched.emit(iid) 203 door_latched.emit(iid)
201 204
205 elif cmd == "SetIgnoredLocations":
206 var locs = []
207 for id in message["locations"]:
208 locs.append(int(id))
209
210 ignored_locations_updated.emit(locs)
211
212 elif cmd == "UpdateHintedLocations":
213 for id in message["locations"]:
214 var iid = int(id)
215 if !_hinted_locations.has(iid):
216 _hinted_locations.append(iid)
217
218 hinted_locations_updated.emit()
219
202 220
203func connectToServer(server, un, pw): 221func connectToServer(server, un, pw):
204 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}]) 222 sendMessage([{"cmd": "Connect", "server": server, "player": un, "password": pw}])
@@ -280,6 +298,14 @@ func getLogicalPath(object_type, object_id):
280 sendMessage([msg]) 298 sendMessage([msg])
281 299
282 300
301func addIgnoredLocation(loc_id):
302 sendMessage([{"cmd": "IgnoreLocation", "id": loc_id}])
303
304
305func removeIgnoredLocation(loc_id):
306 sendMessage([{"cmd": "UnignoreLocation", "id": loc_id}])
307
308
283func sendQuit(): 309func sendQuit():
284 sendMessage([{"cmd": "Quit"}]) 310 sendMessage([{"cmd": "Quit"}])
285 311
diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 3a35125..d7e3136 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd
@@ -15,6 +15,7 @@ var symbol_item_ids = []
15var anti_trap_ids = {} 15var anti_trap_ids = {}
16var location_name_by_id = {} 16var location_name_by_id = {}
17var ending_display_name_by_name = {} 17var ending_display_name_by_name = {}
18var port_id_by_ap_id = {}
18 19
19var kSYMBOL_ITEMS 20var kSYMBOL_ITEMS
20 21
@@ -99,6 +100,9 @@ func load(data_bytes):
99 var map_data = port_id_by_map_node_path[map.get_name()] 100 var map_data = port_id_by_map_node_path[map.get_name()]
100 map_data[port.get_path()] = port.get_id() 101 map_data[port.get_path()] = port.get_id()
101 102
103 if port.has_ap_id():
104 port_id_by_ap_id[port.get_ap_id()] = port.get_id()
105
102 for progressive in objects.get_progressives(): 106 for progressive in objects.get_progressives():
103 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() 107 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
104 108
diff --git a/apworld/client/main.gd b/apworld/client/main.gd index a543678..c90d6e7 100644 --- a/apworld/client/main.gd +++ b/apworld/client/main.gd
@@ -51,6 +51,7 @@ func _ready():
51 installScriptExtension(runtime.load_script("saver.gd")) 51 installScriptExtension(runtime.load_script("saver.gd"))
52 installScriptExtension(runtime.load_script("teleport.gd")) 52 installScriptExtension(runtime.load_script("teleport.gd"))
53 installScriptExtension(runtime.load_script("teleportListener.gd")) 53 installScriptExtension(runtime.load_script("teleportListener.gd"))
54 installScriptExtension(runtime.load_script("unlockReaderListener.gd"))
54 installScriptExtension(runtime.load_script("visibilityListener.gd")) 55 installScriptExtension(runtime.load_script("visibilityListener.gd"))
55 installScriptExtension(runtime.load_script("worldport.gd")) 56 installScriptExtension(runtime.load_script("worldport.gd"))
56 installScriptExtension(runtime.load_script("worldportListener.gd")) 57 installScriptExtension(runtime.load_script("worldportListener.gd"))
@@ -253,6 +254,7 @@ func startGame():
253 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") 254 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
254 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") 255 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
255 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 256 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
257 clearResourceCache("res://objects/nodes/listeners/unlockReaderListener.tscn")
256 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn") 258 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
257 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 259 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
258 clearResourceCache("res://objects/nodes/panel.tscn") 260 clearResourceCache("res://objects/nodes/panel.tscn")
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index aa07559..830ebb8 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -29,6 +29,7 @@ var _inverse_item_locks = {}
29var _held_letters = {} 29var _held_letters = {}
30var _letters_setup = false 30var _letters_setup = false
31var _already_connected = false 31var _already_connected = false
32var _ignored_locations = []
32 33
33const kSHUFFLE_LETTERS_VANILLA = 0 34const kSHUFFLE_LETTERS_VANILLA = 0
34const kSHUFFLE_LETTERS_UNLOCKED = 1 35const kSHUFFLE_LETTERS_UNLOCKED = 1
@@ -144,6 +145,8 @@ func _ready():
144 client.hint_received.connect(_process_hint_received) 145 client.hint_received.connect(_process_hint_received)
145 client.accessible_locations_updated.connect(_on_accessible_locations_updated) 146 client.accessible_locations_updated.connect(_on_accessible_locations_updated)
146 client.checked_locations_updated.connect(_on_checked_locations_updated) 147 client.checked_locations_updated.connect(_on_checked_locations_updated)
148 client.ignored_locations_updated.connect(_on_ignored_locations_updated)
149 client.hinted_locations_updated.connect(_on_hinted_locations_updated)
147 client.checked_worldports_updated.connect(_on_checked_worldports_updated) 150 client.checked_worldports_updated.connect(_on_checked_worldports_updated)
148 client.door_latched.connect(_on_door_latched) 151 client.door_latched.connect(_on_door_latched)
149 152
@@ -384,6 +387,20 @@ func _on_checked_worldports_updated():
384 textclient_node.update_worldports() 387 textclient_node.update_worldports()
385 388
386 389
390func _on_ignored_locations_updated(locations):
391 _ignored_locations = locations
392
393 var textclient_node = global.get_node("Textclient")
394 if textclient_node != null:
395 textclient_node.update_locations()
396
397
398func _on_hinted_locations_updated():
399 var textclient_node = global.get_node("Textclient")
400 if textclient_node != null:
401 textclient_node.update_locations()
402
403
387func _on_door_latched(door_id): 404func _on_door_latched(door_id):
388 var gamedata = global.get_node("Gamedata") 405 var gamedata = global.get_node("Gamedata")
389 if gamedata.get_door_map_name(door_id) != global.map: 406 if gamedata.get_door_map_name(door_id) != global.map:
@@ -472,7 +489,9 @@ func _client_connected(slot_data):
472 var raw_pp = slot_data.get("port_pairings") 489 var raw_pp = slot_data.get("port_pairings")
473 490
474 for p1 in raw_pp.keys(): 491 for p1 in raw_pp.keys():
475 port_pairings[int(p1)] = int(raw_pp[p1]) 492 port_pairings[gamedata.port_id_by_ap_id[int(p1)]] = gamedata.port_id_by_ap_id[int(
493 raw_pp[p1]
494 )]
476 495
477 # Set up item locks. 496 # Set up item locks.
478 _item_locks = {} 497 _item_locks = {}
@@ -675,3 +694,10 @@ func update_job_well_done_sign():
675 694
676 sign2.get_node("MeshInstance3D").mesh.text = sign2.text 695 sign2.get_node("MeshInstance3D").mesh.text = sign2.text
677 sign3.get_node("MeshInstance3D").mesh.text = sign3.text 696 sign3.get_node("MeshInstance3D").mesh.text = sign3.text
697
698
699func toggle_ignored_location(loc_id):
700 if loc_id in _ignored_locations:
701 client.removeIgnoredLocation(loc_id)
702 else:
703 client.addIgnoredLocation(loc_id)
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 0e3fb23..65bf54e 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -98,38 +98,72 @@ func _ready():
98 old_door.queue_free() 98 old_door.queue_free()
99 get_node("/root/scene/Components/Doors").add_child.call_deferred(new_door) 99 get_node("/root/scene/Components/Doors").add_child.call_deferred(new_door)
100 100
101 # Block off roof access in Daedalus. 101 if global.map == "daedalus":
102 if global.map == "daedalus" and not ap.daedalus_roof_access: 102 # Teleport the direction panels when the stairs are there.
103 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) 103 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
104 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1) 104
105 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10) 105 var dir1 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_1")
106 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1) 106 var dir1_tpl = tpl_prefab.instantiate()
107 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1) 107 dir1_tpl.target_path = dir1
108 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22) 108 dir1_tpl.teleport_point = Vector3(59.5, 8, -6.5)
109 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16) 109 dir1_tpl.teleport_rotate = Vector3(-45, 0, 0)
110 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1) 110 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
111 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1) 111 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
112 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4) 112 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
113 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25) 113 dir1.add_child.call_deferred(dir1_tpl)
114 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1) 114
115 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1) 115 var dir2 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_2")
116 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11) 116 var dir2_tpl = tpl_prefab.instantiate()
117 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11) 117 dir2_tpl.target_path = dir2
118 118 dir2_tpl.teleport_point = Vector3(59.5, 8, 6.5)
119 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn") 119 dir2_tpl.teleport_rotate = Vector3(-45, -180, 0)
120 var warp_exit = warp_exit_prefab.instantiate() 120 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
121 warp_exit.name = "roof_access_blocker_warp_exit" 121 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
122 warp_exit.position = Vector3(58, 10, 0) 122 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
123 warp_exit.rotation_degrees.y = 90 123 dir2.add_child.call_deferred(dir2_tpl)
124 get_parent().add_child.call_deferred(warp_exit) 124
125 125 var dir3 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_3")
126 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn") 126 var dir3_tpl = tpl_prefab.instantiate()
127 var warp_enter = warp_enter_prefab.instantiate() 127 dir3_tpl.target_path = dir3
128 warp_enter.target = warp_exit 128 dir3_tpl.teleport_point = Vector3(54, 8, 0)
129 warp_enter.position = Vector3(76.5, 30, 1) 129 dir3_tpl.teleport_rotate = Vector3(-45, 90, 0)
130 warp_enter.scale = Vector3(4, 1.5, 1) 130 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
131 warp_enter.rotation_degrees.y = 90 131 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
132 get_parent().add_child.call_deferred(warp_enter) 132 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
133 dir3.add_child.call_deferred(dir3_tpl)
134
135 # Block off roof access in Daedalus.
136 if not ap.daedalus_roof_access:
137 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
138 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
139 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
140 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
141 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
142 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
143 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
144 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
145 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
146 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
147 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
148 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
149 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
150 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
151 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
152
153 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
154 var warp_exit = warp_exit_prefab.instantiate()
155 warp_exit.name = "roof_access_blocker_warp_exit"
156 warp_exit.position = Vector3(58, 10, 0)
157 warp_exit.rotation_degrees.y = 90
158 get_parent().add_child.call_deferred(warp_exit)
159
160 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
161 var warp_enter = warp_enter_prefab.instantiate()
162 warp_enter.target = warp_exit
163 warp_enter.position = Vector3(76.5, 30, 1)
164 warp_enter.scale = Vector3(4, 1.5, 1)
165 warp_enter.rotation_degrees.y = 90
166 get_parent().add_child.call_deferred(warp_enter)
133 167
134 if global.map == "the_entry": 168 if global.map == "the_entry":
135 # Remove door behind X1. 169 # Remove door behind X1.
@@ -604,9 +638,12 @@ func _ready():
604 continue 638 continue
605 639
606 if ( 640 if (
607 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY 641 not (door.has_legacy_location() and door.get_legacy_location())
608 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING 642 and (
609 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR 643 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
644 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
645 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
646 )
610 ): 647 ):
611 continue 648 continue
612 649
diff --git a/apworld/client/textclient.gd b/apworld/client/textclient.gd index f785a03..ce28a3a 100644 --- a/apworld/client/textclient.gd +++ b/apworld/client/textclient.gd
@@ -16,6 +16,8 @@ var tracker_loc_tree_item_by_id = {}
16var tracker_port_tree_item_by_id = {} 16var tracker_port_tree_item_by_id = {}
17var tracker_goal_tree_item = null 17var tracker_goal_tree_item = null
18var tracker_object_by_index = {} 18var tracker_object_by_index = {}
19var tracker_object_by_ignored_index = {}
20var tracker_ignored_group = null
19 21
20var worldports_tab 22var worldports_tab
21var worldports_tree 23var worldports_tree
@@ -99,7 +101,7 @@ func _ready():
99 tabs.add_child(tracker_margins) 101 tabs.add_child(tracker_margins)
100 102
101 tracker_tree = Tree.new() 103 tracker_tree = Tree.new()
102 tracker_tree.columns = 3 104 tracker_tree.columns = 4
103 tracker_tree.hide_root = true 105 tracker_tree.hide_root = true
104 tracker_tree.add_theme_font_size_override("font_size", 24) 106 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)) 107 tracker_tree.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1))
@@ -108,7 +110,9 @@ func _ready():
108 tracker_tree.set_column_expand(0, false) 110 tracker_tree.set_column_expand(0, false)
109 tracker_tree.set_column_expand(1, true) 111 tracker_tree.set_column_expand(1, true)
110 tracker_tree.set_column_expand(2, false) 112 tracker_tree.set_column_expand(2, false)
113 tracker_tree.set_column_expand(3, false)
111 tracker_tree.set_column_custom_minimum_width(2, 200) 114 tracker_tree.set_column_custom_minimum_width(2, 200)
115 tracker_tree.set_column_custom_minimum_width(3, 200)
112 tracker_margins.add_child(tracker_tree) 116 tracker_margins.add_child(tracker_tree)
113 117
114 worldports_tab = MarginContainer.new() 118 worldports_tab = MarginContainer.new()
@@ -208,6 +212,8 @@ func update_locations(reset_locations = true):
208 "name": location_name, 212 "name": location_name,
209 "type": kLocation, 213 "type": kLocation,
210 "id": location_id, 214 "id": location_id,
215 "ignored": ap._ignored_locations.has(location_id),
216 "hint": ap.client._hinted_locations.has(location_id),
211 } 217 }
212 ) 218 )
213 ) 219 )
@@ -222,11 +228,13 @@ func update_locations(reset_locations = true):
222 "name": port_name, 228 "name": port_name,
223 "type": kWorldport, 229 "type": kWorldport,
224 "id": port_id, 230 "id": port_id,
231 "ignored": false,
232 "hint": false,
225 } 233 }
226 ) 234 )
227 ) 235 )
228 236
229 locations.sort_custom(func(a, b): return a["name"] < b["name"]) 237 locations.sort_custom(_cmp_tracker_objects)
230 238
231 if ap.client._goal_accessible: 239 if ap.client._goal_accessible:
232 var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[ 240 var location_name = gamedata.ending_display_name_by_name[ap.kEndingNameByVictoryValue[
@@ -238,14 +246,18 @@ func update_locations(reset_locations = true):
238 { 246 {
239 "name": location_name, 247 "name": location_name,
240 "type": kGoal, 248 "type": kGoal,
249 "ignored": false,
250 "hint": false,
241 } 251 }
242 ) 252 )
243 ) 253 )
244 254
245 var count = 0 255 var count = 0
246 for location in locations: 256 for location in locations:
247 if count < 18: 257 if count < 18 and not location["ignored"]:
248 locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT) 258 locations_overlay.push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT)
259 if location["hint"]:
260 locations_overlay.push_color(Color("#fafad2"))
249 locations_overlay.append_text(location["name"]) 261 locations_overlay.append_text(location["name"])
250 locations_overlay.append_text(" ") 262 locations_overlay.append_text(" ")
251 if location["type"] == kLocation: 263 if location["type"] == kLocation:
@@ -254,6 +266,8 @@ func update_locations(reset_locations = true):
254 locations_overlay.add_image(worldport_texture) 266 locations_overlay.add_image(worldport_texture)
255 elif location["type"] == kGoal: 267 elif location["type"] == kGoal:
256 locations_overlay.add_image(goal_texture) 268 locations_overlay.add_image(goal_texture)
269 if location["hint"]:
270 locations_overlay.pop()
257 locations_overlay.pop() 271 locations_overlay.pop()
258 count += 1 272 count += 1
259 273
@@ -266,17 +280,43 @@ func update_locations(reset_locations = true):
266 var root_ti = tracker_tree.create_item(null) 280 var root_ti = tracker_tree.create_item(null)
267 281
268 for location in locations: 282 for location in locations:
269 var loc_row = root_ti.create_child() 283 var loc_row
284
285 if location["ignored"]:
286 if tracker_ignored_group == null:
287 tracker_ignored_group = root_ti.create_child()
288 tracker_ignored_group.set_text(1, "Ignored Locations")
289 tracker_ignored_group.set_selectable(0, false)
290 tracker_ignored_group.set_selectable(1, false)
291 tracker_ignored_group.set_selectable(2, false)
292 tracker_ignored_group.set_selectable(3, false)
293
294 loc_row = tracker_ignored_group.create_child()
295 else:
296 loc_row = root_ti.create_child()
297
270 loc_row.set_cell_mode(0, TreeItem.CELL_MODE_ICON) 298 loc_row.set_cell_mode(0, TreeItem.CELL_MODE_ICON)
271 loc_row.set_selectable(0, false) 299 loc_row.set_selectable(0, false)
272 loc_row.set_text(1, location["name"]) 300 loc_row.set_text(1, location["name"])
273 loc_row.set_selectable(1, false) 301 loc_row.set_selectable(1, false)
302 if location["hint"]:
303 loc_row.set_custom_color(1, Color("#fafad2"))
274 loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM) 304 loc_row.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM)
275 loc_row.set_text(2, "Show Path") 305 loc_row.set_text(2, "Show Path")
276 loc_row.set_custom_as_button(2, true) 306 loc_row.set_custom_as_button(2, true)
277 loc_row.set_editable(2, true) 307 loc_row.set_editable(2, true)
278 loc_row.set_selectable(2, false) 308 loc_row.set_selectable(2, false)
279 loc_row.set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER) 309 loc_row.set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER)
310 loc_row.set_selectable(3, false)
311 if location["type"] == kLocation:
312 loc_row.set_cell_mode(3, TreeItem.CELL_MODE_CUSTOM)
313 if location["ignored"]:
314 loc_row.set_text(3, "Unignore")
315 else:
316 loc_row.set_text(3, "Ignore")
317 loc_row.set_custom_as_button(3, true)
318 loc_row.set_editable(3, true)
319 loc_row.set_text_alignment(3, HORIZONTAL_ALIGNMENT_CENTER)
280 320
281 if location["type"] == kLocation: 321 if location["type"] == kLocation:
282 loc_row.set_icon(0, location_texture) 322 loc_row.set_icon(0, location_texture)
@@ -288,7 +328,10 @@ func update_locations(reset_locations = true):
288 loc_row.set_icon(0, goal_texture) 328 loc_row.set_icon(0, goal_texture)
289 tracker_goal_tree_item = loc_row 329 tracker_goal_tree_item = loc_row
290 330
291 tracker_object_by_index[loc_row.get_index()] = location 331 if location["ignored"]:
332 tracker_object_by_ignored_index[loc_row.get_index()] = location
333 else:
334 tracker_object_by_index[loc_row.get_index()] = location
292 else: 335 else:
293 for loc_row in tracker_tree.get_root().get_children(): 336 for loc_row in tracker_tree.get_root().get_children():
294 loc_row.visible = false 337 loc_row.visible = false
@@ -310,6 +353,18 @@ func update_locations(reset_locations = true):
310 if tracker_goal_tree_item != null and ap.client._goal_accessible: 353 if tracker_goal_tree_item != null and ap.client._goal_accessible:
311 tracker_goal_tree_item.visible = true 354 tracker_goal_tree_item.visible = true
312 355
356 if tracker_ignored_group != null:
357 tracker_ignored_group.visible = true
358
359
360func _cmp_tracker_objects(a, b) -> bool:
361 if a["ignored"] != b["ignored"]:
362 return !a["ignored"]
363 elif a["hint"] != b["hint"]:
364 return a["hint"]
365 else:
366 return a["name"] < b["name"]
367
313 368
314func update_locations_visibility(): 369func update_locations_visibility():
315 var ap = global.get_node("Archipelago") 370 var ap = global.get_node("Archipelago")
@@ -317,20 +372,33 @@ func update_locations_visibility():
317 372
318 373
319func _on_tracker_button_clicked(): 374func _on_tracker_button_clicked():
375 var ap = global.get_node("Archipelago")
376
320 var edited_item = tracker_tree.get_edited() 377 var edited_item = tracker_tree.get_edited()
321 var edited_index = edited_item.get_index() 378 var edited_index = edited_item.get_index()
322 379
323 if tracker_object_by_index.has(edited_index): 380 if edited_item.get_parent() == tracker_tree.get_root():
324 var tracker_object = tracker_object_by_index[edited_index] 381 if tracker_object_by_index.has(edited_index):
325 var ap = global.get_node("Archipelago") 382 var tracker_object = tracker_object_by_index[edited_index]
326 var type_str = "" 383 if tracker_tree.get_edited_column() == 2:
327 if tracker_object["type"] == kLocation: 384 var type_str = ""
328 type_str = "location" 385 if tracker_object["type"] == kLocation:
329 elif tracker_object["type"] == kWorldport: 386 type_str = "location"
330 type_str = "worldport" 387 elif tracker_object["type"] == kWorldport:
331 elif tracker_object["type"] == kGoal: 388 type_str = "worldport"
332 type_str = "goal" 389 elif tracker_object["type"] == kGoal:
333 ap.client.getLogicalPath(type_str, tracker_object.get("id", null)) 390 type_str = "goal"
391 ap.client.getLogicalPath(type_str, tracker_object.get("id", null))
392 elif tracker_tree.get_edited_column() == 3:
393 ap.toggle_ignored_location(tracker_object["id"])
394 elif edited_item.get_parent() == tracker_ignored_group:
395 # This is the ignored locations group.
396 if (
397 tracker_object_by_ignored_index.has(edited_index)
398 and tracker_tree.get_edited_column() == 3
399 ):
400 var tracker_object = tracker_object_by_ignored_index[edited_index]
401 ap.toggle_ignored_location(tracker_object["id"])
334 402
335 403
336func display_logical_path(object_type, object_id, paths): 404func display_logical_path(object_type, object_id, paths):
@@ -435,4 +503,6 @@ func reset_tracker_tab():
435 tracker_port_tree_item_by_id.clear() 503 tracker_port_tree_item_by_id.clear()
436 tracker_goal_tree_item = null 504 tracker_goal_tree_item = null
437 tracker_object_by_index.clear() 505 tracker_object_by_index.clear()
506 tracker_object_by_ignored_index.clear()
507 tracker_ignored_group = null
438 tracker_tree.clear() 508 tracker_tree.clear()
diff --git a/apworld/client/unlockReaderListener.gd b/apworld/client/unlockReaderListener.gd new file mode 100644 index 0000000..a5754b9 --- /dev/null +++ b/apworld/client/unlockReaderListener.gd
@@ -0,0 +1,46 @@
1extends "res://scripts/nodes/listeners/unlockReaderListener.gd"
2
3var item_id = null
4var item_amount
5
6
7func _ready():
8 var node_path = String(
9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
10 )
11
12 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null:
15 var ap = global.get_node("Archipelago")
16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
21
22 self.senders = []
23 self.senderGroup = []
24 self.nested = false
25 self.complete_at = 0
26 self.max_length = 0
27 self.excludeSenders = []
28
29 super._ready()
30
31
32func _readier():
33 if item_id != null:
34 var ap = global.get_node("Archipelago")
35
36 if ap.client.getItemAmount(item_id) >= item_amount:
37 handleTriggered()
38 else:
39 super._readier()
40
41
42func handleTriggered():
43 if item_id != null:
44 emit_signal("trigger")
45 else:
46 super.handleTriggered()
diff --git a/apworld/context.py b/apworld/context.py index e2d80cd..86392f9 100644 --- a/apworld/context.py +++ b/apworld/context.py
@@ -30,6 +30,16 @@ KEY_STORAGE_MAPPING = {
30REVERSE_KEY_STORAGE_MAPPING = {t: k for k, t in KEY_STORAGE_MAPPING.items()} 30REVERSE_KEY_STORAGE_MAPPING = {t: k for k, t in KEY_STORAGE_MAPPING.items()}
31 31
32 32
33# There is a distinction between an object's ID and its AP ID. The latter is stable between releases, whereas the former
34# can change and is also namespaced based on the object type. We should only store AP IDs in multiworld state (such as
35# slot data and data storage) to increase compatability between releases. The data we currently store is:
36# - Port pairings for worldport shuffle (slot data)
37# - Checked worldports for worldport shuffle (data storage)
38# - Latched doors (data storage)
39# The client generally deals in the actual object IDs rather than the stable IDs, although it does have to convert the
40# port pairing IDs when reading them from slot data. The context (this file here) does the work of converting back and
41# forth between the values. AP IDs are converted to IDs after reading them from data storage, and IDs are converted to
42# AP IDs before sending them to data storage.
33class Lingo2Manager: 43class Lingo2Manager:
34 game_ctx: "Lingo2GameContext" 44 game_ctx: "Lingo2GameContext"
35 client_ctx: "Lingo2ClientContext" 45 client_ctx: "Lingo2ClientContext"
@@ -39,6 +49,7 @@ class Lingo2Manager:
39 worldports: set[int] 49 worldports: set[int]
40 goaled: bool 50 goaled: bool
41 latches: set[int] 51 latches: set[int]
52 hinted_locations: set[int]
42 53
43 def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): 54 def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"):
44 self.game_ctx = game_ctx 55 self.game_ctx = game_ctx
@@ -47,8 +58,6 @@ class Lingo2Manager:
47 self.client_ctx.manager = self 58 self.client_ctx.manager = self
48 self.tracker = Tracker(self) 59 self.tracker = Tracker(self)
49 self.keyboard = {} 60 self.keyboard = {}
50 self.worldports = set()
51 self.latches = set()
52 61
53 self.reset() 62 self.reset()
54 63
@@ -59,6 +68,7 @@ class Lingo2Manager:
59 self.worldports = set() 68 self.worldports = set()
60 self.goaled = False 69 self.goaled = False
61 self.latches = set() 70 self.latches = set()
71 self.hinted_locations = set()
62 72
63 def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]: 73 def update_keyboard(self, new_keyboard: dict[str, int]) -> dict[str, int]:
64 ret: dict[str, int] = {} 74 ret: dict[str, int] = {}
@@ -74,6 +84,7 @@ class Lingo2Manager:
74 84
75 return ret 85 return ret
76 86
87 # Input should be real IDs, not AP IDs
77 def update_worldports(self, new_worldports: set[int]) -> set[int]: 88 def update_worldports(self, new_worldports: set[int]) -> set[int]:
78 ret = new_worldports.difference(self.worldports) 89 ret = new_worldports.difference(self.worldports)
79 self.worldports.update(new_worldports) 90 self.worldports.update(new_worldports)
@@ -90,6 +101,12 @@ class Lingo2Manager:
90 101
91 return ret 102 return ret
92 103
104 def update_hinted_locations(self, new_locs: set[int]) -> set[int]:
105 ret = new_locs.difference(self.hinted_locations)
106 self.hinted_locations.update(new_locs)
107
108 return ret
109
93 110
94class Lingo2GameContext: 111class Lingo2GameContext:
95 server: Endpoint | None 112 server: Endpoint | None
@@ -227,6 +244,7 @@ class Lingo2GameContext:
227 244
228 async_start(self.send_msgs([msg]), name="update keyboard") 245 async_start(self.send_msgs([msg]), name="update keyboard")
229 246
247 # Input should be real IDs, not AP IDs
230 def send_update_worldports(self, worldports): 248 def send_update_worldports(self, worldports):
231 if self.server is None: 249 if self.server is None:
232 return 250 return
@@ -264,6 +282,28 @@ class Lingo2GameContext:
264 282
265 async_start(self.send_msgs([msg]), name="update latches") 283 async_start(self.send_msgs([msg]), name="update latches")
266 284
285 def send_ignored_locations(self, ignored_locations):
286 if self.server is None:
287 return
288
289 msg = {
290 "cmd": "SetIgnoredLocations",
291 "locations": ignored_locations,
292 }
293
294 async_start(self.send_msgs([msg]), name="set ignored locations")
295
296 def send_update_hinted_locations(self, hinted_locations):
297 if self.server is None:
298 return
299
300 msg = {
301 "cmd": "UpdateHintedLocations",
302 "locations": hinted_locations,
303 }
304
305 async_start(self.send_msgs([msg]), name="update hinted locations")
306
267 async def send_msgs(self, msgs: list[Any]) -> None: 307 async def send_msgs(self, msgs: list[Any]) -> None:
268 """ `msgs` JSON serializable """ 308 """ `msgs` JSON serializable """
269 if not self.server or not self.server.socket.open or self.server.socket.closed: 309 if not self.server or not self.server.socket.open or self.server.socket.closed:
@@ -278,6 +318,7 @@ class Lingo2ClientContext(CommonContext):
278 items_handling = 0b111 318 items_handling = 0b111
279 319
280 slot_data: dict[str, Any] | None 320 slot_data: dict[str, Any] | None
321 hints_data_storage_key: str
281 victory_data_storage_key: str 322 victory_data_storage_key: str
282 323
283 def __init__(self, server_address: str | None = None, password: str | None = None): 324 def __init__(self, server_address: str | None = None, password: str | None = None):
@@ -315,10 +356,12 @@ class Lingo2ClientContext(CommonContext):
315 self.manager.tracker.set_checked_locations(self.checked_locations) 356 self.manager.tracker.set_checked_locations(self.checked_locations)
316 self.manager.game_ctx.send_accessible_locations() 357 self.manager.game_ctx.send_accessible_locations()
317 358
359 self.hints_data_storage_key = f"_read_hints_{self.team}_{self.slot}"
318 self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}" 360 self.victory_data_storage_key = f"_read_client_status_{self.team}_{self.slot}"
319 361
320 self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"), 362 self.set_notify(self.get_datastorage_key("keyboard1"), self.get_datastorage_key("keyboard2"),
321 self.victory_data_storage_key, self.get_datastorage_key("latches")) 363 self.victory_data_storage_key, self.get_datastorage_key("latches"),
364 self.get_datastorage_key("ignored_locations"))
322 msg_batch = [{ 365 msg_batch = [{
323 "cmd": "Set", 366 "cmd": "Set",
324 "key": self.get_datastorage_key("keyboard1"), 367 "key": self.get_datastorage_key("keyboard1"),
@@ -337,6 +380,12 @@ class Lingo2ClientContext(CommonContext):
337 "default": [], 380 "default": [],
338 "want_reply": True, 381 "want_reply": True,
339 "operations": [{"operation": "default", "value": []}] 382 "operations": [{"operation": "default", "value": []}]
383 }, {
384 "cmd": "Set",
385 "key": self.get_datastorage_key("ignored_locations"),
386 "default": [],
387 "want_reply": True,
388 "operations": [{"operation": "default", "value": []}]
340 }] 389 }]
341 390
342 if self.slot_data.get("shuffle_worldports", False): 391 if self.slot_data.get("shuffle_worldports", False):
@@ -435,21 +484,29 @@ class Lingo2ClientContext(CommonContext):
435 for k, v in args["keys"].items(): 484 for k, v in args["keys"].items():
436 if k == self.victory_data_storage_key: 485 if k == self.victory_data_storage_key:
437 self.handle_status_update(v) 486 self.handle_status_update(v)
487 elif k == self.hints_data_storage_key:
488 self.update_hints()
438 elif cmd == "SetReply": 489 elif cmd == "SetReply":
439 if args["key"] == self.get_datastorage_key("keyboard1"): 490 if args["key"] == self.get_datastorage_key("keyboard1"):
440 self.handle_keyboard_update(1, args) 491 self.handle_keyboard_update(1, args)
441 elif args["key"] == self.get_datastorage_key("keyboard2"): 492 elif args["key"] == self.get_datastorage_key("keyboard2"):
442 self.handle_keyboard_update(2, args) 493 self.handle_keyboard_update(2, args)
443 elif args["key"] == self.get_datastorage_key("worldports"): 494 elif args["key"] == self.get_datastorage_key("worldports"):
444 updates = self.manager.update_worldports(set(args["value"])) 495 port_ids = set(Lingo2World.static_logic.port_id_by_ap_id[ap_id] for ap_id in args["value"])
496 updates = self.manager.update_worldports(port_ids)
445 if len(updates) > 0: 497 if len(updates) > 0:
446 self.manager.game_ctx.send_update_worldports(updates) 498 self.manager.game_ctx.send_update_worldports(updates)
447 elif args["key"] == self.victory_data_storage_key: 499 elif args["key"] == self.victory_data_storage_key:
448 self.handle_status_update(args["value"]) 500 self.handle_status_update(args["value"])
449 elif args["key"] == self.get_datastorage_key("latches"): 501 elif args["key"] == self.get_datastorage_key("latches"):
450 updates = self.manager.update_latches(set(args["value"])) 502 door_ids = set(Lingo2World.static_logic.door_id_by_ap_id[ap_id] for ap_id in args["value"])
503 updates = self.manager.update_latches(door_ids)
451 if len(updates) > 0: 504 if len(updates) > 0:
452 self.manager.game_ctx.send_update_latches(updates) 505 self.manager.game_ctx.send_update_latches(updates)
506 elif args["key"] == self.get_datastorage_key("ignored_locations"):
507 self.manager.game_ctx.send_ignored_locations(args["value"])
508 elif args["key"] == self.hints_data_storage_key:
509 self.update_hints()
453 510
454 def get_datastorage_key(self, name: str): 511 def get_datastorage_key(self, name: str):
455 return f"Lingo2_{self.slot}_{name}" 512 return f"Lingo2_{self.slot}_{name}"
@@ -515,14 +572,16 @@ class Lingo2ClientContext(CommonContext):
515 if len(updates) > 0: 572 if len(updates) > 0:
516 self.manager.game_ctx.send_update_keyboard(updates) 573 self.manager.game_ctx.send_update_keyboard(updates)
517 574
575 # Input should be real IDs, not AP IDs
518 async def update_worldports(self, updates: set[int]): 576 async def update_worldports(self, updates: set[int]):
577 port_ap_ids = [Lingo2World.static_logic.objects.ports[port_id].ap_id for port_id in updates]
519 await self.send_msgs([{ 578 await self.send_msgs([{
520 "cmd": "Set", 579 "cmd": "Set",
521 "key": self.get_datastorage_key("worldports"), 580 "key": self.get_datastorage_key("worldports"),
522 "want_reply": True, 581 "want_reply": True,
523 "operations": [{ 582 "operations": [{
524 "operation": "update", 583 "operation": "update",
525 "value": updates 584 "value": port_ap_ids
526 }] 585 }]
527 }]) 586 }])
528 587
@@ -532,16 +591,47 @@ class Lingo2ClientContext(CommonContext):
532 self.manager.game_ctx.send_accessible_locations() 591 self.manager.game_ctx.send_accessible_locations()
533 592
534 async def update_latches(self, updates: set[int]): 593 async def update_latches(self, updates: set[int]):
594 door_ap_ids = [Lingo2World.static_logic.objects.doors[door_id].ap_id for door_id in updates]
535 await self.send_msgs([{ 595 await self.send_msgs([{
536 "cmd": "Set", 596 "cmd": "Set",
537 "key": self.get_datastorage_key("latches"), 597 "key": self.get_datastorage_key("latches"),
538 "want_reply": True, 598 "want_reply": True,
539 "operations": [{ 599 "operations": [{
540 "operation": "update", 600 "operation": "update",
541 "value": updates 601 "value": door_ap_ids
602 }]
603 }])
604
605 async def add_ignored_location(self, loc_id: int):
606 await self.send_msgs([{
607 "cmd": "Set",
608 "key": self.get_datastorage_key("ignored_locations"),
609 "want_reply": True,
610 "operations": [{
611 "operation": "update",
612 "value": [loc_id]
542 }] 613 }]
543 }]) 614 }])
544 615
616 async def remove_ignored_location(self, loc_id: int):
617 await self.send_msgs([{
618 "cmd": "Set",
619 "key": self.get_datastorage_key("ignored_locations"),
620 "want_reply": True,
621 "operations": [{
622 "operation": "remove",
623 "value": loc_id
624 }]
625 }])
626
627 def update_hints(self):
628 hints = self.stored_data.get(self.hints_data_storage_key, [])
629
630 hinted_locations = set(hint["location"] for hint in hints if hint["finding_player"] == self.slot)
631 updates = self.manager.update_hinted_locations(hinted_locations)
632 if len(updates) > 0:
633 self.manager.game_ctx.send_update_hinted_locations(updates)
634
545 635
546async def pipe_loop(manager: Lingo2Manager): 636async def pipe_loop(manager: Lingo2Manager):
547 while not manager.client_ctx.exit_event.is_set(): 637 while not manager.client_ctx.exit_event.is_set():
@@ -590,12 +680,14 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict):
590 async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard") 680 async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard")
591 elif cmd == "CheckWorldport": 681 elif cmd == "CheckWorldport":
592 port_id = args["port_id"] 682 port_id = args["port_id"]
683 port_ap_id = Lingo2World.static_logic.objects.ports[port_id].ap_id
593 worldports = {port_id} 684 worldports = {port_id}
594 685
595 # Also check the reverse port if it's a two-way connection. 686 # Also check the reverse port if it's a two-way connection.
596 port_pairings = manager.client_ctx.slot_data["port_pairings"] 687 port_pairings = manager.client_ctx.slot_data["port_pairings"]
597 if str(port_id) in port_pairings and port_pairings.get(str(port_pairings[str(port_id)]), None) == port_id: 688 if str(port_ap_id) in port_pairings and\
598 worldports.add(port_pairings[str(port_id)]) 689 port_pairings.get(str(port_pairings[str(port_ap_id)]), None) == port_ap_id:
690 worldports.add(Lingo2World.static_logic.port_id_by_ap_id[port_pairings[str(port_ap_id)]])
599 691
600 updates = manager.update_worldports(worldports) 692 updates = manager.update_worldports(worldports)
601 if len(updates) > 0: 693 if len(updates) > 0:
@@ -616,6 +708,10 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict):
616 updates = manager.update_latches({args["door"]}) 708 updates = manager.update_latches({args["door"]})
617 if len(updates) > 0: 709 if len(updates) > 0:
618 async_start(manager.client_ctx.update_latches(updates), name="client update latches") 710 async_start(manager.client_ctx.update_latches(updates), name="client update latches")
711 elif cmd == "IgnoreLocation":
712 async_start(manager.client_ctx.add_ignored_location(args["id"]), name="client ignore loc")
713 elif cmd == "UnignoreLocation":
714 async_start(manager.client_ctx.remove_ignored_location(args["id"]), name="client unignore loc")
619 elif cmd == "Quit": 715 elif cmd == "Quit":
620 manager.client_ctx.exit_event.set() 716 manager.client_ctx.exit_event.set()
621 717
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 1d68e4a..3ee8f38 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -299,8 +299,7 @@ class Lingo2PlayerLogic:
299 if door.map_id not in self.shuffled_maps: 299 if door.map_id not in self.shuffled_maps:
300 continue 300 continue
301 301
302 if door.type in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE, 302 if door.type in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
303 data_pb2.DoorType.LEGACY_LOCATION]:
304 continue 303 continue
305 304
306 if door.id in self.item_by_door: 305 if door.id in self.item_by_door:
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 702f30b..8a84111 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -15,6 +15,9 @@ class Lingo2StaticLogic:
15 15
16 letter_weights: dict[str, int] 16 letter_weights: dict[str, int]
17 17
18 door_id_by_ap_id: dict[int, int]
19 port_id_by_ap_id: dict[int, int]
20
18 def __init__(self): 21 def __init__(self):
19 self.item_id_to_name = {} 22 self.item_id_to_name = {}
20 self.location_id_to_name = {} 23 self.location_id_to_name = {}
@@ -31,8 +34,7 @@ class Lingo2StaticLogic:
31 location_name = self.get_door_location_name(door) 34 location_name = self.get_door_location_name(door)
32 self.location_id_to_name[door.ap_id] = location_name 35 self.location_id_to_name[door.ap_id] = location_name
33 36
34 if door.type not in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE, 37 if door.type not in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
35 data_pb2.DoorType.LEGACY_LOCATION]:
36 item_name = self.get_door_item_name(door) 38 item_name = self.get_door_item_name(door)
37 self.item_id_to_name[door.ap_id] = item_name 39 self.item_id_to_name[door.ap_id] = item_name
38 40
@@ -84,6 +86,9 @@ class Lingo2StaticLogic:
84 for letter in panel.answer.upper(): 86 for letter in panel.answer.upper():
85 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1 87 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1
86 88
89 self.door_id_by_ap_id = {door.ap_id: door.id for door in self.objects.doors if door.HasField("ap_id")}
90 self.port_id_by_ap_id = {port.ap_id: port.id for port in self.objects.ports if port.HasField("ap_id")}
91
87 def get_door_item_name(self, door: data_pb2.Door) -> str: 92 def get_door_item_name(self, door: data_pb2.Door) -> str:
88 return f"{self.get_map_object_map_name(door)} - {door.name}" 93 return f"{self.get_map_object_map_name(door)} - {door.name}"
89 94
diff --git a/apworld/tracker.py b/apworld/tracker.py index c65317c..d473af4 100644 --- a/apworld/tracker.py +++ b/apworld/tracker.py
@@ -47,7 +47,10 @@ class Tracker:
47 self.world.create_regions() 47 self.world.create_regions()
48 48
49 if self.world.options.shuffle_worldports: 49 if self.world.options.shuffle_worldports:
50 port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()} 50 port_pairings = {
51 self.world.static_logic.port_id_by_ap_id[int(fp)]: self.world.static_logic.port_id_by_ap_id[int(tp)]
52 for fp, tp in slot_data["port_pairings"].items()
53 }
51 connect_ports_from_ut(port_pairings, self.world) 54 connect_ports_from_ut(port_pairings, self.world)
52 55
53 self.refresh_state() 56 self.refresh_state()