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__.py13
-rw-r--r--apworld/client/allowNumbers.gd10
-rw-r--r--apworld/client/apworld_runtime.gd5
-rw-r--r--apworld/client/client.gd26
-rw-r--r--apworld/client/gamedata.gd10
-rw-r--r--apworld/client/keyHolderResetterListener.gd2
-rw-r--r--apworld/client/keyboard.gd3
-rw-r--r--apworld/client/main.gd11
-rw-r--r--apworld/client/manager.gd49
-rw-r--r--apworld/client/maps/control_center.gd85
-rw-r--r--apworld/client/maps/daedalus.gd85
-rw-r--r--apworld/client/maps/icarus.gd38
-rw-r--r--apworld/client/maps/the_advanced.gd36
-rw-r--r--apworld/client/maps/the_charismatic.gd26
-rw-r--r--apworld/client/maps/the_crystalline.gd34
-rw-r--r--apworld/client/maps/the_entry.gd156
-rw-r--r--apworld/client/maps/the_fuzzy.gd25
-rw-r--r--apworld/client/maps/the_parthenon.gd51
-rw-r--r--apworld/client/maps/the_plaza.gd4
-rw-r--r--apworld/client/maps/the_stellar.gd30
-rw-r--r--apworld/client/maps/the_sun_temple.gd56
-rw-r--r--apworld/client/maps/the_unkempt.gd4
-rw-r--r--apworld/client/maps/the_unyielding.gd5
-rw-r--r--apworld/client/player.gd428
-rw-r--r--apworld/client/source_runtime.gd4
-rw-r--r--apworld/client/textclient.gd102
-rw-r--r--apworld/client/unlockReaderListener.gd46
-rw-r--r--apworld/context.py114
-rw-r--r--apworld/options.py22
-rw-r--r--apworld/player_logic.py46
-rw-r--r--apworld/requirements.txt2
-rw-r--r--apworld/static_logic.py12
-rw-r--r--apworld/tracker.py6
33 files changed, 1078 insertions, 468 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 4ebf845..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:
@@ -132,7 +135,9 @@ class Lingo2World(World):
132 "daedalus_roof_access", 135 "daedalus_roof_access",
133 "enable_gift_maps", 136 "enable_gift_maps",
134 "enable_icarus", 137 "enable_icarus",
138 "endings_requirement",
135 "keyholder_sanity", 139 "keyholder_sanity",
140 "masteries_requirement",
136 "shuffle_control_center_colors", 141 "shuffle_control_center_colors",
137 "shuffle_doors", 142 "shuffle_doors",
138 "shuffle_gallery_paintings", 143 "shuffle_gallery_paintings",
@@ -150,7 +155,11 @@ class Lingo2World(World):
150 } 155 }
151 156
152 if self.options.shuffle_worldports: 157 if self.options.shuffle_worldports:
153 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()}
154 163
155 return slot_data 164 return slot_data
156 165
diff --git a/apworld/client/allowNumbers.gd b/apworld/client/allowNumbers.gd new file mode 100644 index 0000000..d958b50 --- /dev/null +++ b/apworld/client/allowNumbers.gd
@@ -0,0 +1,10 @@
1extends "res://scripts/nodes/allowNumbers.gd"
2
3
4func _readier():
5 var ap = global.get_node("Archipelago")
6 var gamedata = global.get_node("Gamedata")
7
8 var item_id = gamedata.objects.get_special_ids()["Numbers"]
9 if ap.client.getItemAmount(item_id) >= 1:
10 global.allow_numbers = true
diff --git a/apworld/client/apworld_runtime.gd b/apworld/client/apworld_runtime.gd index faf8e0c..03568bf 100644 --- a/apworld/client/apworld_runtime.gd +++ b/apworld/client/apworld_runtime.gd
@@ -15,6 +15,11 @@ func _get_true_path(path):
15 return "lingo2/client/%s" % path 15 return "lingo2/client/%s" % path
16 16
17 17
18func path_exists(path):
19 var true_path = _get_true_path(path)
20 return apworld_reader.file_exists(true_path)
21
22
18func load_script(path): 23func load_script(path):
19 var true_path = _get_true_path(path) 24 var true_path = _get_true_path(path)
20 25
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 9305003..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
@@ -221,7 +225,11 @@ func _get_generated_door_location_name(door):
221 if door.get_type() != SCRIPT_proto.DoorType.STANDARD: 225 if door.get_type() != SCRIPT_proto.DoorType.STANDARD:
222 return null 226 return null
223 227
224 if door.get_keyholders().size() > 0 or door.get_endings().size() > 0 or door.has_complete_at(): 228 if (
229 door.get_keyholders().size() > 0
230 or (door.has_white_ending() and door.get_white_ending())
231 or door.has_complete_at()
232 ):
225 return null 233 return null
226 234
227 if door.get_panels().size() > 4: 235 if door.get_panels().size() > 4:
diff --git a/apworld/client/keyHolderResetterListener.gd b/apworld/client/keyHolderResetterListener.gd index d5300f3..9ab45f9 100644 --- a/apworld/client/keyHolderResetterListener.gd +++ b/apworld/client/keyHolderResetterListener.gd
@@ -6,3 +6,5 @@ func reset():
6 var was_removed = ap.keyboard.reset_keyholders() 6 var was_removed = ap.keyboard.reset_keyholders()
7 if was_removed: 7 if was_removed:
8 sfxPlayer.sfx_play("pickup") 8 sfxPlayer.sfx_play("pickup")
9
10 ap.client.requestSync()
diff --git a/apworld/client/keyboard.gd b/apworld/client/keyboard.gd index a59c4d0..9026c06 100644 --- a/apworld/client/keyboard.gd +++ b/apworld/client/keyboard.gd
@@ -191,9 +191,6 @@ func load_keyholders(map):
191 191
192 192
193func reset_keyholders(): 193func reset_keyholders():
194 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
195 return false
196
197 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty() 194 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
198 195
199 if keyholder_state.has(global.map): 196 if keyholder_state.has(global.map):
diff --git a/apworld/client/main.gd b/apworld/client/main.gd index 1d0df1f..c90d6e7 100644 --- a/apworld/client/main.gd +++ b/apworld/client/main.gd
@@ -36,6 +36,7 @@ func _ready():
36 global.add_child(ap_instance) 36 global.add_child(ap_instance)
37 37
38 # Let's also inject any scripts we need to inject now. 38 # Let's also inject any scripts we need to inject now.
39 installScriptExtension(runtime.load_script("allowNumbers.gd"))
39 installScriptExtension(runtime.load_script("animationListener.gd")) 40 installScriptExtension(runtime.load_script("animationListener.gd"))
40 installScriptExtension(runtime.load_script("collectable.gd")) 41 installScriptExtension(runtime.load_script("collectable.gd"))
41 installScriptExtension(runtime.load_script("door.gd")) 42 installScriptExtension(runtime.load_script("door.gd"))
@@ -50,6 +51,7 @@ func _ready():
50 installScriptExtension(runtime.load_script("saver.gd")) 51 installScriptExtension(runtime.load_script("saver.gd"))
51 installScriptExtension(runtime.load_script("teleport.gd")) 52 installScriptExtension(runtime.load_script("teleport.gd"))
52 installScriptExtension(runtime.load_script("teleportListener.gd")) 53 installScriptExtension(runtime.load_script("teleportListener.gd"))
54 installScriptExtension(runtime.load_script("unlockReaderListener.gd"))
53 installScriptExtension(runtime.load_script("visibilityListener.gd")) 55 installScriptExtension(runtime.load_script("visibilityListener.gd"))
54 installScriptExtension(runtime.load_script("worldport.gd")) 56 installScriptExtension(runtime.load_script("worldport.gd"))
55 installScriptExtension(runtime.load_script("worldportListener.gd")) 57 installScriptExtension(runtime.load_script("worldportListener.gd"))
@@ -83,6 +85,13 @@ func _ready():
83 compass_overlay_instance.SCRIPT_compass = runtime.load_script("compass.gd") 85 compass_overlay_instance.SCRIPT_compass = runtime.load_script("compass.gd")
84 global.add_child(compass_overlay_instance) 86 global.add_child(compass_overlay_instance)
85 87
88 unlocks.data["advanced_mastery"] = ""
89 unlocks.data["charismatic_mastery"] = ""
90 unlocks.data["crystalline_mastery"] = ""
91 unlocks.data["fuzzy_mastery"] = ""
92 unlocks.data["icarus_mastery"] = ""
93 unlocks.data["stellar_mastery"] = ""
94
86 var ap = global.get_node("Archipelago") 95 var ap = global.get_node("Archipelago")
87 var gamedata = global.get_node("Gamedata") 96 var gamedata = global.get_node("Gamedata")
88 ap.ap_connected.connect(connectionSuccessful) 97 ap.ap_connected.connect(connectionSuccessful)
@@ -237,6 +246,7 @@ func startGame():
237 settings.worldport_fades = "never" 246 settings.worldport_fades = "never"
238 247
239 clearResourceCache("res://objects/meshes/gridDoor.tscn") 248 clearResourceCache("res://objects/meshes/gridDoor.tscn")
249 clearResourceCache("res://objects/nodes/allowNumbers.tscn")
240 clearResourceCache("res://objects/nodes/collectable.tscn") 250 clearResourceCache("res://objects/nodes/collectable.tscn")
241 clearResourceCache("res://objects/nodes/door.tscn") 251 clearResourceCache("res://objects/nodes/door.tscn")
242 clearResourceCache("res://objects/nodes/keyHolder.tscn") 252 clearResourceCache("res://objects/nodes/keyHolder.tscn")
@@ -244,6 +254,7 @@ func startGame():
244 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") 254 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
245 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") 255 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
246 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 256 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
257 clearResourceCache("res://objects/nodes/listeners/unlockReaderListener.tscn")
247 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn") 258 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
248 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 259 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
249 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 41ab648..8c981f9 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -29,6 +29,8 @@ 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 = []
33var _map_scripts = {}
32 34
33const kSHUFFLE_LETTERS_VANILLA = 0 35const kSHUFFLE_LETTERS_VANILLA = 0
34const kSHUFFLE_LETTERS_UNLOCKED = 1 36const kSHUFFLE_LETTERS_UNLOCKED = 1
@@ -65,7 +67,9 @@ var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
65var daedalus_roof_access = false 67var daedalus_roof_access = false
66var enable_gift_maps = [] 68var enable_gift_maps = []
67var enable_icarus = false 69var enable_icarus = false
70var endings_requirement = 0
68var keyholder_sanity = false 71var keyholder_sanity = false
72var masteries_requirement = 0
69var port_pairings = {} 73var port_pairings = {}
70var shuffle_control_center_colors = false 74var shuffle_control_center_colors = false
71var shuffle_doors = false 75var shuffle_doors = false
@@ -142,6 +146,8 @@ func _ready():
142 client.hint_received.connect(_process_hint_received) 146 client.hint_received.connect(_process_hint_received)
143 client.accessible_locations_updated.connect(_on_accessible_locations_updated) 147 client.accessible_locations_updated.connect(_on_accessible_locations_updated)
144 client.checked_locations_updated.connect(_on_checked_locations_updated) 148 client.checked_locations_updated.connect(_on_checked_locations_updated)
149 client.ignored_locations_updated.connect(_on_ignored_locations_updated)
150 client.hinted_locations_updated.connect(_on_hinted_locations_updated)
145 client.checked_worldports_updated.connect(_on_checked_worldports_updated) 151 client.checked_worldports_updated.connect(_on_checked_worldports_updated)
146 client.door_latched.connect(_on_door_latched) 152 client.door_latched.connect(_on_door_latched)
147 153
@@ -259,6 +265,9 @@ func _process_item(item, amount):
259 if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]: 265 if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]:
260 update_job_well_done_sign() 266 update_job_well_done_sign()
261 267
268 if item_id == gamedata.objects.get_special_ids()["Numbers"] and global.map == "the_fuzzy":
269 global.allow_numbers = true
270
262 # Show a message about the item if it's new. 271 # Show a message about the item if it's new.
263 if int(item["index"]) > _last_new_item: 272 if int(item["index"]) > _last_new_item:
264 _last_new_item = int(item["index"]) 273 _last_new_item = int(item["index"])
@@ -379,6 +388,20 @@ func _on_checked_worldports_updated():
379 textclient_node.update_worldports() 388 textclient_node.update_worldports()
380 389
381 390
391func _on_ignored_locations_updated(locations):
392 _ignored_locations = locations
393
394 var textclient_node = global.get_node("Textclient")
395 if textclient_node != null:
396 textclient_node.update_locations()
397
398
399func _on_hinted_locations_updated():
400 var textclient_node = global.get_node("Textclient")
401 if textclient_node != null:
402 textclient_node.update_locations()
403
404
382func _on_door_latched(door_id): 405func _on_door_latched(door_id):
383 var gamedata = global.get_node("Gamedata") 406 var gamedata = global.get_node("Gamedata")
384 if gamedata.get_door_map_name(door_id) != global.map: 407 if gamedata.get_door_map_name(door_id) != global.map:
@@ -443,7 +466,9 @@ func _client_connected(slot_data):
443 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 466 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
444 enable_gift_maps = slot_data.get("enable_gift_maps", []) 467 enable_gift_maps = slot_data.get("enable_gift_maps", [])
445 enable_icarus = bool(slot_data.get("enable_icarus", false)) 468 enable_icarus = bool(slot_data.get("enable_icarus", false))
469 endings_requirement = int(slot_data.get("endings_requirement", 0))
446 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 470 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
471 masteries_requirement = int(slot_data.get("masteries_requirement", 0))
447 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) 472 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
448 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 473 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
449 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false)) 474 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
@@ -465,7 +490,9 @@ func _client_connected(slot_data):
465 var raw_pp = slot_data.get("port_pairings") 490 var raw_pp = slot_data.get("port_pairings")
466 491
467 for p1 in raw_pp.keys(): 492 for p1 in raw_pp.keys():
468 port_pairings[int(p1)] = int(raw_pp[p1]) 493 port_pairings[gamedata.port_id_by_ap_id[int(p1)]] = gamedata.port_id_by_ap_id[int(
494 raw_pp[p1]
495 )]
469 496
470 # Set up item locks. 497 # Set up item locks.
471 _item_locks = {} 498 _item_locks = {}
@@ -668,3 +695,23 @@ func update_job_well_done_sign():
668 695
669 sign2.get_node("MeshInstance3D").mesh.text = sign2.text 696 sign2.get_node("MeshInstance3D").mesh.text = sign2.text
670 sign3.get_node("MeshInstance3D").mesh.text = sign3.text 697 sign3.get_node("MeshInstance3D").mesh.text = sign3.text
698
699
700func toggle_ignored_location(loc_id):
701 if loc_id in _ignored_locations:
702 client.removeIgnoredLocation(loc_id)
703 else:
704 client.addIgnoredLocation(loc_id)
705
706
707func get_map_script(map_name):
708 if !_map_scripts.has(map_name):
709 var runtime = global.get_node("Runtime")
710 var script_path = "maps/%s.gd" % map_name
711 if runtime.path_exists(script_path):
712 var script = runtime.load_script(script_path)
713 _map_scripts[map_name] = script.new()
714 else:
715 _map_scripts[map_name] = null
716
717 return _map_scripts[map_name]
diff --git a/apworld/client/maps/control_center.gd b/apworld/client/maps/control_center.gd new file mode 100644 index 0000000..de9ae4b --- /dev/null +++ b/apworld/client/maps/control_center.gd
@@ -0,0 +1,85 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Remove the door blocking the trophy case.
5 root.get_node("/root/scene/Components/Doors/entry_18").queue_free()
6
7 # Set up mastery listeners for extra maps.
8 _set_up_mastery_listener(root, "advanced")
9 _set_up_mastery_listener(root, "charismatic")
10 _set_up_mastery_listener(root, "crystalline")
11 _set_up_mastery_listener(root, "fuzzy")
12 _set_up_mastery_listener(root, "icarus")
13 _set_up_mastery_listener(root, "stellar")
14
15 if ap.endings_requirement != 12 or ap.masteries_requirement != 0:
16 # Set up listeners for the potential White Ending requirements.
17 var merging_prefab = preload("res://objects/nodes/listeners/mergingListener.tscn")
18
19 var old_door = root.get_node("/root/scene/Components/Doors/entry_19")
20 var new_door = old_door.duplicate()
21 new_door.name = "entry_19_new"
22 new_door.senders.clear()
23 new_door.senderGroup.clear()
24 new_door.excludeSenders.clear()
25
26 if ap.endings_requirement == 12:
27 new_door.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/Listeners"))
28 elif ap.endings_requirement > 0:
29 if ap.masteries_requirement == 0:
30 new_door.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/Listeners"))
31 new_door.complete_at = ap.endings_requirement
32 else:
33 var endings_merge = merging_prefab.instantiate()
34 endings_merge.name = "EndingsMerge"
35 endings_merge.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/Listeners"))
36 endings_merge.complete_at = ap.endings_requirement
37 root.get_node("/root/scene/Components").add_child.call_deferred(endings_merge)
38 new_door.senders.append(NodePath("/root/scene/Components/EndingsMerge"))
39
40 var max_masteries = 13 + ap.enable_gift_maps.size()
41 if ap.enable_icarus:
42 max_masteries += 1
43
44 if ap.masteries_requirement == max_masteries:
45 new_door.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/MasteryListeners"))
46 new_door.excludeSenders.append(
47 NodePath("/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite")
48 )
49 elif ap.masteries_requirement > 0:
50 if ap.endings_requirement == 0:
51 new_door.senderGroup.append(
52 NodePath("/root/scene/Meshes/Trophies/MasteryListeners")
53 )
54 new_door.excludeSenders.append(
55 NodePath(
56 "/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite"
57 )
58 )
59 new_door.complete_at = ap.masteries_requirement
60 else:
61 var masteries_merge = merging_prefab.instantiate()
62 masteries_merge.name = "MasteriesMerge"
63 masteries_merge.senderGroup.append(
64 NodePath("/root/scene/Meshes/Trophies/MasteryListeners")
65 )
66 masteries_merge.excludeSenders.append(
67 NodePath(
68 "/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite"
69 )
70 )
71 masteries_merge.complete_at = ap.masteries_requirement
72 root.get_node("/root/scene/Components").add_child.call_deferred(masteries_merge)
73 new_door.senders.append(NodePath("/root/scene/Components/MasteriesMerge"))
74
75 old_door.queue_free()
76 root.get_node("/root/scene/Components/Doors").add_child.call_deferred(new_door)
77
78
79func _set_up_mastery_listener(root, name):
80 var prefab = preload("res://objects/nodes/listeners/unlockReaderListener.tscn")
81 var url = prefab.instantiate()
82 url.name = "unlockReaderListenerMastery_%s" % name
83 url.key = "%s_mastery" % name
84 url.value = "unlocked"
85 root.get_node("/root/scene/Meshes/Trophies/MasteryListeners").add_child.call_deferred(url)
diff --git a/apworld/client/maps/daedalus.gd b/apworld/client/maps/daedalus.gd new file mode 100644 index 0000000..5fcf7a5 --- /dev/null +++ b/apworld/client/maps/daedalus.gd
@@ -0,0 +1,85 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Teleport the direction panels when the stairs are there.
5 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
6
7 var dir1 = root.get_node("/root/scene/Panels/Castle Entrance/castle_direction_1")
8 var dir1_tpl = tpl_prefab.instantiate()
9 dir1_tpl.target_path = dir1
10 dir1_tpl.teleport_point = Vector3(59.5, 8, -6.5)
11 dir1_tpl.teleport_rotate = Vector3(-45, 0, 0)
12 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
13 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
14 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
15 dir1.add_child.call_deferred(dir1_tpl)
16
17 var dir2 = root.get_node("/root/scene/Panels/Castle Entrance/castle_direction_2")
18 var dir2_tpl = tpl_prefab.instantiate()
19 dir2_tpl.target_path = dir2
20 dir2_tpl.teleport_point = Vector3(59.5, 8, 6.5)
21 dir2_tpl.teleport_rotate = Vector3(-45, -180, 0)
22 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
23 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
24 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
25 dir2.add_child.call_deferred(dir2_tpl)
26
27 var dir3 = root.get_node("/root/scene/Panels/Castle Entrance/castle_direction_3")
28 var dir3_tpl = tpl_prefab.instantiate()
29 dir3_tpl.target_path = dir3
30 dir3_tpl.teleport_point = Vector3(54, 8, 0)
31 dir3_tpl.teleport_rotate = Vector3(-45, 90, 0)
32 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
33 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
34 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
35 dir3.add_child.call_deferred(dir3_tpl)
36
37 # Block off roof access in Daedalus.
38 if not ap.daedalus_roof_access:
39 _set_up_invis_wall(root, 75.5, 11, -24.5, 1, 10, 49)
40 _set_up_invis_wall(root, 51.5, 11, -17, 16, 10, 1)
41 _set_up_invis_wall(root, 46, 10, -9.5, 1, 10, 10)
42 _set_up_invis_wall(root, 67.5, 11, 17, 16, 10, 1)
43 _set_up_invis_wall(root, 50.5, 11, 14, 10, 10, 1)
44 _set_up_invis_wall(root, 39, 10, 18.5, 1, 10, 22)
45 _set_up_invis_wall(root, 20, 15, 18.5, 1, 10, 16)
46 _set_up_invis_wall(root, 11.5, 15, 3, 32, 10, 1)
47 _set_up_invis_wall(root, 11.5, 16, -20, 14, 20, 1)
48 _set_up_invis_wall(root, 14, 16, -26.5, 1, 20, 4)
49 _set_up_invis_wall(root, 28.5, 20.5, -26.5, 1, 15, 25)
50 _set_up_invis_wall(root, 40.5, 20.5, -11, 30, 15, 1)
51 _set_up_invis_wall(root, 50.5, 15, 5.5, 7, 10, 1)
52 _set_up_invis_wall(root, 83.5, 33.5, 5.5, 1, 7, 11)
53 _set_up_invis_wall(root, 83.5, 33.5, -5.5, 1, 7, 11)
54
55 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
56 var warp_exit = warp_exit_prefab.instantiate()
57 warp_exit.name = "roof_access_blocker_warp_exit"
58 warp_exit.position = Vector3(58, 10, 0)
59 warp_exit.rotation_degrees.y = 90
60 root.get_node("/root/scene").add_child.call_deferred(warp_exit)
61
62 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
63 var warp_enter = warp_enter_prefab.instantiate()
64 warp_enter.target = warp_exit
65 warp_enter.position = Vector3(76.5, 30, 1)
66 warp_enter.scale = Vector3(4, 1.5, 1)
67 warp_enter.rotation_degrees.y = 90
68 root.get_node("/root/scene").add_child.call_deferred(warp_enter)
69
70
71func _set_up_invis_wall(root, x, y, z, sx, sy, sz):
72 var prefab = preload("res://objects/nodes/block.tscn")
73 var newwall = prefab.instantiate()
74 newwall.position.x = x
75 newwall.position.y = y
76 newwall.position.z = z
77 newwall.scale.x = sz
78 newwall.scale.y = sy
79 newwall.scale.z = sx
80 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
81 newwall.visibility_range_end = 3
82 newwall.visibility_range_end_margin = 1
83 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
84 newwall.skeleton = ".."
85 root.get_node("/root/scene").add_child.call_deferred(newwall)
diff --git a/apworld/client/maps/icarus.gd b/apworld/client/maps/icarus.gd new file mode 100644 index 0000000..ad00741 --- /dev/null +++ b/apworld/client/maps/icarus.gd
@@ -0,0 +1,38 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Add the mastery to Icarus.
5 if ap.enable_icarus:
6 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
7 var saver_prefab = preload("res://objects/nodes/saver.tscn")
8 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
9 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
10
11 var mastery = collectable_prefab.instantiate()
12 mastery.name = "collectable"
13 mastery.position = Vector3(0, -2000, 0)
14 mastery.unlock_type = "smiley"
15 mastery.material_override = load("res://assets/materials/gold.material")
16 root.get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
17
18 var tpl = tpl_prefab.instantiate()
19 tpl.teleport_point = Vector3(56.25, 0, -5.5)
20 tpl.teleport_rotate = Vector3(0, 0, 0)
21 tpl.target_path = mastery
22 tpl.name = "Teleport"
23 tpl.senderGroup.append(NodePath("/root/scene/Panels"))
24 tpl.nested = true
25 mastery.add_child.call_deferred(tpl)
26
27 var usl = usl_prefab.instantiate()
28 usl.name = "unlockSetterListenerMastery"
29 usl.key = "icarus_mastery"
30 usl.value = "unlocked"
31 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
32 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
33
34 var saver = saver_prefab.instantiate()
35 saver.name = "saver_collectables"
36 saver.type = "collectables"
37 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
38 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_advanced.gd b/apworld/client/maps/the_advanced.gd new file mode 100644 index 0000000..b41549c --- /dev/null +++ b/apworld/client/maps/the_advanced.gd
@@ -0,0 +1,36 @@
1func on_map_load(root):
2 # Add the mastery to The Advanced.
3 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
4 var saver_prefab = preload("res://objects/nodes/saver.tscn")
5 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
6 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
7
8 var mastery = collectable_prefab.instantiate()
9 mastery.name = "collectable"
10 mastery.position = Vector3(0, -200, -5)
11 mastery.unlock_type = "smiley"
12 mastery.material_override = load("res://assets/materials/gold.material")
13 root.get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
14
15 var tpl = tpl_prefab.instantiate()
16 tpl.teleport_point = Vector3(0, 2, -5)
17 tpl.teleport_rotate = Vector3(0, 0, 0)
18 tpl.target_path = mastery
19 tpl.name = "Teleport"
20 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_29"))
21 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_30"))
22 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
23 mastery.add_child.call_deferred(tpl)
24
25 var usl = usl_prefab.instantiate()
26 usl.name = "unlockSetterListenerMastery"
27 usl.key = "advanced_mastery"
28 usl.value = "unlocked"
29 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
30 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
31
32 var saver = saver_prefab.instantiate()
33 saver.name = "saver_collectables"
34 saver.type = "collectables"
35 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
36 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_charismatic.gd b/apworld/client/maps/the_charismatic.gd new file mode 100644 index 0000000..734001d --- /dev/null +++ b/apworld/client/maps/the_charismatic.gd
@@ -0,0 +1,26 @@
1func on_map_load(root):
2 # Add the mastery to The Charismatic.
3 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
4 var saver_prefab = preload("res://objects/nodes/saver.tscn")
5 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
6
7 var mastery = collectable_prefab.instantiate()
8 mastery.name = "collectable"
9 mastery.position = Vector3(-17, 2, -29)
10 mastery.rotation_degrees = Vector3(0, 45, 0)
11 mastery.unlock_type = "smiley"
12 mastery.material_override = load("res://assets/materials/gold.material")
13 root.get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
14
15 var usl = usl_prefab.instantiate()
16 usl.name = "unlockSetterListenerMastery"
17 usl.key = "charismatic_mastery"
18 usl.value = "unlocked"
19 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
20 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
21
22 var saver = saver_prefab.instantiate()
23 saver.name = "saver_collectables"
24 saver.type = "collectables"
25 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
26 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_crystalline.gd b/apworld/client/maps/the_crystalline.gd new file mode 100644 index 0000000..7d43e78 --- /dev/null +++ b/apworld/client/maps/the_crystalline.gd
@@ -0,0 +1,34 @@
1func on_map_load(root):
2 # Add the mastery to The Crystalline.
3 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
4 var saver_prefab = preload("res://objects/nodes/saver.tscn")
5 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
6 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
7
8 var mastery = collectable_prefab.instantiate()
9 mastery.name = "collectable"
10 mastery.position = Vector3(0, 13, 37)
11 mastery.unlock_type = "smiley"
12 mastery.material_override = load("res://assets/materials/gold.material")
13 root.get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
14
15 var tpl = tpl_prefab.instantiate()
16 tpl.teleport_point = Vector3(0, 11.5, -20)
17 tpl.teleport_rotate = Vector3(0, 0, 180)
18 tpl.target_path = mastery
19 tpl.name = "Teleport"
20 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3"))
21 mastery.add_child.call_deferred(tpl)
22
23 var usl = usl_prefab.instantiate()
24 usl.name = "unlockSetterListenerMastery"
25 usl.key = "crystalline_mastery"
26 usl.value = "unlocked"
27 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
28 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
29
30 var saver = saver_prefab.instantiate()
31 saver.name = "saver_collectables"
32 saver.type = "collectables"
33 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
34 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_entry.gd b/apworld/client/maps/the_entry.gd new file mode 100644 index 0000000..3608bb3 --- /dev/null +++ b/apworld/client/maps/the_entry.gd
@@ -0,0 +1,156 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Remove door behind X1.
5 var door_node = root.get_node("/root/scene/Components/Doors/exit_1")
6 door_node.handleTriggered()
7
8 # Display win condition.
9 var sign_prefab = preload("res://objects/nodes/sign.tscn")
10 var sign1 = sign_prefab.instantiate()
11 sign1.position = Vector3(-7, 5, -15.01)
12 sign1.text = "victory"
13 root.get_node("/root/scene").add_child.call_deferred(sign1)
14
15 var sign2 = sign_prefab.instantiate()
16 sign2.position = Vector3(-7, 4, -15.01)
17 sign2.text = "%s ending" % ap.kEndingNameByVictoryValue.get(ap.victory_condition, "?")
18
19 var sign2_color = ap.kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
20 if sign2_color == "white":
21 sign2_color = "silver"
22
23 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
24 root.get_node("/root/scene").add_child.call_deferred(sign2)
25
26 # Add the gift map entry panel if needed.
27 if not ap.enable_gift_maps.is_empty():
28 var panel_prefab = preload("res://objects/nodes/panel.tscn")
29 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
30 var wpl_prefab = preload("res://objects/nodes/listeners/worldportListener.tscn")
31
32 var giftmap_parent = Node.new()
33 giftmap_parent.name = "GiftMapEntrance"
34 root.get_node("/root/scene/Components").add_child.call_deferred(giftmap_parent)
35
36 var symbolless_player = ""
37 for i in range(ap.client.ap_user.length()):
38 if "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(
39 ap.client.ap_user[i]
40 ):
41 symbolless_player = symbolless_player + ap.client.ap_user[i].to_lower()
42
43 var giftmap_panel = panel_prefab.instantiate()
44 giftmap_panel.name = "Panel"
45 giftmap_panel.position = Vector3(33.5, -190, 5.5)
46 giftmap_panel.rotation_degrees = Vector3(-45, 0, 0)
47 giftmap_panel.clue = "player"
48 giftmap_panel.answer = symbolless_player
49
50 if ap.enable_gift_maps.has("The Advanced"):
51 var icely_panel = panel_prefab.instantiate()
52 icely_panel.name = "IcelyPanel"
53 icely_panel.answer = "icely"
54 icely_panel.position = Vector3(33.5, -200, 5.5)
55 giftmap_panel.proxies.append(NodePath("../IcelyPanel"))
56 giftmap_parent.add_child.call_deferred(icely_panel)
57
58 var icely_wpl = wpl_prefab.instantiate()
59 icely_wpl.name = "IcelyWpl"
60 icely_wpl.exit = "the_advanced"
61 icely_wpl.senders.append(NodePath("../IcelyPanel"))
62 giftmap_parent.add_child.call_deferred(icely_wpl)
63
64 if ap.enable_gift_maps.has("The Charismatic"):
65 var souvey_panel = panel_prefab.instantiate()
66 souvey_panel.name = "SouveyPanel"
67 souvey_panel.answer = "souvey"
68 souvey_panel.position = Vector3(33.5, -210, 5.5)
69 giftmap_panel.proxies.append(NodePath("../SouveyPanel"))
70 giftmap_parent.add_child.call_deferred(souvey_panel)
71
72 var souvey_wpl = wpl_prefab.instantiate()
73 souvey_wpl.name = "SouveyWpl"
74 souvey_wpl.exit = "the_charismatic"
75 souvey_wpl.senders.append(NodePath("../SouveyPanel"))
76 giftmap_parent.add_child.call_deferred(souvey_wpl)
77
78 if ap.enable_gift_maps.has("The Crystalline"):
79 var q_panel = panel_prefab.instantiate()
80 q_panel.name = "QPanel"
81 q_panel.answer = "q"
82 q_panel.position = Vector3(33.5, -220, 5.5)
83 giftmap_panel.proxies.append(NodePath("../QPanel"))
84 giftmap_parent.add_child.call_deferred(q_panel)
85
86 var q_wpl = wpl_prefab.instantiate()
87 q_wpl.name = "QWpl"
88 q_wpl.exit = "the_crystalline"
89 q_wpl.senders.append(NodePath("../QPanel"))
90 giftmap_parent.add_child.call_deferred(q_wpl)
91
92 if ap.enable_gift_maps.has("The Fuzzy"):
93 var gongus_panel = panel_prefab.instantiate()
94 gongus_panel.name = "GongusPanel"
95 gongus_panel.answer = "gongus"
96 gongus_panel.position = Vector3(33.5, -260, 5.5)
97 giftmap_panel.proxies.append(NodePath("../GongusPanel"))
98 giftmap_parent.add_child.call_deferred(gongus_panel)
99
100 var kiwi_panel = panel_prefab.instantiate()
101 kiwi_panel.name = "KiwiPanel"
102 kiwi_panel.answer = "kiwi"
103 kiwi_panel.position = Vector3(33.5, -270, 5.5)
104 giftmap_panel.proxies.append(NodePath("../KiwiPanel"))
105 giftmap_parent.add_child.call_deferred(kiwi_panel)
106
107 var fuzzy_wpl = wpl_prefab.instantiate()
108 fuzzy_wpl.name = "FuzzyWpl"
109 fuzzy_wpl.exit = "the_fuzzy"
110 fuzzy_wpl.senders.append(NodePath("../GongusPanel"))
111 fuzzy_wpl.senders.append(NodePath("../KiwiPanel"))
112 fuzzy_wpl.complete_at = 1
113 giftmap_parent.add_child.call_deferred(fuzzy_wpl)
114
115 if ap.enable_gift_maps.has("The Stellar"):
116 var hatkirby_panel = panel_prefab.instantiate()
117 hatkirby_panel.name = "HatkirbyPanel"
118 hatkirby_panel.answer = "hatkirby"
119 hatkirby_panel.position = Vector3(33.5, -230, 5.5)
120 giftmap_panel.proxies.append(NodePath("../HatkirbyPanel"))
121 giftmap_parent.add_child.call_deferred(hatkirby_panel)
122
123 var kirby_panel = panel_prefab.instantiate()
124 kirby_panel.name = "KirbyPanel"
125 kirby_panel.answer = "kirby"
126 kirby_panel.position = Vector3(33.5, -240, 5.5)
127 giftmap_panel.proxies.append(NodePath("../KirbyPanel"))
128 giftmap_parent.add_child.call_deferred(kirby_panel)
129
130 var star_panel = panel_prefab.instantiate()
131 star_panel.name = "StarPanel"
132 star_panel.answer = "star"
133 star_panel.position = Vector3(33.5, -250, 5.5)
134 giftmap_panel.proxies.append(NodePath("../StarPanel"))
135 giftmap_parent.add_child.call_deferred(star_panel)
136
137 var stellar_wpl = wpl_prefab.instantiate()
138 stellar_wpl.name = "StellarWpl"
139 stellar_wpl.exit = "the_stellar"
140 stellar_wpl.senders.append(NodePath("../HatkirbyPanel"))
141 stellar_wpl.senders.append(NodePath("../KirbyPanel"))
142 stellar_wpl.senders.append(NodePath("../StarPanel"))
143 stellar_wpl.complete_at = 1
144 giftmap_parent.add_child.call_deferred(stellar_wpl)
145
146 giftmap_parent.add_child.call_deferred(giftmap_panel)
147
148 var giftmap_tpl = tpl_prefab.instantiate()
149 giftmap_tpl.name = "PanelTeleporter"
150 giftmap_tpl.teleport_point = Vector3(33.5, 1, 5.5)
151 giftmap_tpl.teleport_rotate = Vector3(-45, 0, 0)
152 giftmap_tpl.target_path = giftmap_panel
153 giftmap_tpl.senders.append(
154 NodePath("/root/scene/Components/Listeners/unlockReaderListenerDoubles")
155 )
156 giftmap_parent.add_child.call_deferred(giftmap_tpl)
diff --git a/apworld/client/maps/the_fuzzy.gd b/apworld/client/maps/the_fuzzy.gd new file mode 100644 index 0000000..269dcee --- /dev/null +++ b/apworld/client/maps/the_fuzzy.gd
@@ -0,0 +1,25 @@
1func on_map_load(root):
2 # Add the mastery to The Fuzzy.
3 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
4 var saver_prefab = preload("res://objects/nodes/saver.tscn")
5 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
6
7 var mastery = collectable_prefab.instantiate()
8 mastery.name = "collectable"
9 mastery.position = Vector3(0, 2, -20)
10 mastery.unlock_type = "smiley"
11 mastery.material_override = load("res://assets/materials/gold.material")
12 root.get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
13
14 var usl = usl_prefab.instantiate()
15 usl.name = "unlockSetterListenerMastery"
16 usl.key = "fuzzy_mastery"
17 usl.value = "unlocked"
18 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
19 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
20
21 var saver = saver_prefab.instantiate()
22 saver.name = "saver_collectables"
23 saver.type = "collectables"
24 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
25 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_parthenon.gd b/apworld/client/maps/the_parthenon.gd new file mode 100644 index 0000000..96510da --- /dev/null +++ b/apworld/client/maps/the_parthenon.gd
@@ -0,0 +1,51 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Add the strict cyan ending validation.
5 if ap.strict_cyan_ending:
6 var panel_prefab = preload("res://objects/nodes/panel.tscn")
7 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
8 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
9
10 var previous_panel = null
11 var next_y = -100
12 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
13 for word in words:
14 var panel = panel_prefab.instantiate()
15 panel.position = Vector3(0, next_y, 0)
16 next_y -= 10
17 panel.clue = word
18 panel.symbol = "."
19 panel.answer = "%s%s" % [word, word]
20 panel.name = "EndCheck_%s" % word
21
22 var tpl = tpl_prefab.instantiate()
23 tpl.teleport_point = Vector3(0, 1, -11)
24 tpl.teleport_rotate = Vector3(-45, 0, 0)
25 tpl.target_path = panel
26 tpl.name = "Teleport"
27
28 if previous_panel == null:
29 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
30 else:
31 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
32
33 var reversing = reverse_prefab.instantiate()
34 reversing.senders.append(NodePath(".."))
35 reversing.name = "Reversing"
36 tpl.senders.append(NodePath("../Reversing"))
37
38 panel.add_child.call_deferred(tpl)
39 panel.add_child.call_deferred(reversing)
40 root.get_node("/root/scene/Panels").add_child.call_deferred(panel)
41
42 previous_panel = panel
43
44 # Duplicate the door that usually waits on the rulers. We can't set the
45 # senders here for some reason so we actually set them in the door ready
46 # function.
47 var entry1 = root.get_node("/root/scene/Components/Doors/entry_1")
48 var entry12 = entry1.duplicate()
49 entry12.name = "spe_entry_1"
50 entry1.get_parent().add_child.call_deferred(entry12)
51 entry1.queue_free()
diff --git a/apworld/client/maps/the_plaza.gd b/apworld/client/maps/the_plaza.gd new file mode 100644 index 0000000..13e002d --- /dev/null +++ b/apworld/client/maps/the_plaza.gd
@@ -0,0 +1,4 @@
1func on_map_load(root):
2 # Move the Plaza RTE trigger outside of the turtle.
3 var rte_trigger = root.get_node("/root/scene/Components/Warps/triggerArea")
4 rte_trigger.position.z = 0
diff --git a/apworld/client/maps/the_stellar.gd b/apworld/client/maps/the_stellar.gd new file mode 100644 index 0000000..d633535 --- /dev/null +++ b/apworld/client/maps/the_stellar.gd
@@ -0,0 +1,30 @@
1func on_map_load(root):
2 # Add the mastery to The Stellar.
3 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
4 var saver_prefab = preload("res://objects/nodes/saver.tscn")
5 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
6
7 var collectables = Node.new()
8 collectables.name = "Collectables"
9
10 var mastery = collectable_prefab.instantiate()
11 mastery.name = "collectable"
12 mastery.position = Vector3(2, 2, -31)
13 mastery.rotation_degrees = Vector3(0, 90, 0)
14 mastery.unlock_type = "smiley"
15 mastery.material_override = load("res://assets/materials/gold.material")
16 collectables.add_child.call_deferred(mastery)
17 root.get_node("/root/scene/Components").add_child.call_deferred(collectables)
18
19 var usl = usl_prefab.instantiate()
20 usl.name = "unlockSetterListenerMastery"
21 usl.key = "stellar_mastery"
22 usl.value = "unlocked"
23 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
24 root.get_node("/root/scene/Components").add_child.call_deferred(usl)
25
26 var saver = saver_prefab.instantiate()
27 saver.name = "saver_collectables"
28 saver.type = "collectables"
29 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
30 root.get_node("/root/scene").add_child.call_deferred(saver)
diff --git a/apworld/client/maps/the_sun_temple.gd b/apworld/client/maps/the_sun_temple.gd new file mode 100644 index 0000000..9804bf8 --- /dev/null +++ b/apworld/client/maps/the_sun_temple.gd
@@ -0,0 +1,56 @@
1func on_map_load(root):
2 var ap = global.get_node("Archipelago")
3
4 # Add the strict purple ending validation.
5 if ap.strict_purple_ending:
6 var panel_prefab = preload("res://objects/nodes/panel.tscn")
7 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
8 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
9
10 var previous_panel = null
11 var next_y = -100
12 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
13 for word in words:
14 var panel = panel_prefab.instantiate()
15 panel.position = Vector3(0, next_y, 0)
16 next_y -= 10
17 panel.clue = word
18 panel.symbol = ""
19 panel.answer = word
20 panel.name = "EndCheck_%s" % word
21
22 var tpl = tpl_prefab.instantiate()
23 tpl.teleport_point = Vector3(0, 1, 0)
24 tpl.teleport_rotate = Vector3(-45, 180, 0)
25 tpl.target_path = panel
26 tpl.name = "Teleport"
27
28 if previous_panel == null:
29 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
30 else:
31 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
32
33 var reversing = reverse_prefab.instantiate()
34 reversing.senders.append(NodePath(".."))
35 reversing.name = "Reversing"
36 tpl.senders.append(NodePath("../Reversing"))
37
38 panel.add_child.call_deferred(tpl)
39 panel.add_child.call_deferred(reversing)
40 root.get_node("/root/scene/Panels").add_child.call_deferred(panel)
41
42 previous_panel = panel
43
44 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
45 # here for some reason so we actually set them in the door ready function.
46 var endplat = root.get_node("/root/scene/Components/Doors/EndPlatform")
47 var endplat2 = endplat.duplicate()
48 endplat2.name = "spe_EndPlatform"
49 endplat.get_parent().add_child.call_deferred(endplat2)
50 endplat.queue_free()
51
52 var entry2 = root.get_node("/root/scene/Components/Doors/entry_2")
53 var entry22 = entry2.duplicate()
54 entry22.name = "spe_entry_2"
55 entry2.get_parent().add_child.call_deferred(entry22)
56 entry2.queue_free()
diff --git a/apworld/client/maps/the_unkempt.gd b/apworld/client/maps/the_unkempt.gd new file mode 100644 index 0000000..c907650 --- /dev/null +++ b/apworld/client/maps/the_unkempt.gd
@@ -0,0 +1,4 @@
1func on_map_load(root):
2 # Prevent the COLOR panel from disappearing.
3 var color_tpl = root.get_node("/root/scene/Panels/Assorted/panel_1/teleportListener")
4 color_tpl.target_path = color_tpl
diff --git a/apworld/client/maps/the_unyielding.gd b/apworld/client/maps/the_unyielding.gd new file mode 100644 index 0000000..a2f8eee --- /dev/null +++ b/apworld/client/maps/the_unyielding.gd
@@ -0,0 +1,5 @@
1func on_map_load(root):
2 # Shrink the painting trigger in The Unyielding.
3 var trigger_area = root.get_node("/root/scene/Components/PaintingUnlocker/triggerArea")
4 trigger_area.position = Vector3(0, 0, -6)
5 trigger_area.scale = Vector3(6, 1, 6)
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 789d1b7..5fac9fd 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -19,404 +19,10 @@ func _ready():
19 19
20 ap.start_batching_locations() 20 ap.start_batching_locations()
21 21
22 # Block off roof access in Daedalus. 22 # Run map-specific initialization.
23 if global.map == "daedalus" and not ap.daedalus_roof_access: 23 var map_script = ap.get_map_script(global.map)
24 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) 24 if map_script != null:
25 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1) 25 map_script.on_map_load(get_tree().get_root())
26 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
27 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
28 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
29 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
30 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
31 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
32 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
33 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
34 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
35 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
36 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
37 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
38 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
39
40 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
41 var warp_exit = warp_exit_prefab.instantiate()
42 warp_exit.name = "roof_access_blocker_warp_exit"
43 warp_exit.position = Vector3(58, 10, 0)
44 warp_exit.rotation_degrees.y = 90
45 get_parent().add_child.call_deferred(warp_exit)
46
47 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
48 var warp_enter = warp_enter_prefab.instantiate()
49 warp_enter.target = warp_exit
50 warp_enter.position = Vector3(76.5, 30, 1)
51 warp_enter.scale = Vector3(4, 1.5, 1)
52 warp_enter.rotation_degrees.y = 90
53 get_parent().add_child.call_deferred(warp_enter)
54
55 if global.map == "the_entry":
56 # Remove door behind X1.
57 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
58 door_node.handleTriggered()
59
60 # Display win condition.
61 var sign_prefab = preload("res://objects/nodes/sign.tscn")
62 var sign1 = sign_prefab.instantiate()
63 sign1.position = Vector3(-7, 5, -15.01)
64 sign1.text = "victory"
65 get_parent().add_child.call_deferred(sign1)
66
67 var sign2 = sign_prefab.instantiate()
68 sign2.position = Vector3(-7, 4, -15.01)
69 sign2.text = "%s ending" % ap.kEndingNameByVictoryValue.get(ap.victory_condition, "?")
70
71 var sign2_color = ap.kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
72 if sign2_color == "white":
73 sign2_color = "silver"
74
75 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
76 get_parent().add_child.call_deferred(sign2)
77
78 # Add the gift map entry panel if needed.
79 if not ap.enable_gift_maps.is_empty():
80 var panel_prefab = preload("res://objects/nodes/panel.tscn")
81 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
82 var wpl_prefab = preload("res://objects/nodes/listeners/worldportListener.tscn")
83
84 var giftmap_parent = Node.new()
85 giftmap_parent.name = "GiftMapEntrance"
86 get_node("/root/scene/Components").add_child.call_deferred(giftmap_parent)
87
88 var symbolless_player = ""
89 for i in range(ap.client.ap_user.length()):
90 if "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(
91 ap.client.ap_user[i]
92 ):
93 symbolless_player = symbolless_player + ap.client.ap_user[i].to_lower()
94
95 var giftmap_panel = panel_prefab.instantiate()
96 giftmap_panel.name = "Panel"
97 giftmap_panel.position = Vector3(33.5, -190, 5.5)
98 giftmap_panel.rotation_degrees = Vector3(-45, 0, 0)
99 giftmap_panel.clue = "player"
100 giftmap_panel.answer = symbolless_player
101
102 if ap.enable_gift_maps.has("The Advanced"):
103 var icely_panel = panel_prefab.instantiate()
104 icely_panel.name = "IcelyPanel"
105 icely_panel.answer = "icely"
106 icely_panel.position = Vector3(33.5, -200, 5.5)
107 giftmap_panel.proxies.append(NodePath("../IcelyPanel"))
108 giftmap_parent.add_child.call_deferred(icely_panel)
109
110 var icely_wpl = wpl_prefab.instantiate()
111 icely_wpl.name = "IcelyWpl"
112 icely_wpl.exit = "the_advanced"
113 icely_wpl.senders.append(NodePath("../IcelyPanel"))
114 giftmap_parent.add_child.call_deferred(icely_wpl)
115
116 if ap.enable_gift_maps.has("The Charismatic"):
117 var souvey_panel = panel_prefab.instantiate()
118 souvey_panel.name = "SouveyPanel"
119 souvey_panel.answer = "souvey"
120 souvey_panel.position = Vector3(33.5, -210, 5.5)
121 giftmap_panel.proxies.append(NodePath("../SouveyPanel"))
122 giftmap_parent.add_child.call_deferred(souvey_panel)
123
124 var souvey_wpl = wpl_prefab.instantiate()
125 souvey_wpl.name = "SouveyWpl"
126 souvey_wpl.exit = "the_charismatic"
127 souvey_wpl.senders.append(NodePath("../SouveyPanel"))
128 giftmap_parent.add_child.call_deferred(souvey_wpl)
129
130 if ap.enable_gift_maps.has("The Crystalline"):
131 var q_panel = panel_prefab.instantiate()
132 q_panel.name = "QPanel"
133 q_panel.answer = "q"
134 q_panel.position = Vector3(33.5, -220, 5.5)
135 giftmap_panel.proxies.append(NodePath("../QPanel"))
136 giftmap_parent.add_child.call_deferred(q_panel)
137
138 var q_wpl = wpl_prefab.instantiate()
139 q_wpl.name = "QWpl"
140 q_wpl.exit = "the_crystalline"
141 q_wpl.senders.append(NodePath("../QPanel"))
142 giftmap_parent.add_child.call_deferred(q_wpl)
143
144 if ap.enable_gift_maps.has("The Stellar"):
145 var hatkirby_panel = panel_prefab.instantiate()
146 hatkirby_panel.name = "HatkirbyPanel"
147 hatkirby_panel.answer = "hatkirby"
148 hatkirby_panel.position = Vector3(33.5, -230, 5.5)
149 giftmap_panel.proxies.append(NodePath("../HatkirbyPanel"))
150 giftmap_parent.add_child.call_deferred(hatkirby_panel)
151
152 var kirby_panel = panel_prefab.instantiate()
153 kirby_panel.name = "KirbyPanel"
154 kirby_panel.answer = "kirby"
155 kirby_panel.position = Vector3(33.5, -240, 5.5)
156 giftmap_panel.proxies.append(NodePath("../KirbyPanel"))
157 giftmap_parent.add_child.call_deferred(kirby_panel)
158
159 var star_panel = panel_prefab.instantiate()
160 star_panel.name = "StarPanel"
161 star_panel.answer = "star"
162 star_panel.position = Vector3(33.5, -250, 5.5)
163 giftmap_panel.proxies.append(NodePath("../StarPanel"))
164 giftmap_parent.add_child.call_deferred(star_panel)
165
166 var stellar_wpl = wpl_prefab.instantiate()
167 stellar_wpl.name = "StellarWpl"
168 stellar_wpl.exit = "the_stellar"
169 stellar_wpl.senders.append(NodePath("../HatkirbyPanel"))
170 stellar_wpl.senders.append(NodePath("../KirbyPanel"))
171 stellar_wpl.senders.append(NodePath("../StarPanel"))
172 stellar_wpl.complete_at = 1
173 giftmap_parent.add_child.call_deferred(stellar_wpl)
174
175 giftmap_parent.add_child.call_deferred(giftmap_panel)
176
177 var giftmap_tpl = tpl_prefab.instantiate()
178 giftmap_tpl.name = "PanelTeleporter"
179 giftmap_tpl.teleport_point = Vector3(33.5, 1, 5.5)
180 giftmap_tpl.teleport_rotate = Vector3(-45, 0, 0)
181 giftmap_tpl.target_path = giftmap_panel
182 giftmap_tpl.senders.append(
183 NodePath("/root/scene/Components/Listeners/unlockReaderListenerDoubles")
184 )
185 giftmap_parent.add_child.call_deferred(giftmap_tpl)
186
187 # Add the strict purple ending validation.
188 if global.map == "the_sun_temple" and ap.strict_purple_ending:
189 var panel_prefab = preload("res://objects/nodes/panel.tscn")
190 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
191 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
192
193 var previous_panel = null
194 var next_y = -100
195 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
196 for word in words:
197 var panel = panel_prefab.instantiate()
198 panel.position = Vector3(0, next_y, 0)
199 next_y -= 10
200 panel.clue = word
201 panel.symbol = ""
202 panel.answer = word
203 panel.name = "EndCheck_%s" % word
204
205 var tpl = tpl_prefab.instantiate()
206 tpl.teleport_point = Vector3(0, 1, 0)
207 tpl.teleport_rotate = Vector3(-45, 180, 0)
208 tpl.target_path = panel
209 tpl.name = "Teleport"
210
211 if previous_panel == null:
212 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
213 else:
214 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
215
216 var reversing = reverse_prefab.instantiate()
217 reversing.senders.append(NodePath(".."))
218 reversing.name = "Reversing"
219 tpl.senders.append(NodePath("../Reversing"))
220
221 panel.add_child.call_deferred(tpl)
222 panel.add_child.call_deferred(reversing)
223 get_parent().get_node("Panels").add_child.call_deferred(panel)
224
225 previous_panel = panel
226
227 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
228 # here for some reason so we actually set them in the door ready function.
229 var endplat = get_node("/root/scene/Components/Doors/EndPlatform")
230 var endplat2 = endplat.duplicate()
231 endplat2.name = "spe_EndPlatform"
232 endplat.get_parent().add_child.call_deferred(endplat2)
233 endplat.queue_free()
234
235 var entry2 = get_node("/root/scene/Components/Doors/entry_2")
236 var entry22 = entry2.duplicate()
237 entry22.name = "spe_entry_2"
238 entry2.get_parent().add_child.call_deferred(entry22)
239 entry2.queue_free()
240
241 # Add the strict cyan ending validation.
242 if global.map == "the_parthenon" and ap.strict_cyan_ending:
243 var panel_prefab = preload("res://objects/nodes/panel.tscn")
244 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
245 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
246
247 var previous_panel = null
248 var next_y = -100
249 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
250 for word in words:
251 var panel = panel_prefab.instantiate()
252 panel.position = Vector3(0, next_y, 0)
253 next_y -= 10
254 panel.clue = word
255 panel.symbol = "."
256 panel.answer = "%s%s" % [word, word]
257 panel.name = "EndCheck_%s" % word
258
259 var tpl = tpl_prefab.instantiate()
260 tpl.teleport_point = Vector3(0, 1, -11)
261 tpl.teleport_rotate = Vector3(-45, 0, 0)
262 tpl.target_path = panel
263 tpl.name = "Teleport"
264
265 if previous_panel == null:
266 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
267 else:
268 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
269
270 var reversing = reverse_prefab.instantiate()
271 reversing.senders.append(NodePath(".."))
272 reversing.name = "Reversing"
273 tpl.senders.append(NodePath("../Reversing"))
274
275 panel.add_child.call_deferred(tpl)
276 panel.add_child.call_deferred(reversing)
277 get_parent().get_node("Panels").add_child.call_deferred(panel)
278
279 previous_panel = panel
280
281 # Duplicate the door that usually waits on the rulers. We can't set the
282 # senders here for some reason so we actually set them in the door ready
283 # function.
284 var entry1 = get_node("/root/scene/Components/Doors/entry_1")
285 var entry12 = entry1.duplicate()
286 entry12.name = "spe_entry_1"
287 entry1.get_parent().add_child.call_deferred(entry12)
288 entry1.queue_free()
289
290 # Move the Plaza RTE trigger outside of the turtle.
291 if global.map == "the_plaza":
292 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea")
293 rte_trigger.position.z = 0
294
295 # Add the mastery to Icarus.
296 if global.map == "icarus" and ap.enable_icarus:
297 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
298 var saver_prefab = preload("res://objects/nodes/saver.tscn")
299 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
300
301 var mastery = collectable_prefab.instantiate()
302 mastery.name = "collectable"
303 mastery.position = Vector3(0, -2000, 0)
304 mastery.unlock_type = "smiley"
305 mastery.material_override = load("res://assets/materials/gold.material")
306 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
307
308 var tpl = tpl_prefab.instantiate()
309 tpl.teleport_point = Vector3(56.25, 0, -5.5)
310 tpl.teleport_rotate = Vector3(0, 0, 0)
311 tpl.target_path = mastery
312 tpl.name = "Teleport"
313 tpl.senderGroup.append(NodePath("/root/scene/Panels"))
314 tpl.nested = true
315 mastery.add_child.call_deferred(tpl)
316
317 var saver = saver_prefab.instantiate()
318 saver.name = "saver_collectables"
319 saver.type = "collectables"
320 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
321 get_node("/root/scene").add_child.call_deferred(saver)
322
323 # Add the mastery to The Advanced.
324 if global.map == "the_advanced":
325 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
326 var saver_prefab = preload("res://objects/nodes/saver.tscn")
327 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
328
329 var mastery = collectable_prefab.instantiate()
330 mastery.name = "collectable"
331 mastery.position = Vector3(0, -200, -5)
332 mastery.unlock_type = "smiley"
333 mastery.material_override = load("res://assets/materials/gold.material")
334 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
335
336 var tpl = tpl_prefab.instantiate()
337 tpl.teleport_point = Vector3(0, 2, -5)
338 tpl.teleport_rotate = Vector3(0, 0, 0)
339 tpl.target_path = mastery
340 tpl.name = "Teleport"
341 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_29"))
342 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_30"))
343 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
344 mastery.add_child.call_deferred(tpl)
345
346 var saver = saver_prefab.instantiate()
347 saver.name = "saver_collectables"
348 saver.type = "collectables"
349 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
350 get_node("/root/scene").add_child.call_deferred(saver)
351
352 # Add the mastery to The Charismatic.
353 if global.map == "the_charismatic":
354 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
355 var saver_prefab = preload("res://objects/nodes/saver.tscn")
356
357 var mastery = collectable_prefab.instantiate()
358 mastery.name = "collectable"
359 mastery.position = Vector3(-17, 2, -29)
360 mastery.rotation_degrees = Vector3(0, 45, 0)
361 mastery.unlock_type = "smiley"
362 mastery.material_override = load("res://assets/materials/gold.material")
363 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
364
365 var saver = saver_prefab.instantiate()
366 saver.name = "saver_collectables"
367 saver.type = "collectables"
368 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
369 get_node("/root/scene").add_child.call_deferred(saver)
370
371 # Add the mastery to The Crystalline.
372 if global.map == "the_crystalline":
373 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
374 var saver_prefab = preload("res://objects/nodes/saver.tscn")
375 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
376
377 var mastery = collectable_prefab.instantiate()
378 mastery.name = "collectable"
379 mastery.position = Vector3(0, 13, 37)
380 mastery.unlock_type = "smiley"
381 mastery.material_override = load("res://assets/materials/gold.material")
382 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
383
384 var tpl = tpl_prefab.instantiate()
385 tpl.teleport_point = Vector3(0, 11.5, -20)
386 tpl.teleport_rotate = Vector3(0, 0, 180)
387 tpl.target_path = mastery
388 tpl.name = "Teleport"
389 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3"))
390 mastery.add_child.call_deferred(tpl)
391
392 var saver = saver_prefab.instantiate()
393 saver.name = "saver_collectables"
394 saver.type = "collectables"
395 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
396 get_node("/root/scene").add_child.call_deferred(saver)
397
398 # Add the mastery to The Stellar.
399 if global.map == "the_stellar":
400 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
401 var saver_prefab = preload("res://objects/nodes/saver.tscn")
402
403 var collectables = Node.new()
404 collectables.name = "Collectables"
405
406 var mastery = collectable_prefab.instantiate()
407 mastery.name = "collectable"
408 mastery.position = Vector3(2, 2, -31)
409 mastery.rotation_degrees = Vector3(0, 90, 0)
410 mastery.unlock_type = "smiley"
411 mastery.material_override = load("res://assets/materials/gold.material")
412 collectables.add_child.call_deferred(mastery)
413 get_node("/root/scene/Components").add_child.call_deferred(collectables)
414
415 var saver = saver_prefab.instantiate()
416 saver.name = "saver_collectables"
417 saver.type = "collectables"
418 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
419 get_node("/root/scene").add_child.call_deferred(saver)
420 26
421 ap.update_job_well_done_sign() 27 ap.update_job_well_done_sign()
422 28
@@ -430,9 +36,12 @@ func _ready():
430 continue 36 continue
431 37
432 if ( 38 if (
433 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY 39 not (door.has_legacy_location() and door.get_legacy_location())
434 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING 40 and (
435 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR 41 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
42 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
43 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
44 )
436 ): 45 ):
437 continue 46 continue
438 47
@@ -568,22 +177,5 @@ func _ready():
568 ap.stop_batching_locations() 177 ap.stop_batching_locations()
569 178
570 179
571func _set_up_invis_wall(x, y, z, sx, sy, sz):
572 var prefab = preload("res://objects/nodes/block.tscn")
573 var newwall = prefab.instantiate()
574 newwall.position.x = x
575 newwall.position.y = y
576 newwall.position.z = z
577 newwall.scale.x = sz
578 newwall.scale.y = sy
579 newwall.scale.z = sx
580 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
581 newwall.visibility_range_end = 3
582 newwall.visibility_range_end_margin = 1
583 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
584 newwall.skeleton = ".."
585 get_parent().add_child.call_deferred(newwall)
586
587
588func _process(_dt): 180func _process(_dt):
589 compass.update_rotation(global_rotation.y) 181 compass.update_rotation(global_rotation.y)
diff --git a/apworld/client/source_runtime.gd b/apworld/client/source_runtime.gd index 35428ea..146587a 100644 --- a/apworld/client/source_runtime.gd +++ b/apworld/client/source_runtime.gd
@@ -7,6 +7,10 @@ func _init(path):
7 source_path = path 7 source_path = path
8 8
9 9
10func path_exists(path):
11 return FileAccess.file_exists("%s/%s" % [source_path, path])
12
13
10func load_script(path): 14func load_script(path):
11 return ResourceLoader.load("%s/%s" % [source_path, path]) 15 return ResourceLoader.load("%s/%s" % [source_path, path])
12 16
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/options.py b/apworld/options.py index 7577e0c..f687434 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -181,6 +181,26 @@ class VictoryCondition(Choice):
181 option_white_ending = 12 181 option_white_ending = 12
182 182
183 183
184class EndingsRequirement(Range):
185 """The number of endings required to unlock White Ending."""
186 display_name = "Endings Requirement"
187 range_start = 0
188 range_end = 12
189 default = 12
190
191
192class MasteriesRequirement(Range):
193 """The number of masteries required to unlock White Ending.
194
195 There are only 13 masteries in the base game, but some of the other slot options may add more masteries to the
196 world. If the chosen number of masteries is higher than the total in your world, it will be automatically lowered to
197 the maximum."""
198 display_name = "Masteries Requirement"
199 range_start = 0
200 range_end = 19
201 default = 0
202
203
184class TrapPercentage(Range): 204class TrapPercentage(Range):
185 """Replaces junk items with traps, at the specified rate.""" 205 """Replaces junk items with traps, at the specified rate."""
186 display_name = "Trap Percentage" 206 display_name = "Trap Percentage"
@@ -205,4 +225,6 @@ class Lingo2Options(PerGameCommonOptions):
205 strict_purple_ending: StrictPurpleEnding 225 strict_purple_ending: StrictPurpleEnding
206 strict_cyan_ending: StrictCyanEnding 226 strict_cyan_ending: StrictCyanEnding
207 victory_condition: VictoryCondition 227 victory_condition: VictoryCondition
228 endings_requirement: EndingsRequirement
229 masteries_requirement: MasteriesRequirement
208 trap_percentage: TrapPercentage 230 trap_percentage: TrapPercentage
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 67365b7..3ee8f38 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -241,6 +241,8 @@ class Lingo2PlayerLogic:
241 return "The Charismatic" in world.options.enable_gift_maps.value 241 return "The Charismatic" in world.options.enable_gift_maps.value
242 elif game_map.name == "the_crystalline": 242 elif game_map.name == "the_crystalline":
243 return "The Crystalline" in world.options.enable_gift_maps.value 243 return "The Crystalline" in world.options.enable_gift_maps.value
244 elif game_map.name == "the_fuzzy":
245 return "The Fuzzy" in world.options.enable_gift_maps.value
244 elif game_map.name == "the_stellar": 246 elif game_map.name == "the_stellar":
245 return "The Stellar" in world.options.enable_gift_maps.value 247 return "The Stellar" in world.options.enable_gift_maps.value
246 248
@@ -249,6 +251,16 @@ class Lingo2PlayerLogic:
249 self.shuffled_maps = set(game_map.id for game_map in world.static_logic.objects.maps 251 self.shuffled_maps = set(game_map.id for game_map in world.static_logic.objects.maps
250 if should_shuffle_map(game_map)) 252 if should_shuffle_map(game_map))
251 253
254 maximum_masteries = 13 + len(world.options.enable_gift_maps.value)
255 if world.options.enable_icarus:
256 maximum_masteries += 1
257
258 if world.options.masteries_requirement > maximum_masteries:
259 world.options.masteries_requirement.value = maximum_masteries
260
261 if "The Fuzzy" in world.options.enable_gift_maps.value:
262 self.real_items.append("Numbers")
263
252 if self.world.options.shuffle_doors: 264 if self.world.options.shuffle_doors:
253 for progressive in world.static_logic.objects.progressives: 265 for progressive in world.static_logic.objects.progressives:
254 for i in range(0, len(progressive.doors)): 266 for i in range(0, len(progressive.doors)):
@@ -362,24 +374,29 @@ class Lingo2PlayerLogic:
362 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id, 374 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
363 AccessRequirements())) 375 AccessRequirements()))
364 376
377 if world.options.masteries_requirement > 0:
378 event_name = f"{world.static_logic.get_room_object_map_name(mastery)} - Mastery (Collected)"
379 self.event_loc_item_by_room.setdefault(mastery.room_id, {})[event_name] = "Mastery"
380
365 for ending in world.static_logic.objects.endings: 381 for ending in world.static_logic.objects.endings:
366 if world.static_logic.get_room_object_map_id(ending) not in self.shuffled_maps: 382 if world.static_logic.get_room_object_map_id(ending) not in self.shuffled_maps:
367 continue 383 continue
368 384
369 # Don't create a location for your selected ending, and never create a location for White Ending. 385 # Don't create a location for your selected ending. Also don't create a location for White Ending if it's
386 # necessarily in the postgame, i.e. it requires all 12 other endings.
370 if world.options.victory_condition.current_key.removesuffix("_ending").upper() != ending.name\ 387 if world.options.victory_condition.current_key.removesuffix("_ending").upper() != ending.name\
371 and ending.name != "WHITE": 388 and (ending.name != "WHITE" or world.options.endings_requirement < 12):
372 self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, 389 self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id,
373 AccessRequirements())) 390 AccessRequirements()))
374 391
375 event_name = f"{ending.name.capitalize()} Ending (Achieved)"
376 item_name = event_name
377
378 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: 392 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name:
379 item_name = "Victory" 393 event_name = f"{ending.name.capitalize()} Ending (Goal)"
394 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = "Victory"
380 self.goal_room_id = ending.room_id 395 self.goal_room_id = ending.room_id
381 396
382 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name 397 if ending.name != "WHITE":
398 event_name = f"{ending.name.capitalize()} Ending (Achieved)"
399 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = "Ending"
383 400
384 if self.world.options.keyholder_sanity: 401 if self.world.options.keyholder_sanity:
385 for keyholder in world.static_logic.objects.keyholders: 402 for keyholder in world.static_logic.objects.keyholders:
@@ -494,7 +511,6 @@ class Lingo2PlayerLogic:
494 reqs.possibilities.append(panel_reqs) 511 reqs.possibilities.append(panel_reqs)
495 512
496 if door.HasField("control_center_color"): 513 if door.HasField("control_center_color"):
497 # TODO: Logic for ensuring two CC states aren't needed at once.
498 reqs.rooms.add("Control Center - Main Area") 514 reqs.rooms.add("Control Center - Main Area")
499 self.add_solution_reqs(reqs, door.control_center_color) 515 self.add_solution_reqs(reqs, door.control_center_color)
500 516
@@ -520,13 +536,12 @@ class Lingo2PlayerLogic:
520 for room in door.rooms: 536 for room in door.rooms:
521 reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) 537 reqs.rooms.add(self.world.static_logic.get_room_region_name(room))
522 538
523 for ending_id in door.endings: 539 if door.white_ending:
524 ending = self.world.static_logic.objects.endings[ending_id] 540 if self.world.options.endings_requirement > 0:
541 reqs.progressives["Ending"] = self.world.options.endings_requirement.value
525 542
526 if self.world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: 543 if self.world.options.masteries_requirement > 0:
527 reqs.items.add("Victory") 544 reqs.progressives["Mastery"] = self.world.options.masteries_requirement.value
528 else:
529 reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)")
530 545
531 for sub_door_id in door.doors: 546 for sub_door_id in door.doors:
532 sub_reqs = self.get_door_open_reqs(sub_door_id) 547 sub_reqs = self.get_door_open_reqs(sub_door_id)
@@ -588,3 +603,6 @@ class Lingo2PlayerLogic:
588 603
589 if needed > 0: 604 if needed > 0:
590 reqs.letters[l] = max(reqs.letters.get(l, 0), needed) 605 reqs.letters[l] = max(reqs.letters.get(l, 0), needed)
606
607 if any(l.isnumeric() for l in solution):
608 reqs.items.add("Numbers")
diff --git a/apworld/requirements.txt b/apworld/requirements.txt index dbc395b..b0c79cc 100644 --- a/apworld/requirements.txt +++ b/apworld/requirements.txt
@@ -1 +1 @@
protobuf==3.20.3 protobuf
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 2546007..715178e 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 = {}
@@ -68,6 +71,7 @@ class Lingo2StaticLogic:
68 self.location_name_groups.setdefault("Keyholders", []).append(location_name) 71 self.location_name_groups.setdefault("Keyholders", []).append(location_name)
69 72
70 self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" 73 self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done"
74 self.item_id_to_name[self.objects.special_ids["Numbers"]] = "Numbers"
71 75
72 for symbol_name in SYMBOL_ITEMS.values(): 76 for symbol_name in SYMBOL_ITEMS.values():
73 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name 77 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name
@@ -80,7 +84,11 @@ class Lingo2StaticLogic:
80 84
81 for panel in self.objects.panels: 85 for panel in self.objects.panels:
82 for letter in panel.answer.upper(): 86 for letter in panel.answer.upper():
83 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1 87 if letter.isalpha():
88 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1
89
90 self.door_id_by_ap_id = {door.ap_id: door.id for door in self.objects.doors if door.HasField("ap_id")}
91 self.port_id_by_ap_id = {port.ap_id: port.id for port in self.objects.ports if port.HasField("ap_id")}
84 92
85 def get_door_item_name(self, door: data_pb2.Door) -> str: 93 def get_door_item_name(self, door: data_pb2.Door) -> str:
86 return f"{self.get_map_object_map_name(door)} - {door.name}" 94 return f"{self.get_map_object_map_name(door)} - {door.name}"
@@ -105,7 +113,7 @@ class Lingo2StaticLogic:
105 if door.type != data_pb2.DoorType.STANDARD: 113 if door.type != data_pb2.DoorType.STANDARD:
106 return None 114 return None
107 115
108 if len(door.keyholders) > 0 or len(door.endings) > 0 or door.HasField("complete_at"): 116 if len(door.keyholders) > 0 or door.white_ending or door.HasField("complete_at"):
109 return None 117 return None
110 118
111 if len(door.panels) > 4: 119 if len(door.panels) > 4:
diff --git a/apworld/tracker.py b/apworld/tracker.py index c65317c..a84c3f8 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()
@@ -93,6 +96,7 @@ class Tracker:
93 PLAYER_NUM), prevent_sweep=True) 96 PLAYER_NUM), prevent_sweep=True)
94 97
95 self.state.sweep_for_advancements() 98 self.state.sweep_for_advancements()
99 self.state.update_reachable_regions(PLAYER_NUM)
96 100
97 self.accessible_locations = set() 101 self.accessible_locations = set()
98 self.accessible_worldports = set() 102 self.accessible_worldports = set()