about summary refs log tree commit diff stats
path: root/apworld
diff options
context:
space:
mode:
Diffstat (limited to 'apworld')
-rw-r--r--apworld/__init__.py1
-rw-r--r--apworld/client/manager.gd2
-rw-r--r--apworld/client/player.gd423
-rw-r--r--apworld/options.py28
-rw-r--r--apworld/player_logic.py7
5 files changed, 322 insertions, 139 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..9aac5a6 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,84 @@ 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 tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
82 var wpl_prefab = preload("res://objects/nodes/listeners/worldportListener.tscn")
83
84 var giftmap_parent = Node.new()
85 giftmap_parent.name = "GiftMapEntrance"
86 get_node("/root/scene/Components").add_child.call_deferred(giftmap_parent)
87
88 var symbolless_player = ""
89 for i in range(ap.client.ap_user.length()):
90 if "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(
91 ap.client.ap_user[i]
92 ):
93 symbolless_player = symbolless_player + ap.client.ap_user[i].to_lower()
94
95 var giftmap_panel = panel_prefab.instantiate()
96 giftmap_panel.name = "Panel"
97 giftmap_panel.position = Vector3(33.5, -190, 5.5)
98 giftmap_panel.rotation_degrees = Vector3(-45, 0, 0)
99 giftmap_panel.clue = "player"
100 giftmap_panel.answer = symbolless_player
101
102 if ap.enable_gift_maps.has("The Advanced"):
103 var icely_panel = panel_prefab.instantiate()
104 icely_panel.name = "IcelyPanel"
105 icely_panel.answer = "icely"
106 icely_panel.position = Vector3(33.5, -200, 5.5)
107 giftmap_panel.proxies.append(NodePath("../IcelyPanel"))
108 giftmap_parent.add_child.call_deferred(icely_panel)
109
110 var icely_wpl = wpl_prefab.instantiate()
111 icely_wpl.name = "IcelyWpl"
112 icely_wpl.exit = "the_advanced"
113 icely_wpl.senders.append(NodePath("../IcelyPanel"))
114 giftmap_parent.add_child.call_deferred(icely_wpl)
115
116 if ap.enable_gift_maps.has("The Charismatic"):
117 var souvey_panel = panel_prefab.instantiate()
118 souvey_panel.name = "SouveyPanel"
119 souvey_panel.answer = "souvey"
120 souvey_panel.position = Vector3(33.5, -210, 5.5)
121 giftmap_panel.proxies.append(NodePath("../SouveyPanel"))
122 giftmap_parent.add_child.call_deferred(souvey_panel)
123
124 var souvey_wpl = wpl_prefab.instantiate()
125 souvey_wpl.name = "SouveyWpl"
126 souvey_wpl.exit = "the_charismatic"
127 souvey_wpl.senders.append(NodePath("../SouveyPanel"))
128 giftmap_parent.add_child.call_deferred(souvey_wpl)
129
130 if ap.enable_gift_maps.has("The Crystalline"):
131 var q_panel = panel_prefab.instantiate()
132 q_panel.name = "QPanel"
133 q_panel.answer = "q"
134 q_panel.position = Vector3(33.5, -220, 5.5)
135 giftmap_panel.proxies.append(NodePath("../QPanel"))
136 giftmap_parent.add_child.call_deferred(q_panel)
137
138 var q_wpl = wpl_prefab.instantiate()
139 q_wpl.name = "QWpl"
140 q_wpl.exit = "the_crystalline"
141 q_wpl.senders.append(NodePath("../QPanel"))
142 giftmap_parent.add_child.call_deferred(q_wpl)
143
144 giftmap_parent.add_child.call_deferred(giftmap_panel)
145
146 var giftmap_tpl = tpl_prefab.instantiate()
147 giftmap_tpl.name = "PanelTeleporter"
148 giftmap_tpl.teleport_point = Vector3(33.5, 1, 5.5)
149 giftmap_tpl.teleport_rotate = Vector3(-45, 0, 0)
150 giftmap_tpl.target_path = giftmap_panel
151 giftmap_tpl.senders.append(
152 NodePath("/root/scene/Components/Listeners/unlockReaderListenerDoubles")
153 )
154 giftmap_parent.add_child.call_deferred(giftmap_tpl)
155
213 # Add the strict purple ending validation. 156 # Add the strict purple ending validation.
214 if global.map == "the_sun_temple" and ap.strict_purple_ending: 157 if global.map == "the_sun_temple" and ap.strict_purple_ending:
215 var panel_prefab = preload("res://objects/nodes/panel.tscn") 158 var panel_prefab = preload("res://objects/nodes/panel.tscn")
@@ -318,8 +261,218 @@ func _ready():
318 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea") 261 var rte_trigger = get_node("/root/scene/Components/Warps/triggerArea")
319 rte_trigger.position.z = 0 262 rte_trigger.position.z = 0
320 263
264 # Add the mastery to The Advanced.
265 if global.map == "the_advanced":
266 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
267 var saver_prefab = preload("res://objects/nodes/saver.tscn")
268 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
269
270 var mastery = collectable_prefab.instantiate()
271 mastery.name = "collectable"
272 mastery.position = Vector3(0, -200, -5)
273 mastery.unlock_type = "smiley"
274 mastery.material_override = load("res://assets/materials/gold.material")
275 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
276
277 var tpl = tpl_prefab.instantiate()
278 tpl.teleport_point = Vector3(0, 2, -5)
279 tpl.teleport_rotate = Vector3(0, 0, 0)
280 tpl.target_path = mastery
281 tpl.name = "Teleport"
282 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_29"))
283 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_30"))
284 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
285 mastery.add_child.call_deferred(tpl)
286
287 var saver = saver_prefab.instantiate()
288 saver.name = "saver_collectables"
289 saver.type = "collectables"
290 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
291 get_node("/root/scene").add_child.call_deferred(saver)
292
293 # Add the mastery to The Charismatic.
294 if global.map == "the_charismatic":
295 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
296 var saver_prefab = preload("res://objects/nodes/saver.tscn")
297
298 var mastery = collectable_prefab.instantiate()
299 mastery.name = "collectable"
300 mastery.position = Vector3(-17, 2, -29)
301 mastery.rotation_degrees = Vector3(0, 45, 0)
302 mastery.unlock_type = "smiley"
303 mastery.material_override = load("res://assets/materials/gold.material")
304 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
305
306 var saver = saver_prefab.instantiate()
307 saver.name = "saver_collectables"
308 saver.type = "collectables"
309 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
310 get_node("/root/scene").add_child.call_deferred(saver)
311
312 # Add the mastery to The Crystalline.
313 if global.map == "the_crystalline":
314 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
315 var saver_prefab = preload("res://objects/nodes/saver.tscn")
316 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
317
318 var mastery = collectable_prefab.instantiate()
319 mastery.name = "collectable"
320 mastery.position = Vector3(0, 13, 37)
321 mastery.unlock_type = "smiley"
322 mastery.material_override = load("res://assets/materials/gold.material")
323 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
324
325 var tpl = tpl_prefab.instantiate()
326 tpl.teleport_point = Vector3(0, 11.5, -20)
327 tpl.teleport_rotate = Vector3(0, 0, 180)
328 tpl.target_path = mastery
329 tpl.name = "Teleport"
330 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3"))
331 mastery.add_child.call_deferred(tpl)
332
333 var saver = saver_prefab.instantiate()
334 saver.name = "saver_collectables"
335 saver.type = "collectables"
336 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
337 get_node("/root/scene").add_child.call_deferred(saver)
338
321 ap.update_job_well_done_sign() 339 ap.update_job_well_done_sign()
322 340
341 # Set up door locations.
342 var map_id = gamedata.map_id_by_name.get(global.map)
343 for door in gamedata.objects.get_doors():
344 if door.get_map_id() != map_id:
345 continue
346
347 if not door.has_ap_id():
348 continue
349
350 if (
351 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
352 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
353 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
354 ):
355 continue
356
357 var locationListener = ap.SCRIPT_locationListener.new()
358 locationListener.location_id = door.get_ap_id()
359 locationListener.name = "locationListener_%d" % door.get_ap_id()
360
361 for panel_ref in door.get_panels():
362 var panel_data = gamedata.objects.get_panels()[panel_ref.get_panel()]
363 var panel_path = panel_data.get_path()
364
365 if panel_ref.has_answer():
366 for proxy in panel_data.get_proxies():
367 if proxy.get_answer() == panel_ref.get_answer():
368 panel_path = proxy.get_path()
369 break
370
371 locationListener.senders.append(NodePath("/root/scene/" + panel_path))
372
373 for keyholder_ref in door.get_keyholders():
374 var keyholder_data = gamedata.objects.get_keyholders()[keyholder_ref.get_keyholder()]
375
376 var khl = khl_script.new()
377 khl.name = (
378 "location_%d_keyholder_%d" % [door.get_ap_id(), keyholder_ref.get_keyholder()]
379 )
380 khl.answer = keyholder_ref.get_key()
381 khl.senders.append(NodePath("/root/scene/" + keyholder_data.get_path()))
382 get_parent().add_child.call_deferred(khl)
383
384 locationListener.senders.append(NodePath("../" + khl.name))
385
386 for sender in door.get_senders():
387 locationListener.senders.append(NodePath("/root/scene/" + sender))
388
389 if door.has_complete_at():
390 locationListener.complete_at = door.get_complete_at()
391
392 get_parent().add_child.call_deferred(locationListener)
393
394 # Set up letter locations.
395 for letter in gamedata.objects.get_letters():
396 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
397 if room.get_map_id() != map_id:
398 continue
399
400 var locationListener = ap.SCRIPT_locationListener.new()
401 locationListener.location_id = letter.get_ap_id()
402 locationListener.name = "locationListener_%d" % letter.get_ap_id()
403 locationListener.senders.append(NodePath("/root/scene/" + letter.get_path()))
404
405 get_parent().add_child.call_deferred(locationListener)
406
407 if (
408 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
409 != ap.kLETTER_BEHAVIOR_VANILLA
410 ):
411 var scout = ap.scout_location(letter.get_ap_id())
412 if scout != null and not (scout["for_self"] and scout["flags"] & 4 != 0):
413 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
414 letter.get_path()
415 )
416 if collectable != null:
417 collectable.setScoutedText.call_deferred(scout["item"])
418
419 # Set up mastery locations.
420 for mastery in gamedata.objects.get_masteries():
421 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
422 if room.get_map_id() != map_id:
423 continue
424
425 var locationListener = ap.SCRIPT_locationListener.new()
426 locationListener.location_id = mastery.get_ap_id()
427 locationListener.name = "locationListener_%d" % mastery.get_ap_id()
428 locationListener.senders.append(NodePath("/root/scene/" + mastery.get_path()))
429
430 get_parent().add_child.call_deferred(locationListener)
431
432 # Set up ending locations.
433 for ending in gamedata.objects.get_endings():
434 var room = gamedata.objects.get_rooms()[ending.get_room_id()]
435 if room.get_map_id() != map_id:
436 continue
437
438 var locationListener = ap.SCRIPT_locationListener.new()
439 locationListener.location_id = ending.get_ap_id()
440 locationListener.name = "locationListener_%d" % ending.get_ap_id()
441 locationListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
442
443 get_parent().add_child.call_deferred(locationListener)
444
445 if ap.kEndingNameByVictoryValue.get(ap.victory_condition, null) == ending.get_name():
446 var victoryListener = ap.SCRIPT_victoryListener.new()
447 victoryListener.name = "victoryListener"
448 victoryListener.senders.append(NodePath("/root/scene/" + ending.get_path()))
449
450 get_parent().add_child.call_deferred(victoryListener)
451
452 # Set up keyholder locations, in keyholder sanity.
453 if ap.keyholder_sanity:
454 for keyholder in gamedata.objects.get_keyholders():
455 if not keyholder.has_key():
456 continue
457
458 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
459 if room.get_map_id() != map_id:
460 continue
461
462 var locationListener = ap.SCRIPT_locationListener.new()
463 locationListener.location_id = keyholder.get_ap_id()
464 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
465
466 var khl = khl_script.new()
467 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
468 khl.answer = keyholder.get_key()
469 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
470 get_parent().add_child.call_deferred(khl)
471
472 locationListener.senders.append(NodePath("../" + khl.name))
473
474 get_parent().add_child.call_deferred(locationListener)
475
323 var minimap = ap.SCRIPT_minimap.new() 476 var minimap = ap.SCRIPT_minimap.new()
324 minimap.name = "Minimap" 477 minimap.name = "Minimap"
325 minimap.visible = ap.show_minimap 478 minimap.visible = ap.show_minimap
diff --git a/apworld/options.py b/apworld/options.py index 3d8abee..7577e0c 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):
@@ -57,9 +57,6 @@ class ShuffleWorldports(Toggle):
57 Randomizes the connections between maps. This affects worldports only, which are the loading zones you walk into in 57 Randomizes the connections between maps. This affects worldports only, which are the loading zones you walk into in
58 order to change maps. This does not affect paintings, panels that teleport you, or certain other special connections 58 order to change maps. This does not affect paintings, panels that teleport you, or certain other special connections
59 like the one between The Shop and Control Center. 59 like the one between The Shop and Control Center.
60
61 NOTE: It is highly recommended that you turn on Shuffle Control Center Colors when using Shuffle Worldports. Not
62 doing so runs the risk of creating an unfinishable seed.
63 """ 60 """
64 display_name = "Shuffle Worldports" 61 display_name = "Shuffle Worldports"
65 62
@@ -102,6 +99,28 @@ class EnableIcarus(Toggle):
102 display_name = "Enable Icarus" 99 display_name = "Enable Icarus"
103 100
104 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. This option allows you to enter the maps, and creates items and locations for them. If worldport
106 shuffle is on, their worldports will be included in the randomization.
107
108 The gift maps are accessed via a panel in The Entry's Starting Room, which only appears if at least one gift map is
109 enabled. It is also treated like a cyan door, and will not appear until the condition specified in the Cyan Door
110 Behavior option is satisfied. Solving this panel with the name of one of the beta testers will teleport you to their
111 corresponding gift map.
112
113 In the base game, nothing happens once you complete a gift map. Masteries have been added to the gift maps in the
114 randomizer so that the player can be rewarded for completing them.
115
116 Note that the gift maps were originally only intended to be played by specific people, and as a result may be
117 frustrating or require knowledge of inside jokes. The Crystalline is particularly difficult as it requires
118 completing a parkour course.
119 """
120 display_name = "Enable Gift Maps"
121 valid_keys = ["The Advanced", "The Charismatic", "The Crystalline", "The Fuzzy", "The Stellar"]
122
123
105class DaedalusRoofAccess(Toggle): 124class DaedalusRoofAccess(Toggle):
106 """ 125 """
107 If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus 126 If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus
@@ -181,6 +200,7 @@ class Lingo2Options(PerGameCommonOptions):
181 keyholder_sanity: KeyholderSanity 200 keyholder_sanity: KeyholderSanity
182 cyan_door_behavior: CyanDoorBehavior 201 cyan_door_behavior: CyanDoorBehavior
183 enable_icarus: EnableIcarus 202 enable_icarus: EnableIcarus
203 enable_gift_maps: EnableGiftMaps
184 daedalus_roof_access: DaedalusRoofAccess 204 daedalus_roof_access: DaedalusRoofAccess
185 strict_purple_ending: StrictPurpleEnding 205 strict_purple_ending: StrictPurpleEnding
186 strict_cyan_ending: StrictCyanEnding 206 strict_cyan_ending: StrictCyanEnding
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 0cf0473..5f4f1d7 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -234,6 +234,13 @@ 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
240 elif game_map.name == "the_charismatic":
241 return "The Charismatic" in world.options.enable_gift_maps.value
242 elif game_map.name == "the_crystalline":
243 return "The Crystalline" in world.options.enable_gift_maps.value
237 244
238 return False 245 return False
239 246