about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--apworld/__init__.py1
-rw-r--r--apworld/client/manager.gd2
-rw-r--r--apworld/client/player.gd338
-rw-r--r--apworld/options.py20
-rw-r--r--apworld/player_logic.py3
5 files changed, 228 insertions, 136 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 6540b08..4ebf845 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -130,6 +130,7 @@ class Lingo2World(World):
130 slot_options = [ 130 slot_options = [
131 "cyan_door_behavior", 131 "cyan_door_behavior",
132 "daedalus_roof_access", 132 "daedalus_roof_access",
133 "enable_gift_maps",
133 "enable_icarus", 134 "enable_icarus",
134 "keyholder_sanity", 135 "keyholder_sanity",
135 "shuffle_control_center_colors", 136 "shuffle_control_center_colors",
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index a17bee8..91797b2 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -63,6 +63,7 @@ const kEndingNameByVictoryValue = {
63var apworld_version = [0, 0, 0] 63var apworld_version = [0, 0, 0]
64var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 64var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
65var daedalus_roof_access = false 65var daedalus_roof_access = false
66var enable_gift_maps = []
66var keyholder_sanity = false 67var keyholder_sanity = false
67var port_pairings = {} 68var port_pairings = {}
68var shuffle_control_center_colors = false 69var shuffle_control_center_colors = false
@@ -439,6 +440,7 @@ func _client_connected(slot_data):
439 # Read slot data. 440 # Read slot data.
440 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) 441 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
441 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 442 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
443 enable_gift_maps = slot_data.get("enable_gift_maps", [])
442 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 444 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
443 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) 445 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
444 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 446 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 1330e24..43bf758 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -19,141 +19,6 @@ func _ready():
19 19
20 ap.start_batching_locations() 20 ap.start_batching_locations()
21 21
22 # Set up door locations.
23 var map_id = gamedata.map_id_by_name.get(global.map)
24 for door in gamedata.objects.get_doors():
25 if door.get_map_id() != map_id:
26 continue
27
28 if not door.has_ap_id():
29 continue
30
31 if (
32 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
33 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
34 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
35 ):
36 continue
37
38 var locationListener = ap.SCRIPT_locationListener.new()
39 locationListener.location_id = door.get_ap_id()
40 locationListener.name = "locationListener_%d" % door.get_ap_id()
41
42 for panel_ref in door.get_panels():
43 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
44 var panel_path = panel_data.get_path()
45
46 if panel_ref.has_answer():
47 for proxy in panel_data.get_proxies():
48 if proxy.get_answer() == panel_ref.get_answer():
49 panel_path = proxy.get_path()
50 break
51
52 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
53
54 for keyholder_ref in door.get_keyholders():
55 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
56
57 var khl = khl_script.new()
58 khl.name = (
59 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
60 )
61 khl.answer = keyholder_ref.get_key()
62 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
63 get_parent().add_child.call_deferred(khl)
64
65 locationListener.senders.append(NodePath("../" + khl.name))
66
67 for sender in door.get_senders():
68 locationListener.senders.append(NodePath("/root/scene/" + sender))
69
70 if door.has_complete_at():
71 locationListener.complete_at = door.get_complete_at()
72
73 get_parent().add_child.call_deferred(locationListener)
74
75 # Set up letter locations.
76 for letter in gamedata.objects.get_letters():
77 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
78 if room.get_map_id() != map_id:
79 continue
80
81 var locationListener = ap.SCRIPT_locationListener.new()
82 locationListener.location_id = letter.get_ap_id()
83 locationListener.name = "locationListener_%d" % letter.get_ap_id()
84 locationListener.senders.append(NodePath("/root/scene/" + letter.get_path()))
85
86 get_parent().add_child.call_deferred(locationListener)
87
88 if (
89 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
90 != ap.kLETTER_BEHAVIOR_VANILLA
91 ):
92 var scout = ap.scout_location(letter.get_ap_id())
93 if scout != null and not (scout["for_self"] and scout["flags"] & 4 != 0):
94 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
95 letter.get_path()
96 )
97 if collectable != null:
98 collectable.setScoutedText.call_deferred(scout["item"])
99
100 # Set up mastery locations.
101 for mastery in gamedata.objects.get_masteries():
102 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
103 if room.get_map_id() != map_id:
104 continue
105
106 var locationListener = ap.SCRIPT_locationListener.new()
107 locationListener.location_id = mastery.get_ap_id()
108 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
109 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
110
111 get_parent().add_child.call_deferred(locationListener)
112
113 # Set up ending locations.
114 for ending in gamedata.objects.get_endings():
115 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
116 if room.get_map_id() != map_id:
117 continue
118
119 var locationListener = ap.SCRIPT_locationListener.new()
120 locationListener.location_id = ending.get_ap_id()
121 locationListener.name = "locationListener_%d" % ending.get_ap_id()
122 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
123
124 get_parent().add_child.call_deferred(locationListener)
125
126 if ap.kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
127 var victoryListener = ap.SCRIPT_victoryListener.new()
128 victoryListener.name = "victoryListener"
129 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
130
131 get_parent().add_child.call_deferred(victoryListener)
132
133 # Set up keyholder locations, in keyholder sanity.
134 if ap.keyholder_sanity:
135 for keyholder in gamedata.objects.get_keyholders():
136 if not keyholder.has_key():
137 continue
138
139 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
140 if room.get_map_id() != map_id:
141 continue
142
143 var locationListener = ap.SCRIPT_locationListener.new()
144 locationListener.location_id = keyholder.get_ap_id()
145 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
146
147 var khl = khl_script.new()
148 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
149 khl.answer = keyholder.get_key()
150 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
151 get_parent().add_child.call_deferred(khl)
152
153 locationListener.senders.append(NodePath("../" + khl.name))
154
155 get_parent().add_child.call_deferred(locationListener)
156
157 # Block off roof access in Daedalus. 22 # Block off roof access in Daedalus.
158 if global.map == "daedalus" and not ap.daedalus_roof_access: 23 if global.map == "daedalus" and not ap.daedalus_roof_access:
159 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) 24 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
@@ -210,6 +75,45 @@ func _ready():
210 sign2.material = load("res://assets/materials/%s.material" % sign2_color) 75 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
211 get_parent().add_child.call_deferred(sign2) 76 get_parent().add_child.call_deferred(sign2)
212 77
78 # Add the gift map entry panel if needed.
79 if not ap.enable_gift_maps.is_empty():
80 var panel_prefab = preload("res://objects/nodes/panel.tscn")
81 var wpl_prefab = preload("res://objects/nodes/listeners/worldportListener.tscn")
82
83 var giftmap_parent = Node.new()
84 giftmap_parent.name = "GiftMapEntrance"
85 get_node("/root/scene/Components").add_child.call_deferred(giftmap_parent)
86
87 var symbolless_player = ""
88 for i in range(ap.client.ap_user.length()):
89 if "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(
90 ap.client.ap_user[i]
91 ):
92 symbolless_player = symbolless_player + ap.client.ap_user[i].to_lower()
93
94 var giftmap_panel = panel_prefab.instantiate()
95 giftmap_panel.name = "Panel"
96 giftmap_panel.position = Vector3(33.5, 1, 5.5)
97 giftmap_panel.rotation_degrees = Vector3(-45, 0, 0)
98 giftmap_panel.clue = "player"
99 giftmap_panel.answer = symbolless_player
100
101 if ap.enable_gift_maps.has("The Advanced"):
102 var icely_panel = panel_prefab.instantiate()
103 icely_panel.name = "IcelyPanel"
104 icely_panel.answer = "icely"
105 icely_panel.position = Vector3(33.5, -200, 5.5)
106 giftmap_panel.proxies.append(NodePath("../IcelyPanel"))
107 giftmap_parent.add_child.call_deferred(icely_panel)
108
109 var icely_wpl = wpl_prefab.instantiate()
110 icely_wpl.name = "IcelyWpl"
111 icely_wpl.exit = "the_advanced"
112 icely_wpl.senders.append(NodePath("../IcelyPanel"))
113 giftmap_parent.add_child.call_deferred(icely_wpl)
114
115 giftmap_parent.add_child.call_deferred(giftmap_panel)
116
213 # Add the strict purple ending validation. 117 # Add the strict purple ending validation.
214 if global.map == "the_sun_temple" and ap.strict_purple_ending: 118 if global.map == "the_sun_temple" and ap.strict_purple_ending:
215 var panel_prefab = preload("res://objects/nodes/panel.tscn") 119 var panel_prefab = preload("res://objects/nodes/panel.tscn")
@@ -318,8 +222,172 @@ func _ready():
318 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea") 222 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea")
319 rte_trigger.position.z = 0 223 rte_trigger.position.z = 0
320 224
225 # Add the mastery to The Advanced.
226 if global.map == "the_advanced":
227 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
228 var saver_prefab = preload("res://objects/nodes/saver.tscn")
229 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
230
231 var mastery = collectable_prefab.instantiate()
232 mastery.name = "collectable"
233 mastery.position = Vector3(0, -200, -5)
234 mastery.unlock_type = "smiley"
235 mastery.material_override = load("res://assets/materials/gold.material")
236 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
237
238 var tpl = tpl_prefab.instantiate()
239 tpl.teleport_point = Vector3(0, 2, -5)
240 tpl.teleport_rotate = Vector3(0, 0, 0)
241 tpl.target_path = mastery
242 tpl.name = "Teleport"
243 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_29"))
244 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_30"))
245 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
246 mastery.add_child.call_deferred(tpl)
247
248 var saver = saver_prefab.instantiate()
249 saver.name = "saver_collectables"
250 saver.type = "collectables"
251 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
252 get_node("/root/scene").add_child.call_deferred(saver)
253
321 ap.update_job_well_done_sign() 254 ap.update_job_well_done_sign()
322 255
256 # Set up door locations.
257 var map_id = gamedata.map_id_by_name.get(global.map)
258 for door in gamedata.objects.get_doors():
259 if door.get_map_id() != map_id:
260 continue
261
262 if not door.has_ap_id():
263 continue
264
265 if (
266 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
267 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
268 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
269 ):
270 continue
271
272 var locationListener = ap.SCRIPT_locationListener.new()
273 locationListener.location_id = door.get_ap_id()
274 locationListener.name = "locationListener_%d" % door.get_ap_id()
275
276 for panel_ref in door.get_panels():
277 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
278 var panel_path = panel_data.get_path()
279
280 if panel_ref.has_answer():
281 for proxy in panel_data.get_proxies():
282 if proxy.get_answer() == panel_ref.get_answer():
283 panel_path = proxy.get_path()
284 break
285
286 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
287
288 for keyholder_ref in door.get_keyholders():
289 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
290
291 var khl = khl_script.new()
292 khl.name = (
293 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
294 )
295 khl.answer = keyholder_ref.get_key()
296 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
297 get_parent().add_child.call_deferred(khl)
298
299 locationListener.senders.append(NodePath("../" + khl.name))
300
301 for sender in door.get_senders():
302 locationListener.senders.append(NodePath("/root/scene/" + sender))
303
304 if door.has_complete_at():
305 locationListener.complete_at = door.get_complete_at()
306
307 get_parent().add_child.call_deferred(locationListener)
308
309 # Set up letter locations.
310 for letter in gamedata.objects.get_letters():
311 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
312 if room.get_map_id() != map_id:
313 continue
314
315 var locationListener = ap.SCRIPT_locationListener.new()
316 locationListener.location_id = letter.get_ap_id()
317 locationListener.name = "locationListener_%d" % letter.get_ap_id()
318 locationListener.senders.append(NodePath("/root/scene/" + letter.get_path()))
319
320 get_parent().add_child.call_deferred(locationListener)
321
322 if (
323 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
324 != ap.kLETTER_BEHAVIOR_VANILLA
325 ):
326 var scout = ap.scout_location(letter.get_ap_id())
327 if scout != null and not (scout["for_self"] and scout["flags"] & 4 != 0):
328 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
329 letter.get_path()
330 )
331 if collectable != null:
332 collectable.setScoutedText.call_deferred(scout["item"])
333
334 # Set up mastery locations.
335 for mastery in gamedata.objects.get_masteries():
336 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
337 if room.get_map_id() != map_id:
338 continue
339
340 var locationListener = ap.SCRIPT_locationListener.new()
341 locationListener.location_id = mastery.get_ap_id()
342 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
343 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
344
345 get_parent().add_child.call_deferred(locationListener)
346
347 # Set up ending locations.
348 for ending in gamedata.objects.get_endings():
349 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
350 if room.get_map_id() != map_id:
351 continue
352
353 var locationListener = ap.SCRIPT_locationListener.new()
354 locationListener.location_id = ending.get_ap_id()
355 locationListener.name = "locationListener_%d" % ending.get_ap_id()
356 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
357
358 get_parent().add_child.call_deferred(locationListener)
359
360 if ap.kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
361 var victoryListener = ap.SCRIPT_victoryListener.new()
362 victoryListener.name = "victoryListener"
363 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
364
365 get_parent().add_child.call_deferred(victoryListener)
366
367 # Set up keyholder locations, in keyholder sanity.
368 if ap.keyholder_sanity:
369 for keyholder in gamedata.objects.get_keyholders():
370 if not keyholder.has_key():
371 continue
372
373 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
374 if room.get_map_id() != map_id:
375 continue
376
377 var locationListener = ap.SCRIPT_locationListener.new()
378 locationListener.location_id = keyholder.get_ap_id()
379 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
380
381 var khl = khl_script.new()
382 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
383 khl.answer = keyholder.get_key()
384 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
385 get_parent().add_child.call_deferred(khl)
386
387 locationListener.senders.append(NodePath("../" + khl.name))
388
389 get_parent().add_child.call_deferred(locationListener)
390
323 var minimap = ap.SCRIPT_minimap.new() 391 var minimap = ap.SCRIPT_minimap.new()
324 minimap.name = "Minimap" 392 minimap.name = "Minimap"
325 minimap.visible = ap.show_minimap 393 minimap.visible = ap.show_minimap
diff --git a/apworld/options.py b/apworld/options.py index 600df6a..4d9c3aa 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -1,6 +1,6 @@
1from dataclasses import dataclass 1from dataclasses import dataclass
2 2
3from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range 3from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range, OptionSet
4 4
5 5
6class ShuffleDoors(DefaultOnToggle): 6class ShuffleDoors(DefaultOnToggle):
@@ -99,6 +99,23 @@ class EnableIcarus(Toggle):
99 display_name = "Enable Icarus" 99 display_name = "Enable Icarus"
100 100
101 101
102class EnableGiftMaps(OptionSet):
103 """
104 Controls whether the beta tester gift maps are randomized. By default, these are not accessible at all from within
105 the randomizer. Enabling at least one gift map will cause a panel to appear in The Entry's Starting Room. Gift maps
106 can be accessed by taking a player name that would ordinarily cause the game to load into a gift map, and entering
107 it into this panel.
108
109 In the base game, nothing happens once you complete a gift map. Masteries have been added to the gift maps in the
110 randomizer so that the player can be rewarded for completing them.
111
112 Note that the gift maps are intended only for specific people, and as a result may be frustrating or require
113 knowledge of inside jokes. The Crystalline is particularly difficult as it requires completing a parkour course.
114 """
115 display_name = "Enable Gift Maps"
116 valid_keys = ["The Advanced", "The Charismatic", "The Crystalline", "The Fuzzy", "The Stellar"]
117
118
102class DaedalusRoofAccess(Toggle): 119class DaedalusRoofAccess(Toggle):
103 """ 120 """
104 If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus 121 If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus
@@ -178,6 +195,7 @@ class Lingo2Options(PerGameCommonOptions):
178 keyholder_sanity: KeyholderSanity 195 keyholder_sanity: KeyholderSanity
179 cyan_door_behavior: CyanDoorBehavior 196 cyan_door_behavior: CyanDoorBehavior
180 enable_icarus: EnableIcarus 197 enable_icarus: EnableIcarus
198 enable_gift_maps: EnableGiftMaps
181 daedalus_roof_access: DaedalusRoofAccess 199 daedalus_roof_access: DaedalusRoofAccess
182 strict_purple_ending: StrictPurpleEnding 200 strict_purple_ending: StrictPurpleEnding
183 strict_cyan_ending: StrictCyanEnding 201 strict_cyan_ending: StrictCyanEnding
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 0cf0473..4f825b9 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -234,6 +234,9 @@ class Lingo2PlayerLogic:
234 return True 234 return True
235 elif game_map.type == data_pb2.MapType.ICARUS: 235 elif game_map.type == data_pb2.MapType.ICARUS:
236 return bool(world.options.enable_icarus) 236 return bool(world.options.enable_icarus)
237 elif game_map.type == data_pb2.MapType.GIFT_MAP:
238 if game_map.name == "the_advanced":
239 return "The Advanced" in world.options.enable_gift_maps.value
237 240
238 return False 241 return False
239 242