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/gamedata.gd4
-rw-r--r--apworld/client/manager.gd4
-rw-r--r--apworld/client/player.gd113
-rw-r--r--apworld/context.py31
-rw-r--r--apworld/player_logic.py3
-rw-r--r--apworld/static_logic.py9
-rw-r--r--apworld/tracker.py5
8 files changed, 131 insertions, 49 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/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/manager.gd b/apworld/client/manager.gd index aa07559..727d17a 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -472,7 +472,9 @@ func _client_connected(slot_data):
472 var raw_pp = slot_data.get("port_pairings") 472 var raw_pp = slot_data.get("port_pairings")
473 473
474 for p1 in raw_pp.keys(): 474 for p1 in raw_pp.keys():
475 port_pairings[int(p1)] = int(raw_pp[p1]) 475 port_pairings[gamedata.port_id_by_ap_id[int(p1)]] = gamedata.port_id_by_ap_id[int(
476 raw_pp[p1]
477 )]
476 478
477 # Set up item locks. 479 # Set up item locks.
478 _item_locks = {} 480 _item_locks = {}
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 712a59b..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.
@@ -586,6 +620,12 @@ func _ready():
586 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables")) 620 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
587 get_node("/root/scene").add_child.call_deferred(saver) 621 get_node("/root/scene").add_child.call_deferred(saver)
588 622
623 # Shrink the painting trigger in The Unyielding.
624 if global.map == "the_unyielding":
625 var trigger_area = get_node("/root/scene/Components/PaintingUnlocker/triggerArea")
626 trigger_area.position = Vector3(0, 0, -6)
627 trigger_area.scale = Vector3(6, 1, 6)
628
589 ap.update_job_well_done_sign() 629 ap.update_job_well_done_sign()
590 630
591 # Set up door locations. 631 # Set up door locations.
@@ -598,9 +638,12 @@ func _ready():
598 continue 638 continue
599 639
600 if ( 640 if (
601 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY 641 not (door.has_legacy_location() and door.get_legacy_location())
602 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING 642 and (
603 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 )
604 ): 647 ):
605 continue 648 continue
606 649
diff --git a/apworld/context.py b/apworld/context.py index e2d80cd..52b04ae 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"
@@ -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)
@@ -227,6 +238,7 @@ class Lingo2GameContext:
227 238
228 async_start(self.send_msgs([msg]), name="update keyboard") 239 async_start(self.send_msgs([msg]), name="update keyboard")
229 240
241 # Input should be real IDs, not AP IDs
230 def send_update_worldports(self, worldports): 242 def send_update_worldports(self, worldports):
231 if self.server is None: 243 if self.server is None:
232 return 244 return
@@ -441,13 +453,15 @@ class Lingo2ClientContext(CommonContext):
441 elif args["key"] == self.get_datastorage_key("keyboard2"): 453 elif args["key"] == self.get_datastorage_key("keyboard2"):
442 self.handle_keyboard_update(2, args) 454 self.handle_keyboard_update(2, args)
443 elif args["key"] == self.get_datastorage_key("worldports"): 455 elif args["key"] == self.get_datastorage_key("worldports"):
444 updates = self.manager.update_worldports(set(args["value"])) 456 port_ids = set(Lingo2World.static_logic.port_id_by_ap_id[ap_id] for ap_id in args["value"])
457 updates = self.manager.update_worldports(port_ids)
445 if len(updates) > 0: 458 if len(updates) > 0:
446 self.manager.game_ctx.send_update_worldports(updates) 459 self.manager.game_ctx.send_update_worldports(updates)
447 elif args["key"] == self.victory_data_storage_key: 460 elif args["key"] == self.victory_data_storage_key:
448 self.handle_status_update(args["value"]) 461 self.handle_status_update(args["value"])
449 elif args["key"] == self.get_datastorage_key("latches"): 462 elif args["key"] == self.get_datastorage_key("latches"):
450 updates = self.manager.update_latches(set(args["value"])) 463 door_ids = set(Lingo2World.static_logic.door_id_by_ap_id[ap_id] for ap_id in args["value"])
464 updates = self.manager.update_latches(door_ids)
451 if len(updates) > 0: 465 if len(updates) > 0:
452 self.manager.game_ctx.send_update_latches(updates) 466 self.manager.game_ctx.send_update_latches(updates)
453 467
@@ -515,14 +529,16 @@ class Lingo2ClientContext(CommonContext):
515 if len(updates) > 0: 529 if len(updates) > 0:
516 self.manager.game_ctx.send_update_keyboard(updates) 530 self.manager.game_ctx.send_update_keyboard(updates)
517 531
532 # Input should be real IDs, not AP IDs
518 async def update_worldports(self, updates: set[int]): 533 async def update_worldports(self, updates: set[int]):
534 port_ap_ids = [Lingo2World.static_logic.objects.ports[port_id].ap_id for port_id in updates]
519 await self.send_msgs([{ 535 await self.send_msgs([{
520 "cmd": "Set", 536 "cmd": "Set",
521 "key": self.get_datastorage_key("worldports"), 537 "key": self.get_datastorage_key("worldports"),
522 "want_reply": True, 538 "want_reply": True,
523 "operations": [{ 539 "operations": [{
524 "operation": "update", 540 "operation": "update",
525 "value": updates 541 "value": port_ap_ids
526 }] 542 }]
527 }]) 543 }])
528 544
@@ -532,13 +548,14 @@ class Lingo2ClientContext(CommonContext):
532 self.manager.game_ctx.send_accessible_locations() 548 self.manager.game_ctx.send_accessible_locations()
533 549
534 async def update_latches(self, updates: set[int]): 550 async def update_latches(self, updates: set[int]):
551 door_ap_ids = [Lingo2World.static_logic.objects.doors[door_id].ap_id for door_id in updates]
535 await self.send_msgs([{ 552 await self.send_msgs([{
536 "cmd": "Set", 553 "cmd": "Set",
537 "key": self.get_datastorage_key("latches"), 554 "key": self.get_datastorage_key("latches"),
538 "want_reply": True, 555 "want_reply": True,
539 "operations": [{ 556 "operations": [{
540 "operation": "update", 557 "operation": "update",
541 "value": updates 558 "value": door_ap_ids
542 }] 559 }]
543 }]) 560 }])
544 561
@@ -590,12 +607,14 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict):
590 async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard") 607 async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard")
591 elif cmd == "CheckWorldport": 608 elif cmd == "CheckWorldport":
592 port_id = args["port_id"] 609 port_id = args["port_id"]
610 port_ap_id = Lingo2World.static_logic.objects.ports[port_id].ap_id
593 worldports = {port_id} 611 worldports = {port_id}
594 612
595 # Also check the reverse port if it's a two-way connection. 613 # Also check the reverse port if it's a two-way connection.
596 port_pairings = manager.client_ctx.slot_data["port_pairings"] 614 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: 615 if str(port_ap_id) in port_pairings and\
598 worldports.add(port_pairings[str(port_id)]) 616 port_pairings.get(str(port_pairings[str(port_ap_id)]), None) == port_ap_id:
617 worldports.add(Lingo2World.static_logic.port_id_by_ap_id[port_pairings[str(port_ap_id)]])
599 618
600 updates = manager.update_worldports(worldports) 619 updates = manager.update_worldports(worldports)
601 if len(updates) > 0: 620 if len(updates) > 0:
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()