about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md102
-rw-r--r--apworld/client/apworld_runtime.gd5
-rw-r--r--apworld/client/client.gd26
-rw-r--r--apworld/client/keyHolderResetterListener.gd2
-rw-r--r--apworld/client/keyboard.gd3
-rw-r--r--apworld/client/main.gd2
-rw-r--r--apworld/client/manager.gd38
-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.gd636
-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.py83
-rw-r--r--apworld/requirements.txt2
-rw-r--r--apworld/static_logic.py3
-rw-r--r--apworld/tracker.py1
-rw-r--r--data/door_groups.txtpb4
-rw-r--r--data/maps/daedalus/doors.txtpb2
-rw-r--r--data/maps/the_entry/rooms/Four Rooms Entrance.txtpb2
-rw-r--r--data/maps/the_extravagant/rooms/Engine Room.txtpb2
-rw-r--r--data/maps/the_owl/doors.txtpb6
-rw-r--r--data/maps/the_owl/rooms/Connected Area.txtpb1
-rw-r--r--data/maps/the_symbolic/doors.txtpb38
-rw-r--r--data/maps/the_unkempt/doors.txtpb1
-rw-r--r--data/maps/the_unkempt/rooms/Right Area.txtpb1
-rw-r--r--data/metadata.txtpb6
39 files changed, 1085 insertions, 668 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 76890e9..723befc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,107 @@
1# lingo2-archipelago Releases 1# lingo2-archipelago Releases
2 2
3## v8.0.3 - 2025-12-18
4
5- Fixed the "Set changed during iteration" error some users were experiencing.
6- Fixed issue where generation would try to create anti-number traps when The
7 Fuzzy was enabled.
8- Using the key return now acts like interacting with a keyholder, and should
9 force-update your keyboard in case it gets out of sync.
10
11Compatibility notes:
12
13- This client should be completely compatible with worlds generated on v8.0.2.
14- See the v8.0.2 release notes for additional compatibility notes regarding
15 earlier versions.
16
17Download:
18[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.3/lingo2.apworld)<br/>
19Template YAML:
20[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.3/Lingo%202.yaml)<br/>
21Source: [v8.0.3](https://code.fourisland.com/lingo2-archipelago/tag/?h=v8.0.3)
22
23## v8.0.2 - 2025-11-11
24
25- Fixed a logic issue in The Symbolic where Poetry Room and Whirred Room were
26 not part of the requirements to access the mastery.
27- Fixed the issue where the Poetry Room location would be sent early.
28- The Daedalus Hedges Tower door is now latched (it will stay open after being
29 opened once).
30
31Compatibility notes:
32
33- This client should overall be compatible with worlds generated on v8.0.0 and
34 v8.0.1. The tracker will now correctly show when The Symbolic - Mastery is
35 possible to get in-game. However, this does not match the generator's logic in
36 earlier versions of the apworld, so you may end up in a position where you are
37 blocked because the mastery is impossible to get. Playing on another version
38 of the client will not fix this problem, nor is there an easy way to get the
39 mastery out-of-logic, and you may need to use server commands to send the
40 location early.
41- See the v8.0.1 release notes for additional compatibility notes regarding
42 earlier versions.
43
44Download:
45[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.2/lingo2.apworld)<br/>
46Template YAML:
47[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.2/Lingo%202.yaml)<br/>
48Source: [v8.0.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=v8.0.2)
49
50## v8.0.1 - 2025-11-05
51
52- Fixed issue where The Unkempt - COLOR would disappear when it became logical
53 to solve it. It is now always present, and does not logically require cyan
54 doors or the orange control center door.
55- Fixed issue where you would be expected to solve The Owl - COLOR while it is
56 invisible. It is now considered a cyan door.
57
58Compatibility notes:
59
60- This client should overall be compatible with worlds generated on v8.0.0. The
61 tracker will show the new logic for The Unkempt - COLOR, which reflects when
62 it is solvable in game, but may place it in an earlier sphere than it was in
63 the original generation. Conversely, The Owl - COLOR is now more restricted in
64 the new logic, so you may end up in a situation where you are required to
65 solve it but the tracker does not realize it is in logic. However, the panel
66 is simply invisible when you don't have cyan doors, meaning it is easy to
67 solve it early if necessary.
68
69Download:
70[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.1/lingo2.apworld)<br/>
71Template YAML:
72[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.1/Lingo%202.yaml)<br/>
73Source: [v8.0.1](https://code.fourisland.com/lingo2-archipelago/tag/?h=v8.0.1)
74
75## v8.0.0 - 2025-11-02
76
77- ~50 new locations were added, such that almost every panel in the game is now
78 part of either a location or a connection. Some existing locations were also
79 modified or removed to make this cleaner. The only panels that are not part of
80 any location or connection are the ones in rooms that you are explicitly not
81 supposed to solve (e.g. the letter rooms in Daedalus where you are only
82 supposed to solve the panels indicated by the ceiling).
83- Multiworld state is now saved in a way that should increase compatibility when
84 playing on a client that is a different version than the apworld that
85 generated the world, going forward. Complete compatibility is not guaranteed,
86 but this fixes some of the glaring issues. Also note that this client is _not_
87 compatible with worlds generated on v7.x.x.
88- Hinted locations are now prioritized in the tracker, and are displayed in a
89 different color.
90- Locations can now be ignored in the tracker, which removes them from the
91 overlay and puts them at the bottom of the tab in the text client.
92- Fixed the Yellow Ending door not opening properly when gallery paintings are
93 shuffled.
94- The trigger for the gallery painting in The Unyielding is now smaller, so that
95 it matches the logical requirement.
96- The DIRECTION panels near the Castle in Daedalus are now moved when the roof
97 access stairs are present, so that you don't lose access to them.
98
99Download:
100[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.0/lingo2.apworld)<br/>
101Template YAML:
102[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v8.0.0/Lingo%202.yaml)<br/>
103Source: [v8.0.0](https://code.fourisland.com/lingo2-archipelago/tag/?h=v8.0.0)
104
3## v7.2.0 - 2025-10-25 105## v7.2.0 - 2025-10-25
4 106
5- Doors that rely on keyholders or the control center color panel are now 107- Doors that rely on keyholders or the control center color panel are now
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/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 a543678..c90d6e7 100644 --- a/apworld/client/main.gd +++ b/apworld/client/main.gd
@@ -51,6 +51,7 @@ func _ready():
51 installScriptExtension(runtime.load_script("saver.gd")) 51 installScriptExtension(runtime.load_script("saver.gd"))
52 installScriptExtension(runtime.load_script("teleport.gd")) 52 installScriptExtension(runtime.load_script("teleport.gd"))
53 installScriptExtension(runtime.load_script("teleportListener.gd")) 53 installScriptExtension(runtime.load_script("teleportListener.gd"))
54 installScriptExtension(runtime.load_script("unlockReaderListener.gd"))
54 installScriptExtension(runtime.load_script("visibilityListener.gd")) 55 installScriptExtension(runtime.load_script("visibilityListener.gd"))
55 installScriptExtension(runtime.load_script("worldport.gd")) 56 installScriptExtension(runtime.load_script("worldport.gd"))
56 installScriptExtension(runtime.load_script("worldportListener.gd")) 57 installScriptExtension(runtime.load_script("worldportListener.gd"))
@@ -253,6 +254,7 @@ func startGame():
253 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn") 254 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
254 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") 255 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
255 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 256 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
257 clearResourceCache("res://objects/nodes/listeners/unlockReaderListener.tscn")
256 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn") 258 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
257 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 259 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
258 clearResourceCache("res://objects/nodes/panel.tscn") 260 clearResourceCache("res://objects/nodes/panel.tscn")
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 727d17a..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
@@ -144,6 +146,8 @@ func _ready():
144 client.hint_received.connect(_process_hint_received) 146 client.hint_received.connect(_process_hint_received)
145 client.accessible_locations_updated.connect(_on_accessible_locations_updated) 147 client.accessible_locations_updated.connect(_on_accessible_locations_updated)
146 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)
147 client.checked_worldports_updated.connect(_on_checked_worldports_updated) 151 client.checked_worldports_updated.connect(_on_checked_worldports_updated)
148 client.door_latched.connect(_on_door_latched) 152 client.door_latched.connect(_on_door_latched)
149 153
@@ -384,6 +388,20 @@ func _on_checked_worldports_updated():
384 textclient_node.update_worldports() 388 textclient_node.update_worldports()
385 389
386 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
387func _on_door_latched(door_id): 405func _on_door_latched(door_id):
388 var gamedata = global.get_node("Gamedata") 406 var gamedata = global.get_node("Gamedata")
389 if gamedata.get_door_map_name(door_id) != global.map: 407 if gamedata.get_door_map_name(door_id) != global.map:
@@ -677,3 +695,23 @@ func update_job_well_done_sign():
677 695
678 sign2.get_node("MeshInstance3D").mesh.text = sign2.text 696 sign2.get_node("MeshInstance3D").mesh.text = sign2.text
679 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 65bf54e..5fac9fd 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -19,612 +19,10 @@ func _ready():
19 19
20 ap.start_batching_locations() 20 ap.start_batching_locations()
21 21
22 if global.map == "control_center": 22 # Run map-specific initialization.
23 get_node("/root/scene/Components/Doors/entry_18").queue_free() 23 var map_script = ap.get_map_script(global.map)
24 24 if map_script != null:
25 _set_up_mastery_listener("advanced") 25 map_script.on_map_load(get_tree().get_root())
26 _set_up_mastery_listener("charismatic")
27 _set_up_mastery_listener("crystalline")
28 _set_up_mastery_listener("fuzzy")
29 _set_up_mastery_listener("icarus")
30 _set_up_mastery_listener("stellar")
31
32 if ap.endings_requirement != 12 or ap.masteries_requirement != 0:
33 # Set up listeners for the potential White Ending requirements.
34 var merging_prefab = preload("res://objects/nodes/listeners/mergingListener.tscn")
35
36 var old_door = get_node("/root/scene/Components/Doors/entry_19")
37 var new_door = old_door.duplicate()
38 new_door.name = "entry_19_new"
39 new_door.senders.clear()
40 new_door.senderGroup.clear()
41 new_door.excludeSenders.clear()
42
43 if ap.endings_requirement == 12:
44 new_door.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/Listeners"))
45 elif ap.endings_requirement > 0:
46 if ap.masteries_requirement == 0:
47 new_door.senderGroup.append(NodePath("/root/scene/Meshes/Trophies/Listeners"))
48 new_door.complete_at = ap.endings_requirement
49 else:
50 var endings_merge = merging_prefab.instantiate()
51 endings_merge.name = "EndingsMerge"
52 endings_merge.senderGroup.append(
53 NodePath("/root/scene/Meshes/Trophies/Listeners")
54 )
55 endings_merge.complete_at = ap.endings_requirement
56 get_node("/root/scene/Components").add_child.call_deferred(endings_merge)
57 new_door.senders.append(NodePath("/root/scene/Components/EndingsMerge"))
58
59 var max_masteries = 13 + ap.enable_gift_maps.size()
60 if ap.enable_icarus:
61 max_masteries += 1
62
63 if ap.masteries_requirement == max_masteries:
64 new_door.senderGroup.append(
65 NodePath("/root/scene/Meshes/Trophies/MasteryListeners")
66 )
67 new_door.excludeSenders.append(
68 NodePath(
69 "/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite"
70 )
71 )
72 elif ap.masteries_requirement > 0:
73 if ap.endings_requirement == 0:
74 new_door.senderGroup.append(
75 NodePath("/root/scene/Meshes/Trophies/MasteryListeners")
76 )
77 new_door.excludeSenders.append(
78 NodePath(
79 "/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite"
80 )
81 )
82 new_door.complete_at = ap.masteries_requirement
83 else:
84 var masteries_merge = merging_prefab.instantiate()
85 masteries_merge.name = "MasteriesMerge"
86 masteries_merge.senderGroup.append(
87 NodePath("/root/scene/Meshes/Trophies/MasteryListeners")
88 )
89 masteries_merge.excludeSenders.append(
90 NodePath(
91 "/root/scene/Meshes/Trophies/MasteryListeners/unlockReaderListenerWhite"
92 )
93 )
94 masteries_merge.complete_at = ap.masteries_requirement
95 get_node("/root/scene/Components").add_child.call_deferred(masteries_merge)
96 new_door.senders.append(NodePath("/root/scene/Components/MasteriesMerge"))
97
98 old_door.queue_free()
99 get_node("/root/scene/Components/Doors").add_child.call_deferred(new_door)
100
101 if global.map == "daedalus":
102 # Teleport the direction panels when the stairs are there.
103 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
104
105 var dir1 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_1")
106 var dir1_tpl = tpl_prefab.instantiate()
107 dir1_tpl.target_path = dir1
108 dir1_tpl.teleport_point = Vector3(59.5, 8, -6.5)
109 dir1_tpl.teleport_rotate = Vector3(-45, 0, 0)
110 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
111 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
112 dir1_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
113 dir1.add_child.call_deferred(dir1_tpl)
114
115 var dir2 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_2")
116 var dir2_tpl = tpl_prefab.instantiate()
117 dir2_tpl.target_path = dir2
118 dir2_tpl.teleport_point = Vector3(59.5, 8, 6.5)
119 dir2_tpl.teleport_rotate = Vector3(-45, -180, 0)
120 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
121 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
122 dir2_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
123 dir2.add_child.call_deferred(dir2_tpl)
124
125 var dir3 = get_node("/root/scene/Panels/Castle Entrance/castle_direction_3")
126 var dir3_tpl = tpl_prefab.instantiate()
127 dir3_tpl.target_path = dir3
128 dir3_tpl.teleport_point = Vector3(54, 8, 0)
129 dir3_tpl.teleport_rotate = Vector3(-45, 90, 0)
130 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_south"))
131 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_north"))
132 dir3_tpl.senders.append(NodePath("/root/scene/Panels/Castle Entrance/castle_west"))
133 dir3.add_child.call_deferred(dir3_tpl)
134
135 # Block off roof access in Daedalus.
136 if not ap.daedalus_roof_access:
137 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
138 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
139 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
140 _set_up_invis_wall(67.5, 11, 17, 16, 10, 1)
141 _set_up_invis_wall(50.5, 11, 14, 10, 10, 1)
142 _set_up_invis_wall(39, 10, 18.5, 1, 10, 22)
143 _set_up_invis_wall(20, 15, 18.5, 1, 10, 16)
144 _set_up_invis_wall(11.5, 15, 3, 32, 10, 1)
145 _set_up_invis_wall(11.5, 16, -20, 14, 20, 1)
146 _set_up_invis_wall(14, 16, -26.5, 1, 20, 4)
147 _set_up_invis_wall(28.5, 20.5, -26.5, 1, 15, 25)
148 _set_up_invis_wall(40.5, 20.5, -11, 30, 15, 1)
149 _set_up_invis_wall(50.5, 15, 5.5, 7, 10, 1)
150 _set_up_invis_wall(83.5, 33.5, 5.5, 1, 7, 11)
151 _set_up_invis_wall(83.5, 33.5, -5.5, 1, 7, 11)
152
153 var warp_exit_prefab = preload("res://objects/nodes/exit.tscn")
154 var warp_exit = warp_exit_prefab.instantiate()
155 warp_exit.name = "roof_access_blocker_warp_exit"
156 warp_exit.position = Vector3(58, 10, 0)
157 warp_exit.rotation_degrees.y = 90
158 get_parent().add_child.call_deferred(warp_exit)
159
160 var warp_enter_prefab = preload("res://objects/nodes/teleportAuto.tscn")
161 var warp_enter = warp_enter_prefab.instantiate()
162 warp_enter.target = warp_exit
163 warp_enter.position = Vector3(76.5, 30, 1)
164 warp_enter.scale = Vector3(4, 1.5, 1)
165 warp_enter.rotation_degrees.y = 90
166 get_parent().add_child.call_deferred(warp_enter)
167
168 if global.map == "the_entry":
169 # Remove door behind X1.
170 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
171 door_node.handleTriggered()
172
173 # Display win condition.
174 var sign_prefab = preload("res://objects/nodes/sign.tscn")
175 var sign1 = sign_prefab.instantiate()
176 sign1.position = Vector3(-7, 5, -15.01)
177 sign1.text = "victory"
178 get_parent().add_child.call_deferred(sign1)
179
180 var sign2 = sign_prefab.instantiate()
181 sign2.position = Vector3(-7, 4, -15.01)
182 sign2.text = "%s ending" % ap.kEndingNameByVictoryValue.get(ap.victory_condition, "?")
183
184 var sign2_color = ap.kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
185 if sign2_color == "white":
186 sign2_color = "silver"
187
188 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
189 get_parent().add_child.call_deferred(sign2)
190
191 # Add the gift map entry panel if needed.
192 if not ap.enable_gift_maps.is_empty():
193 var panel_prefab = preload("res://objects/nodes/panel.tscn")
194 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
195 var wpl_prefab = preload("res://objects/nodes/listeners/worldportListener.tscn")
196
197 var giftmap_parent = Node.new()
198 giftmap_parent.name = "GiftMapEntrance"
199 get_node("/root/scene/Components").add_child.call_deferred(giftmap_parent)
200
201 var symbolless_player = ""
202 for i in range(ap.client.ap_user.length()):
203 if "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(
204 ap.client.ap_user[i]
205 ):
206 symbolless_player = symbolless_player + ap.client.ap_user[i].to_lower()
207
208 var giftmap_panel = panel_prefab.instantiate()
209 giftmap_panel.name = "Panel"
210 giftmap_panel.position = Vector3(33.5, -190, 5.5)
211 giftmap_panel.rotation_degrees = Vector3(-45, 0, 0)
212 giftmap_panel.clue = "player"
213 giftmap_panel.answer = symbolless_player
214
215 if ap.enable_gift_maps.has("The Advanced"):
216 var icely_panel = panel_prefab.instantiate()
217 icely_panel.name = "IcelyPanel"
218 icely_panel.answer = "icely"
219 icely_panel.position = Vector3(33.5, -200, 5.5)
220 giftmap_panel.proxies.append(NodePath("../IcelyPanel"))
221 giftmap_parent.add_child.call_deferred(icely_panel)
222
223 var icely_wpl = wpl_prefab.instantiate()
224 icely_wpl.name = "IcelyWpl"
225 icely_wpl.exit = "the_advanced"
226 icely_wpl.senders.append(NodePath("../IcelyPanel"))
227 giftmap_parent.add_child.call_deferred(icely_wpl)
228
229 if ap.enable_gift_maps.has("The Charismatic"):
230 var souvey_panel = panel_prefab.instantiate()
231 souvey_panel.name = "SouveyPanel"
232 souvey_panel.answer = "souvey"
233 souvey_panel.position = Vector3(33.5, -210, 5.5)
234 giftmap_panel.proxies.append(NodePath("../SouveyPanel"))
235 giftmap_parent.add_child.call_deferred(souvey_panel)
236
237 var souvey_wpl = wpl_prefab.instantiate()
238 souvey_wpl.name = "SouveyWpl"
239 souvey_wpl.exit = "the_charismatic"
240 souvey_wpl.senders.append(NodePath("../SouveyPanel"))
241 giftmap_parent.add_child.call_deferred(souvey_wpl)
242
243 if ap.enable_gift_maps.has("The Crystalline"):
244 var q_panel = panel_prefab.instantiate()
245 q_panel.name = "QPanel"
246 q_panel.answer = "q"
247 q_panel.position = Vector3(33.5, -220, 5.5)
248 giftmap_panel.proxies.append(NodePath("../QPanel"))
249 giftmap_parent.add_child.call_deferred(q_panel)
250
251 var q_wpl = wpl_prefab.instantiate()
252 q_wpl.name = "QWpl"
253 q_wpl.exit = "the_crystalline"
254 q_wpl.senders.append(NodePath("../QPanel"))
255 giftmap_parent.add_child.call_deferred(q_wpl)
256
257 if ap.enable_gift_maps.has("The Fuzzy"):
258 var gongus_panel = panel_prefab.instantiate()
259 gongus_panel.name = "GongusPanel"
260 gongus_panel.answer = "gongus"
261 gongus_panel.position = Vector3(33.5, -260, 5.5)
262 giftmap_panel.proxies.append(NodePath("../GongusPanel"))
263 giftmap_parent.add_child.call_deferred(gongus_panel)
264
265 var kiwi_panel = panel_prefab.instantiate()
266 kiwi_panel.name = "KiwiPanel"
267 kiwi_panel.answer = "kiwi"
268 kiwi_panel.position = Vector3(33.5, -270, 5.5)
269 giftmap_panel.proxies.append(NodePath("../KiwiPanel"))
270 giftmap_parent.add_child.call_deferred(kiwi_panel)
271
272 var fuzzy_wpl = wpl_prefab.instantiate()
273 fuzzy_wpl.name = "FuzzyWpl"
274 fuzzy_wpl.exit = "the_fuzzy"
275 fuzzy_wpl.senders.append(NodePath("../GongusPanel"))
276 fuzzy_wpl.senders.append(NodePath("../KiwiPanel"))
277 fuzzy_wpl.complete_at = 1
278 giftmap_parent.add_child.call_deferred(fuzzy_wpl)
279
280 if ap.enable_gift_maps.has("The Stellar"):
281 var hatkirby_panel = panel_prefab.instantiate()
282 hatkirby_panel.name = "HatkirbyPanel"
283 hatkirby_panel.answer = "hatkirby"
284 hatkirby_panel.position = Vector3(33.5, -230, 5.5)
285 giftmap_panel.proxies.append(NodePath("../HatkirbyPanel"))
286 giftmap_parent.add_child.call_deferred(hatkirby_panel)
287
288 var kirby_panel = panel_prefab.instantiate()
289 kirby_panel.name = "KirbyPanel"
290 kirby_panel.answer = "kirby"
291 kirby_panel.position = Vector3(33.5, -240, 5.5)
292 giftmap_panel.proxies.append(NodePath("../KirbyPanel"))
293 giftmap_parent.add_child.call_deferred(kirby_panel)
294
295 var star_panel = panel_prefab.instantiate()
296 star_panel.name = "StarPanel"
297 star_panel.answer = "star"
298 star_panel.position = Vector3(33.5, -250, 5.5)
299 giftmap_panel.proxies.append(NodePath("../StarPanel"))
300 giftmap_parent.add_child.call_deferred(star_panel)
301
302 var stellar_wpl = wpl_prefab.instantiate()
303 stellar_wpl.name = "StellarWpl"
304 stellar_wpl.exit = "the_stellar"
305 stellar_wpl.senders.append(NodePath("../HatkirbyPanel"))
306 stellar_wpl.senders.append(NodePath("../KirbyPanel"))
307 stellar_wpl.senders.append(NodePath("../StarPanel"))
308 stellar_wpl.complete_at = 1
309 giftmap_parent.add_child.call_deferred(stellar_wpl)
310
311 giftmap_parent.add_child.call_deferred(giftmap_panel)
312
313 var giftmap_tpl = tpl_prefab.instantiate()
314 giftmap_tpl.name = "PanelTeleporter"
315 giftmap_tpl.teleport_point = Vector3(33.5, 1, 5.5)
316 giftmap_tpl.teleport_rotate = Vector3(-45, 0, 0)
317 giftmap_tpl.target_path = giftmap_panel
318 giftmap_tpl.senders.append(
319 NodePath("/root/scene/Components/Listeners/unlockReaderListenerDoubles")
320 )
321 giftmap_parent.add_child.call_deferred(giftmap_tpl)
322
323 # Add the strict purple ending validation.
324 if global.map == "the_sun_temple" and ap.strict_purple_ending:
325 var panel_prefab = preload("res://objects/nodes/panel.tscn")
326 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
327 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
328
329 var previous_panel = null
330 var next_y = -100
331 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
332 for word in words:
333 var panel = panel_prefab.instantiate()
334 panel.position = Vector3(0, next_y, 0)
335 next_y -= 10
336 panel.clue = word
337 panel.symbol = ""
338 panel.answer = word
339 panel.name = "EndCheck_%s" % word
340
341 var tpl = tpl_prefab.instantiate()
342 tpl.teleport_point = Vector3(0, 1, 0)
343 tpl.teleport_rotate = Vector3(-45, 180, 0)
344 tpl.target_path = panel
345 tpl.name = "Teleport"
346
347 if previous_panel == null:
348 tpl.senders.append(NodePath("/root/scene/Panels/End/panel_24"))
349 else:
350 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
351
352 var reversing = reverse_prefab.instantiate()
353 reversing.senders.append(NodePath(".."))
354 reversing.name = "Reversing"
355 tpl.senders.append(NodePath("../Reversing"))
356
357 panel.add_child.call_deferred(tpl)
358 panel.add_child.call_deferred(reversing)
359 get_parent().get_node("Panels").add_child.call_deferred(panel)
360
361 previous_panel = panel
362
363 # Duplicate the doors that usually wait on EQUINOX. We can't set the senders
364 # here for some reason so we actually set them in the door ready function.
365 var endplat = get_node("/root/scene/Components/Doors/EndPlatform")
366 var endplat2 = endplat.duplicate()
367 endplat2.name = "spe_EndPlatform"
368 endplat.get_parent().add_child.call_deferred(endplat2)
369 endplat.queue_free()
370
371 var entry2 = get_node("/root/scene/Components/Doors/entry_2")
372 var entry22 = entry2.duplicate()
373 entry22.name = "spe_entry_2"
374 entry2.get_parent().add_child.call_deferred(entry22)
375 entry2.queue_free()
376
377 # Add the strict cyan ending validation.
378 if global.map == "the_parthenon" and ap.strict_cyan_ending:
379 var panel_prefab = preload("res://objects/nodes/panel.tscn")
380 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
381 var reverse_prefab = preload("res://objects/nodes/listeners/reversingListener.tscn")
382
383 var previous_panel = null
384 var next_y = -100
385 var words = ["quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
386 for word in words:
387 var panel = panel_prefab.instantiate()
388 panel.position = Vector3(0, next_y, 0)
389 next_y -= 10
390 panel.clue = word
391 panel.symbol = "."
392 panel.answer = "%s%s" % [word, word]
393 panel.name = "EndCheck_%s" % word
394
395 var tpl = tpl_prefab.instantiate()
396 tpl.teleport_point = Vector3(0, 1, -11)
397 tpl.teleport_rotate = Vector3(-45, 0, 0)
398 tpl.target_path = panel
399 tpl.name = "Teleport"
400
401 if previous_panel == null:
402 tpl.senderGroup.append(NodePath("/root/scene/Panels/Rulers"))
403 else:
404 tpl.senders.append(NodePath("../../%s" % previous_panel.name))
405
406 var reversing = reverse_prefab.instantiate()
407 reversing.senders.append(NodePath(".."))
408 reversing.name = "Reversing"
409 tpl.senders.append(NodePath("../Reversing"))
410
411 panel.add_child.call_deferred(tpl)
412 panel.add_child.call_deferred(reversing)
413 get_parent().get_node("Panels").add_child.call_deferred(panel)
414
415 previous_panel = panel
416
417 # Duplicate the door that usually waits on the rulers. We can't set the
418 # senders here for some reason so we actually set them in the door ready
419 # function.
420 var entry1 = get_node("/root/scene/Components/Doors/entry_1")
421 var entry12 = entry1.duplicate()
422 entry12.name = "spe_entry_1"
423 entry1.get_parent().add_child.call_deferred(entry12)
424 entry1.queue_free()
425
426 # Move the Plaza RTE trigger outside of the turtle.
427 if global.map == "the_plaza":
428 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea")
429 rte_trigger.position.z = 0
430
431 # Add the mastery to Icarus.
432 if global.map == "icarus" and ap.enable_icarus:
433 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
434 var saver_prefab = preload("res://objects/nodes/saver.tscn")
435 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
436 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
437
438 var mastery = collectable_prefab.instantiate()
439 mastery.name = "collectable"
440 mastery.position = Vector3(0, -2000, 0)
441 mastery.unlock_type = "smiley"
442 mastery.material_override = load("res://assets/materials/gold.material")
443 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
444
445 var tpl = tpl_prefab.instantiate()
446 tpl.teleport_point = Vector3(56.25, 0, -5.5)
447 tpl.teleport_rotate = Vector3(0, 0, 0)
448 tpl.target_path = mastery
449 tpl.name = "Teleport"
450 tpl.senderGroup.append(NodePath("/root/scene/Panels"))
451 tpl.nested = true
452 mastery.add_child.call_deferred(tpl)
453
454 var usl = usl_prefab.instantiate()
455 usl.name = "unlockSetterListenerMastery"
456 usl.key = "icarus_mastery"
457 usl.value = "unlocked"
458 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
459 get_node("/root/scene/Components").add_child.call_deferred(usl)
460
461 var saver = saver_prefab.instantiate()
462 saver.name = "saver_collectables"
463 saver.type = "collectables"
464 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
465 get_node("/root/scene").add_child.call_deferred(saver)
466
467 # Add the mastery to The Advanced.
468 if global.map == "the_advanced":
469 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
470 var saver_prefab = preload("res://objects/nodes/saver.tscn")
471 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
472 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
473
474 var mastery = collectable_prefab.instantiate()
475 mastery.name = "collectable"
476 mastery.position = Vector3(0, -200, -5)
477 mastery.unlock_type = "smiley"
478 mastery.material_override = load("res://assets/materials/gold.material")
479 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
480
481 var tpl = tpl_prefab.instantiate()
482 tpl.teleport_point = Vector3(0, 2, -5)
483 tpl.teleport_rotate = Vector3(0, 0, 0)
484 tpl.target_path = mastery
485 tpl.name = "Teleport"
486 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_29"))
487 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_30"))
488 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
489 mastery.add_child.call_deferred(tpl)
490
491 var usl = usl_prefab.instantiate()
492 usl.name = "unlockSetterListenerMastery"
493 usl.key = "advanced_mastery"
494 usl.value = "unlocked"
495 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
496 get_node("/root/scene/Components").add_child.call_deferred(usl)
497
498 var saver = saver_prefab.instantiate()
499 saver.name = "saver_collectables"
500 saver.type = "collectables"
501 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
502 get_node("/root/scene").add_child.call_deferred(saver)
503
504 # Add the mastery to The Charismatic.
505 if global.map == "the_charismatic":
506 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
507 var saver_prefab = preload("res://objects/nodes/saver.tscn")
508 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
509
510 var mastery = collectable_prefab.instantiate()
511 mastery.name = "collectable"
512 mastery.position = Vector3(-17, 2, -29)
513 mastery.rotation_degrees = Vector3(0, 45, 0)
514 mastery.unlock_type = "smiley"
515 mastery.material_override = load("res://assets/materials/gold.material")
516 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
517
518 var usl = usl_prefab.instantiate()
519 usl.name = "unlockSetterListenerMastery"
520 usl.key = "charismatic_mastery"
521 usl.value = "unlocked"
522 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
523 get_node("/root/scene/Components").add_child.call_deferred(usl)
524
525 var saver = saver_prefab.instantiate()
526 saver.name = "saver_collectables"
527 saver.type = "collectables"
528 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
529 get_node("/root/scene").add_child.call_deferred(saver)
530
531 # Add the mastery to The Crystalline.
532 if global.map == "the_crystalline":
533 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
534 var saver_prefab = preload("res://objects/nodes/saver.tscn")
535 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
536 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
537
538 var mastery = collectable_prefab.instantiate()
539 mastery.name = "collectable"
540 mastery.position = Vector3(0, 13, 37)
541 mastery.unlock_type = "smiley"
542 mastery.material_override = load("res://assets/materials/gold.material")
543 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
544
545 var tpl = tpl_prefab.instantiate()
546 tpl.teleport_point = Vector3(0, 11.5, -20)
547 tpl.teleport_rotate = Vector3(0, 0, 180)
548 tpl.target_path = mastery
549 tpl.name = "Teleport"
550 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3"))
551 mastery.add_child.call_deferred(tpl)
552
553 var usl = usl_prefab.instantiate()
554 usl.name = "unlockSetterListenerMastery"
555 usl.key = "crystalline_mastery"
556 usl.value = "unlocked"
557 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
558 get_node("/root/scene/Components").add_child.call_deferred(usl)
559
560 var saver = saver_prefab.instantiate()
561 saver.name = "saver_collectables"
562 saver.type = "collectables"
563 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
564 get_node("/root/scene").add_child.call_deferred(saver)
565
566 # Add the mastery to The Fuzzy.
567 if global.map == "the_fuzzy":
568 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
569 var saver_prefab = preload("res://objects/nodes/saver.tscn")
570 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
571
572 var mastery = collectable_prefab.instantiate()
573 mastery.name = "collectable"
574 mastery.position = Vector3(0, 2, -20)
575 mastery.unlock_type = "smiley"
576 mastery.material_override = load("res://assets/materials/gold.material")
577 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
578
579 var usl = usl_prefab.instantiate()
580 usl.name = "unlockSetterListenerMastery"
581 usl.key = "fuzzy_mastery"
582 usl.value = "unlocked"
583 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
584 get_node("/root/scene/Components").add_child.call_deferred(usl)
585
586 var saver = saver_prefab.instantiate()
587 saver.name = "saver_collectables"
588 saver.type = "collectables"
589 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
590 get_node("/root/scene").add_child.call_deferred(saver)
591
592 # Add the mastery to The Stellar.
593 if global.map == "the_stellar":
594 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
595 var saver_prefab = preload("res://objects/nodes/saver.tscn")
596 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
597
598 var collectables = Node.new()
599 collectables.name = "Collectables"
600
601 var mastery = collectable_prefab.instantiate()
602 mastery.name = "collectable"
603 mastery.position = Vector3(2, 2, -31)
604 mastery.rotation_degrees = Vector3(0, 90, 0)
605 mastery.unlock_type = "smiley"
606 mastery.material_override = load("res://assets/materials/gold.material")
607 collectables.add_child.call_deferred(mastery)
608 get_node("/root/scene/Components").add_child.call_deferred(collectables)
609
610 var usl = usl_prefab.instantiate()
611 usl.name = "unlockSetterListenerMastery"
612 usl.key = "stellar_mastery"
613 usl.value = "unlocked"
614 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
615 get_node("/root/scene/Components").add_child.call_deferred(usl)
616
617 var saver = saver_prefab.instantiate()
618 saver.name = "saver_collectables"
619 saver.type = "collectables"
620 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
621 get_node("/root/scene").add_child.call_deferred(saver)
622
623 # Shrink the painting trigger in The Unyielding.
624 if global.map == "the_unyielding":
625 var trigger_area = get_node("/root/scene/Components/PaintingUnlocker/triggerArea")
626 trigger_area.position = Vector3(0, 0, -6)
627 trigger_area.scale = Vector3(6, 1, 6)
628 26
629 ap.update_job_well_done_sign() 27 ap.update_job_well_done_sign()
630 28
@@ -779,31 +177,5 @@ func _ready():
779 ap.stop_batching_locations() 177 ap.stop_batching_locations()
780 178
781 179
782func _set_up_invis_wall(x, y, z, sx, sy, sz):
783 var prefab = preload("res://objects/nodes/block.tscn")
784 var newwall = prefab.instantiate()
785 newwall.position.x = x
786 newwall.position.y = y
787 newwall.position.z = z
788 newwall.scale.x = sz
789 newwall.scale.y = sy
790 newwall.scale.z = sx
791 newwall.set_surface_override_material(0, preload("res://assets/materials/blackMatte.material"))
792 newwall.visibility_range_end = 3
793 newwall.visibility_range_end_margin = 1
794 newwall.visibility_range_fade_mode = RenderingServer.VISIBILITY_RANGE_FADE_SELF
795 newwall.skeleton = ".."
796 get_parent().add_child.call_deferred(newwall)
797
798
799func _set_up_mastery_listener(name):
800 var prefab = preload("res://objects/nodes/listeners/unlockReaderListener.tscn")
801 var url = prefab.instantiate()
802 url.name = "unlockReaderListenerMastery_%s" % name
803 url.key = "%s_mastery" % name
804 url.value = "unlocked"
805 get_node("/root/scene/Meshes/Trophies/MasteryListeners").add_child.call_deferred(url)
806
807
808func _process(_dt): 180func _process(_dt):
809 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 52b04ae..86392f9 100644 --- a/apworld/context.py +++ b/apworld/context.py
@@ -49,6 +49,7 @@ class Lingo2Manager:
49 worldports: set[int] 49 worldports: set[int]
50 goaled: bool 50 goaled: bool
51 latches: set[int] 51 latches: set[int]
52 hinted_locations: set[int]
52 53
53 def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"): 54 def __init__(self, game_ctx: "Lingo2GameContext", client_ctx: "Lingo2ClientContext"):
54 self.game_ctx = game_ctx 55 self.game_ctx = game_ctx
@@ -57,8 +58,6 @@ class Lingo2Manager:
57 self.client_ctx.manager = self 58 self.client_ctx.manager = self
58 self.tracker = Tracker(self) 59 self.tracker = Tracker(self)
59 self.keyboard = {} 60 self.keyboard = {}
60 self.worldports = set()
61 self.latches = set()
62 61
63 self.reset() 62 self.reset()
64 63
@@ -69,6 +68,7 @@ class Lingo2Manager:
69 self.worldports = set() 68 self.worldports = set()
70 self.goaled = False 69 self.goaled = False
71 self.latches = set() 70 self.latches = set()
71 self.hinted_locations = set()
72 72
73 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]:
74 ret: dict[str, int] = {} 74 ret: dict[str, int] = {}
@@ -101,6 +101,12 @@ class Lingo2Manager:
101 101
102 return ret 102 return ret
103 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
104 110
105class Lingo2GameContext: 111class Lingo2GameContext:
106 server: Endpoint | None 112 server: Endpoint | None
@@ -276,6 +282,28 @@ class Lingo2GameContext:
276 282
277 async_start(self.send_msgs([msg]), name="update latches") 283 async_start(self.send_msgs([msg]), name="update latches")
278 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
279 async def send_msgs(self, msgs: list[Any]) -> None: 307 async def send_msgs(self, msgs: list[Any]) -> None:
280 """ `msgs` JSON serializable """ 308 """ `msgs` JSON serializable """
281 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:
@@ -290,6 +318,7 @@ class Lingo2ClientContext(CommonContext):
290 items_handling = 0b111 318 items_handling = 0b111
291 319
292 slot_data: dict[str, Any] | None 320 slot_data: dict[str, Any] | None
321 hints_data_storage_key: str
293 victory_data_storage_key: str 322 victory_data_storage_key: str
294 323
295 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):
@@ -327,10 +356,12 @@ class Lingo2ClientContext(CommonContext):
327 self.manager.tracker.set_checked_locations(self.checked_locations) 356 self.manager.tracker.set_checked_locations(self.checked_locations)
328 self.manager.game_ctx.send_accessible_locations() 357 self.manager.game_ctx.send_accessible_locations()
329 358
359 self.hints_data_storage_key = f"_read_hints_{self.team}_{self.slot}"
330 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}"
331 361
332 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"),
333 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"))
334 msg_batch = [{ 365 msg_batch = [{
335 "cmd": "Set", 366 "cmd": "Set",
336 "key": self.get_datastorage_key("keyboard1"), 367 "key": self.get_datastorage_key("keyboard1"),
@@ -349,6 +380,12 @@ class Lingo2ClientContext(CommonContext):
349 "default": [], 380 "default": [],
350 "want_reply": True, 381 "want_reply": True,
351 "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": []}]
352 }] 389 }]
353 390
354 if self.slot_data.get("shuffle_worldports", False): 391 if self.slot_data.get("shuffle_worldports", False):
@@ -447,6 +484,8 @@ class Lingo2ClientContext(CommonContext):
447 for k, v in args["keys"].items(): 484 for k, v in args["keys"].items():
448 if k == self.victory_data_storage_key: 485 if k == self.victory_data_storage_key:
449 self.handle_status_update(v) 486 self.handle_status_update(v)
487 elif k == self.hints_data_storage_key:
488 self.update_hints()
450 elif cmd == "SetReply": 489 elif cmd == "SetReply":
451 if args["key"] == self.get_datastorage_key("keyboard1"): 490 if args["key"] == self.get_datastorage_key("keyboard1"):
452 self.handle_keyboard_update(1, args) 491 self.handle_keyboard_update(1, args)
@@ -464,6 +503,10 @@ class Lingo2ClientContext(CommonContext):
464 updates = self.manager.update_latches(door_ids) 503 updates = self.manager.update_latches(door_ids)
465 if len(updates) > 0: 504 if len(updates) > 0:
466 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()
467 510
468 def get_datastorage_key(self, name: str): 511 def get_datastorage_key(self, name: str):
469 return f"Lingo2_{self.slot}_{name}" 512 return f"Lingo2_{self.slot}_{name}"
@@ -559,6 +602,36 @@ class Lingo2ClientContext(CommonContext):
559 }] 602 }]
560 }]) 603 }])
561 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]
613 }]
614 }])
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
562 635
563async def pipe_loop(manager: Lingo2Manager): 636async def pipe_loop(manager: Lingo2Manager):
564 while not manager.client_ctx.exit_event.is_set(): 637 while not manager.client_ctx.exit_event.is_set():
@@ -635,6 +708,10 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict):
635 updates = manager.update_latches({args["door"]}) 708 updates = manager.update_latches({args["door"]})
636 if len(updates) > 0: 709 if len(updates) > 0:
637 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")
638 elif cmd == "Quit": 715 elif cmd == "Quit":
639 manager.client_ctx.exit_event.set() 716 manager.client_ctx.exit_event.set()
640 717
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 8a84111..715178e 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -84,7 +84,8 @@ class Lingo2StaticLogic:
84 84
85 for panel in self.objects.panels: 85 for panel in self.objects.panels:
86 for letter in panel.answer.upper(): 86 for letter in panel.answer.upper():
87 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
88 89
89 self.door_id_by_ap_id = {door.ap_id: door.id for door in self.objects.doors if door.HasField("ap_id")} 90 self.door_id_by_ap_id = {door.ap_id: door.id for door in self.objects.doors if door.HasField("ap_id")}
90 self.port_id_by_ap_id = {port.ap_id: port.id for port in self.objects.ports if port.HasField("ap_id")} 91 self.port_id_by_ap_id = {port.ap_id: port.id for port in self.objects.ports if port.HasField("ap_id")}
diff --git a/apworld/tracker.py b/apworld/tracker.py index d473af4..a84c3f8 100644 --- a/apworld/tracker.py +++ b/apworld/tracker.py
@@ -96,6 +96,7 @@ class Tracker:
96 PLAYER_NUM), prevent_sweep=True) 96 PLAYER_NUM), prevent_sweep=True)
97 97
98 self.state.sweep_for_advancements() 98 self.state.sweep_for_advancements()
99 self.state.update_reachable_regions(PLAYER_NUM)
99 100
100 self.accessible_locations = set() 101 self.accessible_locations = set()
101 self.accessible_worldports = set() 102 self.accessible_worldports = set()
diff --git a/data/door_groups.txtpb b/data/door_groups.txtpb index e72621e..fab75f5 100644 --- a/data/door_groups.txtpb +++ b/data/door_groups.txtpb
@@ -129,6 +129,10 @@ door_groups {
129 name: "Cyan Doors" 129 name: "Cyan Doors"
130 } 130 }
131 doors { 131 doors {
132 map: "the_owl"
133 name: "Double Letters"
134 }
135 doors {
132 map: "the_parthenon" 136 map: "the_parthenon"
133 name: "Double Letters" 137 name: "Double Letters"
134 } 138 }
diff --git a/data/maps/daedalus/doors.txtpb b/data/maps/daedalus/doors.txtpb index ed7516f..f2f4592 100644 --- a/data/maps/daedalus/doors.txtpb +++ b/data/maps/daedalus/doors.txtpb
@@ -699,6 +699,8 @@ doors {
699doors { 699doors {
700 name: "Hedges Tower" 700 name: "Hedges Tower"
701 type: LOCATION_ONLY 701 type: LOCATION_ONLY
702 latch: true
703 receivers: "Components/Doors/Halls/tower_door"
702 # TODO: Not making this an item right now in order to force the player to 704 # TODO: Not making this an item right now in order to force the player to
703 # solve the puzzles in order to enter The Tenacious. In the future, I'd like 705 # solve the puzzles in order to enter The Tenacious. In the future, I'd like
704 # to make this an item, and make you solve the panels in order to get the 706 # to make this an item, and make you solve the panels in order to get the
diff --git a/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb index 4d94d5a..d4650f0 100644 --- a/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb +++ b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb
@@ -5,5 +5,5 @@ ports {
5 path: "Components/Warps/worldport9" 5 path: "Components/Warps/worldport9"
6 destination { x: -41 y: 6 z: -17.5 } 6 destination { x: -41 y: 6 z: -17.5 }
7 rotation: 0 7 rotation: 0
8 gravity: Y_PLUS 8 # This isn't actually Y_PLUS gravity! A nearby warp sneakily flips you.
9} 9}
diff --git a/data/maps/the_extravagant/rooms/Engine Room.txtpb b/data/maps/the_extravagant/rooms/Engine Room.txtpb index 7157757..18dfcad 100644 --- a/data/maps/the_extravagant/rooms/Engine Room.txtpb +++ b/data/maps/the_extravagant/rooms/Engine Room.txtpb
@@ -25,6 +25,6 @@ ports {
25 display_name: "Engine Room Worldport" 25 display_name: "Engine Room Worldport"
26 path: "Components/Warps/worldport2" 26 path: "Components/Warps/worldport2"
27 gravity: Z_PLUS 27 gravity: Z_PLUS
28 # TODO: entrance shuffling for non Y axis gravity 28 # TODO: entrance shuffling for non Y_MINUS gravity
29 no_shuffle: true 29 no_shuffle: true
30} 30}
diff --git a/data/maps/the_owl/doors.txtpb b/data/maps/the_owl/doors.txtpb index eaafa48..2d1c851 100644 --- a/data/maps/the_owl/doors.txtpb +++ b/data/maps/the_owl/doors.txtpb
@@ -349,3 +349,9 @@ doors {
349 location_room: "Z Room" 349 location_room: "Z Room"
350 location_name: "MAZE" 350 location_name: "MAZE"
351} 351}
352doors {
353 name: "Double Letters"
354 type: EVENT
355 receivers: "Panels/Warps/magenta/visibilityListener"
356 double_letters: true
357}
diff --git a/data/maps/the_owl/rooms/Connected Area.txtpb b/data/maps/the_owl/rooms/Connected Area.txtpb index 432d6b2..b604cba 100644 --- a/data/maps/the_owl/rooms/Connected Area.txtpb +++ b/data/maps/the_owl/rooms/Connected Area.txtpb
@@ -26,6 +26,7 @@ panels {
26 clue: "color" 26 clue: "color"
27 answer: "magenta" 27 answer: "magenta"
28 symbols: EXAMPLE 28 symbols: EXAMPLE
29 required_door { name: "Double Letters" }
29} 30}
30panels { 31panels {
31 name: "WHITE" 32 name: "WHITE"
diff --git a/data/maps/the_symbolic/doors.txtpb b/data/maps/the_symbolic/doors.txtpb index 5a443e7..7728e0d 100644 --- a/data/maps/the_symbolic/doors.txtpb +++ b/data/maps/the_symbolic/doors.txtpb
@@ -139,17 +139,13 @@ doors {
139doors { 139doors {
140 name: "Poetry Room Panels" 140 name: "Poetry Room Panels"
141 type: LOCATION_ONLY 141 type: LOCATION_ONLY
142 panels { room: "Poetry Room 1" name: "ABSORBED" }
143 panels { room: "Poetry Room 1" name: "PRIMORDIAL" }
144 panels { room: "Poetry Room 2" name: "NOT" } 142 panels { room: "Poetry Room 2" name: "NOT" }
145 panels { room: "Poetry Room 2" name: "THERE" } 143 panels { room: "Poetry Room 2" name: "THERE" }
146 panels { room: "Poetry Room 2" name: "NOT THERE" } 144 panels { room: "Poetry Room 2" name: "NOT THERE" }
147 panels { room: "Poetry Room 3" name: "NOT" } 145 panels { room: "Poetry Room 3" name: "NOT" }
148 panels { room: "Poetry Room 3" name: "PRETTY" } 146 panels { room: "Poetry Room 3" name: "PRETTY" }
149 panels { room: "Poetry Room 3" name: "NOT PRETTY" }
150 panels { room: "Poetry Room Left" name: "NOT" } 147 panels { room: "Poetry Room Left" name: "NOT" }
151 panels { room: "Poetry Room Left" name: "TRUE" } 148 panels { room: "Poetry Room Left" name: "TRUE" }
152 panels { room: "Poetry Room Left" name: "NOT TRUE" }
153 panels { room: "Poetry Room Left Left" name: "NOT (1)" } 149 panels { room: "Poetry Room Left Left" name: "NOT (1)" }
154 panels { room: "Poetry Room Left Left" name: "NOT (2)" } 150 panels { room: "Poetry Room Left Left" name: "NOT (2)" }
155 panels { room: "Poetry Room Left Left" name: "LEFT" } 151 panels { room: "Poetry Room Left Left" name: "LEFT" }
@@ -160,7 +156,6 @@ doors {
160 panels { room: "Poetry Room Left Right" name: "NOT NOT MISS" } 156 panels { room: "Poetry Room Left Right" name: "NOT NOT MISS" }
161 panels { room: "Poetry Room Right" name: "NOT" } 157 panels { room: "Poetry Room Right" name: "NOT" }
162 panels { room: "Poetry Room Right" name: "BETTER" } 158 panels { room: "Poetry Room Right" name: "BETTER" }
163 panels { room: "Poetry Room Right" name: "NOT BETTER" }
164 panels { room: "Poetry Room Right Left" name: "NOT (1)" } 159 panels { room: "Poetry Room Right Left" name: "NOT (1)" }
165 panels { room: "Poetry Room Right Left" name: "NOT (2)" } 160 panels { room: "Poetry Room Right Left" name: "NOT (2)" }
166 panels { room: "Poetry Room Right Left" name: "TABLET" } 161 panels { room: "Poetry Room Right Left" name: "TABLET" }
@@ -276,6 +271,9 @@ doors {
276doors { 271doors {
277 name: "Main Area Exit" 272 name: "Main Area Exit"
278 type: EVENT 273 type: EVENT
274 # The game logic here requires you to solve every panel on the map, EXCEPT:
275 # 1) The four panels past the door, and
276 # 2) Any panel that has a proxy.
279 panels { room: "Main Area" name: "JUSTICE" } 277 panels { room: "Main Area" name: "JUSTICE" }
280 panels { room: "Main Area" name: "NOTICE (1)" } 278 panels { room: "Main Area" name: "NOTICE (1)" }
281 panels { room: "Main Area" name: "NOTICE (2)" } 279 panels { room: "Main Area" name: "NOTICE (2)" }
@@ -353,6 +351,36 @@ doors {
353 panels { room: "Main Area" name: "LIKE" } 351 panels { room: "Main Area" name: "LIKE" }
354 panels { room: "Main Area" name: "NEEDLESS" } 352 panels { room: "Main Area" name: "NEEDLESS" }
355 panels { room: "Main Area" name: "RESTLESS" } 353 panels { room: "Main Area" name: "RESTLESS" }
354 panels { room: "Poetry Room 2" name: "NOT" }
355 panels { room: "Poetry Room 2" name: "THERE" }
356 panels { room: "Poetry Room 2" name: "NOT THERE" }
357 panels { room: "Poetry Room 3" name: "NOT" }
358 panels { room: "Poetry Room 3" name: "PRETTY" }
359 panels { room: "Poetry Room Left" name: "NOT" }
360 panels { room: "Poetry Room Left" name: "TRUE" }
361 panels { room: "Poetry Room Left Left" name: "NOT (1)" }
362 panels { room: "Poetry Room Left Left" name: "NOT (2)" }
363 panels { room: "Poetry Room Left Left" name: "LEFT" }
364 panels { room: "Poetry Room Left Left" name: "NOT NOT LEFT" }
365 panels { room: "Poetry Room Left Right" name: "NOT (1)" }
366 panels { room: "Poetry Room Left Right" name: "NOT (2)" }
367 panels { room: "Poetry Room Left Right" name: "MISS" }
368 panels { room: "Poetry Room Left Right" name: "NOT NOT MISS" }
369 panels { room: "Poetry Room Right" name: "NOT" }
370 panels { room: "Poetry Room Right" name: "BETTER" }
371 panels { room: "Poetry Room Right Left" name: "NOT (1)" }
372 panels { room: "Poetry Room Right Left" name: "NOT (2)" }
373 panels { room: "Poetry Room Right Left" name: "TABLET" }
374 panels { room: "Poetry Room Right Left" name: "NOT NOT TABLET" }
375 panels { room: "Poetry Room Right Right" name: "NOT (1)" }
376 panels { room: "Poetry Room Right Right" name: "NOT (2)" }
377 panels { room: "Poetry Room Right Right" name: "NOT (3)" }
378 panels { room: "Poetry Room Right Right" name: "NOT NOT NOT" }
379 panels { room: "Whirred Room" name: "TAIPEI" }
380 panels { room: "Whirred Room" name: "NAYSAYER" }
381 panels { room: "Whirred Room" name: "NAY" }
382 panels { room: "Whirred Room" name: "INDEX (1)" }
383 panels { room: "Whirred Room" name: "INDEX (2)" }
356} 384}
357doors { 385doors {
358 name: "Mastery" 386 name: "Mastery"
diff --git a/data/maps/the_unkempt/doors.txtpb b/data/maps/the_unkempt/doors.txtpb index d2e9bc6..f758369 100644 --- a/data/maps/the_unkempt/doors.txtpb +++ b/data/maps/the_unkempt/doors.txtpb
@@ -71,7 +71,6 @@ doors {
71 latch: true 71 latch: true
72 receivers: "Components/Doors/entry_6" 72 receivers: "Components/Doors/entry_6"
73 receivers: "Components/Doors/entry_13" 73 receivers: "Components/Doors/entry_13"
74 receivers: "Panels/Assorted/panel_1/teleportListener"
75 control_center_color: "orange" 74 control_center_color: "orange"
76 double_letters: true 75 double_letters: true
77} 76}
diff --git a/data/maps/the_unkempt/rooms/Right Area.txtpb b/data/maps/the_unkempt/rooms/Right Area.txtpb index 03d7cea..313c276 100644 --- a/data/maps/the_unkempt/rooms/Right Area.txtpb +++ b/data/maps/the_unkempt/rooms/Right Area.txtpb
@@ -159,5 +159,4 @@ panels {
159 clue: "color" 159 clue: "color"
160 answer: "orange" 160 answer: "orange"
161 symbols: EXAMPLE 161 symbols: EXAMPLE
162 required_door { name: "Control Center Orange Door" }
163} 162}
diff --git a/data/metadata.txtpb b/data/metadata.txtpb index a939456..998aa8c 100644 --- a/data/metadata.txtpb +++ b/data/metadata.txtpb
@@ -1,7 +1,7 @@
1version { 1version {
2 major: 7 2 major: 8
3 minor: 2 3 minor: 0
4 patch: 0 4 patch: 3
5} 5}
6# Filler item. 6# Filler item.
7special_names: "A Job Well Done" 7special_names: "A Job Well Done"