about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--apworld/__init__.py2
-rw-r--r--apworld/client/allowNumbers.gd10
-rw-r--r--apworld/client/gamedata.gd6
-rw-r--r--apworld/client/main.gd13
-rw-r--r--apworld/client/manager.gd7
-rw-r--r--apworld/client/player.gd177
-rw-r--r--apworld/options.py22
-rw-r--r--apworld/player_logic.py37
-rw-r--r--apworld/static_logic.py3
-rw-r--r--data/connections.txtpb50
-rw-r--r--data/ids.yaml30
-rw-r--r--data/maps/control_center/doors.txtpb16
-rw-r--r--data/maps/the_entry/metadata.txtpb2
-rw-r--r--data/maps/the_entry/rooms/Starting Room.txtpb2
-rw-r--r--data/maps/the_fuzzy/connections.txtpb5
-rw-r--r--data/maps/the_fuzzy/doors.txtpb12
-rw-r--r--data/maps/the_fuzzy/metadata.txtpb4
-rw-r--r--data/maps/the_fuzzy/rooms/Main Area.txtpb119
-rw-r--r--data/maps/the_fuzzy/rooms/Mastery.txtpb5
-rw-r--r--data/maps/the_relentless/doors.txtpb32
-rw-r--r--data/metadata.txtpb2
-rw-r--r--proto/data.proto2
-rw-r--r--proto/human.proto2
-rw-r--r--tools/datapacker/main.cpp4
-rw-r--r--tools/validator/human_processor.cpp5
-rw-r--r--tools/validator/structs.h2
-rw-r--r--tools/validator/validator.cpp8
27 files changed, 527 insertions, 52 deletions
diff --git a/apworld/__init__.py b/apworld/__init__.py index 4ebf845..f5774c6 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -132,7 +132,9 @@ class Lingo2World(World):
132 "daedalus_roof_access", 132 "daedalus_roof_access",
133 "enable_gift_maps", 133 "enable_gift_maps",
134 "enable_icarus", 134 "enable_icarus",
135 "endings_requirement",
135 "keyholder_sanity", 136 "keyholder_sanity",
137 "masteries_requirement",
136 "shuffle_control_center_colors", 138 "shuffle_control_center_colors",
137 "shuffle_doors", 139 "shuffle_doors",
138 "shuffle_gallery_paintings", 140 "shuffle_gallery_paintings",
diff --git a/apworld/client/allowNumbers.gd b/apworld/client/allowNumbers.gd new file mode 100644 index 0000000..d958b50 --- /dev/null +++ b/apworld/client/allowNumbers.gd
@@ -0,0 +1,10 @@
1extends "res://scripts/nodes/allowNumbers.gd"
2
3
4func _readier():
5 var ap = global.get_node("Archipelago")
6 var gamedata = global.get_node("Gamedata")
7
8 var item_id = gamedata.objects.get_special_ids()["Numbers"]
9 if ap.client.getItemAmount(item_id) >= 1:
10 global.allow_numbers = true
diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 9305003..3a35125 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd
@@ -221,7 +221,11 @@ func _get_generated_door_location_name(door):
221 if door.get_type() != SCRIPT_proto.DoorType.STANDARD: 221 if door.get_type() != SCRIPT_proto.DoorType.STANDARD:
222 return null 222 return null
223 223
224 if door.get_keyholders().size() > 0 or door.get_endings().size() > 0 or door.has_complete_at(): 224 if (
225 door.get_keyholders().size() > 0
226 or (door.has_white_ending() and door.get_white_ending())
227 or door.has_complete_at()
228 ):
225 return null 229 return null
226 230
227 if door.get_panels().size() > 4: 231 if door.get_panels().size() > 4:
diff --git a/apworld/client/main.gd b/apworld/client/main.gd index 3a62f81..a543678 100644 --- a/apworld/client/main.gd +++ b/apworld/client/main.gd
@@ -36,6 +36,7 @@ func _ready():
36 global.add_child(ap_instance) 36 global.add_child(ap_instance)
37 37
38 # Let's also inject any scripts we need to inject now. 38 # Let's also inject any scripts we need to inject now.
39 installScriptExtension(runtime.load_script("allowNumbers.gd"))
39 installScriptExtension(runtime.load_script("animationListener.gd")) 40 installScriptExtension(runtime.load_script("animationListener.gd"))
40 installScriptExtension(runtime.load_script("collectable.gd")) 41 installScriptExtension(runtime.load_script("collectable.gd"))
41 installScriptExtension(runtime.load_script("door.gd")) 42 installScriptExtension(runtime.load_script("door.gd"))
@@ -83,6 +84,13 @@ func _ready():
83 compass_overlay_instance.SCRIPT_compass = runtime.load_script("compass.gd") 84 compass_overlay_instance.SCRIPT_compass = runtime.load_script("compass.gd")
84 global.add_child(compass_overlay_instance) 85 global.add_child(compass_overlay_instance)
85 86
87 unlocks.data["advanced_mastery"] = ""
88 unlocks.data["charismatic_mastery"] = ""
89 unlocks.data["crystalline_mastery"] = ""
90 unlocks.data["fuzzy_mastery"] = ""
91 unlocks.data["icarus_mastery"] = ""
92 unlocks.data["stellar_mastery"] = ""
93
86 var ap = global.get_node("Archipelago") 94 var ap = global.get_node("Archipelago")
87 var gamedata = global.get_node("Gamedata") 95 var gamedata = global.get_node("Gamedata")
88 ap.ap_connected.connect(connectionSuccessful) 96 ap.ap_connected.connect(connectionSuccessful)
@@ -224,11 +232,11 @@ func startGame():
224 232
225 unlocks.resetCollectables() 233 unlocks.resetCollectables()
226 unlocks.resetData() 234 unlocks.resetData()
235 unlocks.loadCollectables()
236 unlocks.loadData()
227 237
228 ap.setup_keys() 238 ap.setup_keys()
229 239
230 unlocks.loadCollectables()
231 unlocks.loadData()
232 unlocks.unlockKey("capslock", 1) 240 unlocks.unlockKey("capslock", 1)
233 241
234 if ap.shuffle_worldports: 242 if ap.shuffle_worldports:
@@ -237,6 +245,7 @@ func startGame():
237 settings.worldport_fades = "never" 245 settings.worldport_fades = "never"
238 246
239 clearResourceCache("res://objects/meshes/gridDoor.tscn") 247 clearResourceCache("res://objects/meshes/gridDoor.tscn")
248 clearResourceCache("res://objects/nodes/allowNumbers.tscn")
240 clearResourceCache("res://objects/nodes/collectable.tscn") 249 clearResourceCache("res://objects/nodes/collectable.tscn")
241 clearResourceCache("res://objects/nodes/door.tscn") 250 clearResourceCache("res://objects/nodes/door.tscn")
242 clearResourceCache("res://objects/nodes/keyHolder.tscn") 251 clearResourceCache("res://objects/nodes/keyHolder.tscn")
diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 41ab648..aa07559 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd
@@ -65,7 +65,9 @@ var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
65var daedalus_roof_access = false 65var daedalus_roof_access = false
66var enable_gift_maps = [] 66var enable_gift_maps = []
67var enable_icarus = false 67var enable_icarus = false
68var endings_requirement = 0
68var keyholder_sanity = false 69var keyholder_sanity = false
70var masteries_requirement = 0
69var port_pairings = {} 71var port_pairings = {}
70var shuffle_control_center_colors = false 72var shuffle_control_center_colors = false
71var shuffle_doors = false 73var shuffle_doors = false
@@ -259,6 +261,9 @@ func _process_item(item, amount):
259 if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]: 261 if item_id == gamedata.objects.get_special_ids()["A Job Well Done"]:
260 update_job_well_done_sign() 262 update_job_well_done_sign()
261 263
264 if item_id == gamedata.objects.get_special_ids()["Numbers"] and global.map == "the_fuzzy":
265 global.allow_numbers = true
266
262 # Show a message about the item if it's new. 267 # Show a message about the item if it's new.
263 if int(item["index"]) > _last_new_item: 268 if int(item["index"]) > _last_new_item:
264 _last_new_item = int(item["index"]) 269 _last_new_item = int(item["index"])
@@ -443,7 +448,9 @@ func _client_connected(slot_data):
443 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) 448 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
444 enable_gift_maps = slot_data.get("enable_gift_maps", []) 449 enable_gift_maps = slot_data.get("enable_gift_maps", [])
445 enable_icarus = bool(slot_data.get("enable_icarus", false)) 450 enable_icarus = bool(slot_data.get("enable_icarus", false))
451 endings_requirement = int(slot_data.get("endings_requirement", 0))
446 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false)) 452 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
453 masteries_requirement = int(slot_data.get("masteries_requirement", 0))
447 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) 454 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
448 shuffle_doors = bool(slot_data.get("shuffle_doors", false)) 455 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
449 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false)) 456 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 789d1b7..712a59b 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd
@@ -19,6 +19,85 @@ func _ready():
19 19
20 ap.start_batching_locations() 20 ap.start_batching_locations()
21 21
22 if global.map == "control_center":
23 get_node("/root/scene/Components/Doors/entry_18").queue_free()
24
25 _set_up_mastery_listener("advanced")
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
22 # Block off roof access in Daedalus. 101 # Block off roof access in Daedalus.
23 if global.map == "daedalus" and not ap.daedalus_roof_access: 102 if global.map == "daedalus" and not ap.daedalus_roof_access:
24 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) 103 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
@@ -141,6 +220,29 @@ func _ready():
141 q_wpl.senders.append(NodePath("../QPanel")) 220 q_wpl.senders.append(NodePath("../QPanel"))
142 giftmap_parent.add_child.call_deferred(q_wpl) 221 giftmap_parent.add_child.call_deferred(q_wpl)
143 222
223 if ap.enable_gift_maps.has("The Fuzzy"):
224 var gongus_panel = panel_prefab.instantiate()
225 gongus_panel.name = "GongusPanel"
226 gongus_panel.answer = "gongus"
227 gongus_panel.position = Vector3(33.5, -260, 5.5)
228 giftmap_panel.proxies.append(NodePath("../GongusPanel"))
229 giftmap_parent.add_child.call_deferred(gongus_panel)
230
231 var kiwi_panel = panel_prefab.instantiate()
232 kiwi_panel.name = "KiwiPanel"
233 kiwi_panel.answer = "kiwi"
234 kiwi_panel.position = Vector3(33.5, -270, 5.5)
235 giftmap_panel.proxies.append(NodePath("../KiwiPanel"))
236 giftmap_parent.add_child.call_deferred(kiwi_panel)
237
238 var fuzzy_wpl = wpl_prefab.instantiate()
239 fuzzy_wpl.name = "FuzzyWpl"
240 fuzzy_wpl.exit = "the_fuzzy"
241 fuzzy_wpl.senders.append(NodePath("../GongusPanel"))
242 fuzzy_wpl.senders.append(NodePath("../KiwiPanel"))
243 fuzzy_wpl.complete_at = 1
244 giftmap_parent.add_child.call_deferred(fuzzy_wpl)
245
144 if ap.enable_gift_maps.has("The Stellar"): 246 if ap.enable_gift_maps.has("The Stellar"):
145 var hatkirby_panel = panel_prefab.instantiate() 247 var hatkirby_panel = panel_prefab.instantiate()
146 hatkirby_panel.name = "HatkirbyPanel" 248 hatkirby_panel.name = "HatkirbyPanel"
@@ -297,6 +399,7 @@ func _ready():
297 var collectable_prefab = preload("res://objects/nodes/collectable.tscn") 399 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
298 var saver_prefab = preload("res://objects/nodes/saver.tscn") 400 var saver_prefab = preload("res://objects/nodes/saver.tscn")
299 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") 401 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
402 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
300 403
301 var mastery = collectable_prefab.instantiate() 404 var mastery = collectable_prefab.instantiate()
302 mastery.name = "collectable" 405 mastery.name = "collectable"
@@ -314,6 +417,13 @@ func _ready():
314 tpl.nested = true 417 tpl.nested = true
315 mastery.add_child.call_deferred(tpl) 418 mastery.add_child.call_deferred(tpl)
316 419
420 var usl = usl_prefab.instantiate()
421 usl.name = "unlockSetterListenerMastery"
422 usl.key = "icarus_mastery"
423 usl.value = "unlocked"
424 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
425 get_node("/root/scene/Components").add_child.call_deferred(usl)
426
317 var saver = saver_prefab.instantiate() 427 var saver = saver_prefab.instantiate()
318 saver.name = "saver_collectables" 428 saver.name = "saver_collectables"
319 saver.type = "collectables" 429 saver.type = "collectables"
@@ -325,6 +435,7 @@ func _ready():
325 var collectable_prefab = preload("res://objects/nodes/collectable.tscn") 435 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
326 var saver_prefab = preload("res://objects/nodes/saver.tscn") 436 var saver_prefab = preload("res://objects/nodes/saver.tscn")
327 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") 437 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
438 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
328 439
329 var mastery = collectable_prefab.instantiate() 440 var mastery = collectable_prefab.instantiate()
330 mastery.name = "collectable" 441 mastery.name = "collectable"
@@ -343,6 +454,13 @@ func _ready():
343 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31")) 454 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_31"))
344 mastery.add_child.call_deferred(tpl) 455 mastery.add_child.call_deferred(tpl)
345 456
457 var usl = usl_prefab.instantiate()
458 usl.name = "unlockSetterListenerMastery"
459 usl.key = "advanced_mastery"
460 usl.value = "unlocked"
461 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
462 get_node("/root/scene/Components").add_child.call_deferred(usl)
463
346 var saver = saver_prefab.instantiate() 464 var saver = saver_prefab.instantiate()
347 saver.name = "saver_collectables" 465 saver.name = "saver_collectables"
348 saver.type = "collectables" 466 saver.type = "collectables"
@@ -353,6 +471,7 @@ func _ready():
353 if global.map == "the_charismatic": 471 if global.map == "the_charismatic":
354 var collectable_prefab = preload("res://objects/nodes/collectable.tscn") 472 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
355 var saver_prefab = preload("res://objects/nodes/saver.tscn") 473 var saver_prefab = preload("res://objects/nodes/saver.tscn")
474 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
356 475
357 var mastery = collectable_prefab.instantiate() 476 var mastery = collectable_prefab.instantiate()
358 mastery.name = "collectable" 477 mastery.name = "collectable"
@@ -362,6 +481,13 @@ func _ready():
362 mastery.material_override = load("res://assets/materials/gold.material") 481 mastery.material_override = load("res://assets/materials/gold.material")
363 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery) 482 get_node("/root/scene/Components/Collectables").add_child.call_deferred(mastery)
364 483
484 var usl = usl_prefab.instantiate()
485 usl.name = "unlockSetterListenerMastery"
486 usl.key = "charismatic_mastery"
487 usl.value = "unlocked"
488 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
489 get_node("/root/scene/Components").add_child.call_deferred(usl)
490
365 var saver = saver_prefab.instantiate() 491 var saver = saver_prefab.instantiate()
366 saver.name = "saver_collectables" 492 saver.name = "saver_collectables"
367 saver.type = "collectables" 493 saver.type = "collectables"
@@ -373,6 +499,7 @@ func _ready():
373 var collectable_prefab = preload("res://objects/nodes/collectable.tscn") 499 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
374 var saver_prefab = preload("res://objects/nodes/saver.tscn") 500 var saver_prefab = preload("res://objects/nodes/saver.tscn")
375 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") 501 var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn")
502 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
376 503
377 var mastery = collectable_prefab.instantiate() 504 var mastery = collectable_prefab.instantiate()
378 mastery.name = "collectable" 505 mastery.name = "collectable"
@@ -389,6 +516,39 @@ func _ready():
389 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3")) 516 tpl.senders.append(NodePath("/root/scene/Panels/Room_1/panel_3"))
390 mastery.add_child.call_deferred(tpl) 517 mastery.add_child.call_deferred(tpl)
391 518
519 var usl = usl_prefab.instantiate()
520 usl.name = "unlockSetterListenerMastery"
521 usl.key = "crystalline_mastery"
522 usl.value = "unlocked"
523 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
524 get_node("/root/scene/Components").add_child.call_deferred(usl)
525
526 var saver = saver_prefab.instantiate()
527 saver.name = "saver_collectables"
528 saver.type = "collectables"
529 saver.senderGroup.append(NodePath("/root/scene/Components/Collectables"))
530 get_node("/root/scene").add_child.call_deferred(saver)
531
532 # Add the mastery to The Fuzzy.
533 if global.map == "the_fuzzy":
534 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
535 var saver_prefab = preload("res://objects/nodes/saver.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, 2, -20)
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 usl = usl_prefab.instantiate()
546 usl.name = "unlockSetterListenerMastery"
547 usl.key = "fuzzy_mastery"
548 usl.value = "unlocked"
549 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
550 get_node("/root/scene/Components").add_child.call_deferred(usl)
551
392 var saver = saver_prefab.instantiate() 552 var saver = saver_prefab.instantiate()
393 saver.name = "saver_collectables" 553 saver.name = "saver_collectables"
394 saver.type = "collectables" 554 saver.type = "collectables"
@@ -399,6 +559,7 @@ func _ready():
399 if global.map == "the_stellar": 559 if global.map == "the_stellar":
400 var collectable_prefab = preload("res://objects/nodes/collectable.tscn") 560 var collectable_prefab = preload("res://objects/nodes/collectable.tscn")
401 var saver_prefab = preload("res://objects/nodes/saver.tscn") 561 var saver_prefab = preload("res://objects/nodes/saver.tscn")
562 var usl_prefab = preload("res://objects/nodes/listeners/unlockSetterListener.tscn")
402 563
403 var collectables = Node.new() 564 var collectables = Node.new()
404 collectables.name = "Collectables" 565 collectables.name = "Collectables"
@@ -412,6 +573,13 @@ func _ready():
412 collectables.add_child.call_deferred(mastery) 573 collectables.add_child.call_deferred(mastery)
413 get_node("/root/scene/Components").add_child.call_deferred(collectables) 574 get_node("/root/scene/Components").add_child.call_deferred(collectables)
414 575
576 var usl = usl_prefab.instantiate()
577 usl.name = "unlockSetterListenerMastery"
578 usl.key = "stellar_mastery"
579 usl.value = "unlocked"
580 usl.senders.append(NodePath("/root/scene/Components/Collectables/collectable"))
581 get_node("/root/scene/Components").add_child.call_deferred(usl)
582
415 var saver = saver_prefab.instantiate() 583 var saver = saver_prefab.instantiate()
416 saver.name = "saver_collectables" 584 saver.name = "saver_collectables"
417 saver.type = "collectables" 585 saver.type = "collectables"
@@ -585,5 +753,14 @@ func _set_up_invis_wall(x, y, z, sx, sy, sz):
585 get_parent().add_child.call_deferred(newwall) 753 get_parent().add_child.call_deferred(newwall)
586 754
587 755
756func _set_up_mastery_listener(name):
757 var prefab = preload("res://objects/nodes/listeners/unlockReaderListener.tscn")
758 var url = prefab.instantiate()
759 url.name = "unlockReaderListenerMastery_%s" % name
760 url.key = "%s_mastery" % name
761 url.value = "unlocked"
762 get_node("/root/scene/Meshes/Trophies/MasteryListeners").add_child.call_deferred(url)
763
764
588func _process(_dt): 765func _process(_dt):
589 compass.update_rotation(global_rotation.y) 766 compass.update_rotation(global_rotation.y)
diff --git a/apworld/options.py b/apworld/options.py index 7577e0c..a56b40d 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -181,6 +181,26 @@ class VictoryCondition(Choice):
181 option_white_ending = 12 181 option_white_ending = 12
182 182
183 183
184class EndingsRequirement(Range):
185 """The number of endings required to unlock White Ending."""
186 display_name = "Endings Requirement"
187 range_start = 0
188 range_end = 12
189 default = 12
190
191
192class MasteriesRequirement(Range):
193 """The number of masteries required to unlock White Ending.
194
195 There are only 13 masteries in the base game, but some of the other slot options may add more masteries to the
196 world. If the chosen number of masteries is higher than the total in your world, it will be automatically lowered to
197 the maximum."""
198 display_name = "Masteries Requirement"
199 range_start = 0
200 range_end = 18
201 default = 0
202
203
184class TrapPercentage(Range): 204class TrapPercentage(Range):
185 """Replaces junk items with traps, at the specified rate.""" 205 """Replaces junk items with traps, at the specified rate."""
186 display_name = "Trap Percentage" 206 display_name = "Trap Percentage"
@@ -205,4 +225,6 @@ class Lingo2Options(PerGameCommonOptions):
205 strict_purple_ending: StrictPurpleEnding 225 strict_purple_ending: StrictPurpleEnding
206 strict_cyan_ending: StrictCyanEnding 226 strict_cyan_ending: StrictCyanEnding
207 victory_condition: VictoryCondition 227 victory_condition: VictoryCondition
228 endings_requirement: EndingsRequirement
229 masteries_requirement: MasteriesRequirement
208 trap_percentage: TrapPercentage 230 trap_percentage: TrapPercentage
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 67365b7..0cbcdec 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -241,6 +241,8 @@ class Lingo2PlayerLogic:
241 return "The Charismatic" in world.options.enable_gift_maps.value 241 return "The Charismatic" in world.options.enable_gift_maps.value
242 elif game_map.name == "the_crystalline": 242 elif game_map.name == "the_crystalline":
243 return "The Crystalline" in world.options.enable_gift_maps.value 243 return "The Crystalline" in world.options.enable_gift_maps.value
244 elif game_map.name == "the_fuzzy":
245 return "The Fuzzy" in world.options.enable_gift_maps.value
244 elif game_map.name == "the_stellar": 246 elif game_map.name == "the_stellar":
245 return "The Stellar" in world.options.enable_gift_maps.value 247 return "The Stellar" in world.options.enable_gift_maps.value
246 248
@@ -249,6 +251,16 @@ class Lingo2PlayerLogic:
249 self.shuffled_maps = set(game_map.id for game_map in world.static_logic.objects.maps 251 self.shuffled_maps = set(game_map.id for game_map in world.static_logic.objects.maps
250 if should_shuffle_map(game_map)) 252 if should_shuffle_map(game_map))
251 253
254 maximum_masteries = 13 + len(world.options.enable_gift_maps.value)
255 if world.options.enable_icarus:
256 maximum_masteries += 1
257
258 if world.options.masteries_requirement > maximum_masteries:
259 world.options.masteries_requirement.value = maximum_masteries
260
261 if "The Fuzzy" in world.options.enable_gift_maps.value:
262 self.real_items.append("Numbers")
263
252 if self.world.options.shuffle_doors: 264 if self.world.options.shuffle_doors:
253 for progressive in world.static_logic.objects.progressives: 265 for progressive in world.static_logic.objects.progressives:
254 for i in range(0, len(progressive.doors)): 266 for i in range(0, len(progressive.doors)):
@@ -362,18 +374,23 @@ class Lingo2PlayerLogic:
362 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id, 374 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
363 AccessRequirements())) 375 AccessRequirements()))
364 376
377 if world.options.masteries_requirement > 0:
378 event_name = f"{world.static_logic.get_room_object_map_name(mastery)} - Mastery (Collected)"
379 self.event_loc_item_by_room.setdefault(mastery.room_id, {})[event_name] = "Mastery"
380
365 for ending in world.static_logic.objects.endings: 381 for ending in world.static_logic.objects.endings:
366 if world.static_logic.get_room_object_map_id(ending) not in self.shuffled_maps: 382 if world.static_logic.get_room_object_map_id(ending) not in self.shuffled_maps:
367 continue 383 continue
368 384
369 # Don't create a location for your selected ending, and never create a location for White Ending. 385 # Don't create a location for your selected ending. Also don't create a location for White Ending if it's
386 # necessarily in the postgame, i.e. it requires all 12 other endings.
370 if world.options.victory_condition.current_key.removesuffix("_ending").upper() != ending.name\ 387 if world.options.victory_condition.current_key.removesuffix("_ending").upper() != ending.name\
371 and ending.name != "WHITE": 388 and (ending.name != "WHITE" or world.options.endings_requirement < 12):
372 self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, 389 self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id,
373 AccessRequirements())) 390 AccessRequirements()))
374 391
375 event_name = f"{ending.name.capitalize()} Ending (Achieved)" 392 event_name = f"{ending.name.capitalize()} Ending (Achieved)"
376 item_name = event_name 393 item_name = "Ending"
377 394
378 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: 395 if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name:
379 item_name = "Victory" 396 item_name = "Victory"
@@ -520,13 +537,12 @@ class Lingo2PlayerLogic:
520 for room in door.rooms: 537 for room in door.rooms:
521 reqs.rooms.add(self.world.static_logic.get_room_region_name(room)) 538 reqs.rooms.add(self.world.static_logic.get_room_region_name(room))
522 539
523 for ending_id in door.endings: 540 if door.white_ending:
524 ending = self.world.static_logic.objects.endings[ending_id] 541 if self.world.options.endings_requirement > 0:
542 reqs.progressives["Ending"] = self.world.options.endings_requirement.value
525 543
526 if self.world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: 544 if self.world.options.masteries_requirement > 0:
527 reqs.items.add("Victory") 545 reqs.progressives["Mastery"] = self.world.options.masteries_requirement.value
528 else:
529 reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)")
530 546
531 for sub_door_id in door.doors: 547 for sub_door_id in door.doors:
532 sub_reqs = self.get_door_open_reqs(sub_door_id) 548 sub_reqs = self.get_door_open_reqs(sub_door_id)
@@ -588,3 +604,6 @@ class Lingo2PlayerLogic:
588 604
589 if needed > 0: 605 if needed > 0:
590 reqs.letters[l] = max(reqs.letters.get(l, 0), needed) 606 reqs.letters[l] = max(reqs.letters.get(l, 0), needed)
607
608 if any(l.isnumeric() for l in solution):
609 reqs.items.add("Numbers")
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 2546007..8e07b82 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -68,6 +68,7 @@ class Lingo2StaticLogic:
68 self.location_name_groups.setdefault("Keyholders", []).append(location_name) 68 self.location_name_groups.setdefault("Keyholders", []).append(location_name)
69 69
70 self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" 70 self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done"
71 self.item_id_to_name[self.objects.special_ids["Numbers"]] = "Numbers"
71 72
72 for symbol_name in SYMBOL_ITEMS.values(): 73 for symbol_name in SYMBOL_ITEMS.values():
73 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name 74 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name
@@ -105,7 +106,7 @@ class Lingo2StaticLogic:
105 if door.type != data_pb2.DoorType.STANDARD: 106 if door.type != data_pb2.DoorType.STANDARD:
106 return None 107 return None
107 108
108 if len(door.keyholders) > 0 or len(door.endings) > 0 or door.HasField("complete_at"): 109 if len(door.keyholders) > 0 or door.white_ending or door.HasField("complete_at"):
109 return None 110 return None
110 111
111 if len(door.panels) > 4: 112 if len(door.panels) > 4:
diff --git a/data/connections.txtpb b/data/connections.txtpb index 4bec584..8d75dab 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb
@@ -2688,3 +2688,53 @@ connections {
2688 } 2688 }
2689 oneway: true 2689 oneway: true
2690} 2690}
2691connections {
2692 from {
2693 panel {
2694 map: "the_entry"
2695 room: "Starting Room"
2696 name: "Gift Maps"
2697 answer: "gongus"
2698 }
2699 }
2700 to {
2701 room {
2702 map: "the_fuzzy"
2703 name: "Main Area"
2704 }
2705 }
2706 oneway: true
2707}
2708connections {
2709 from {
2710 panel {
2711 map: "the_entry"
2712 room: "Starting Room"
2713 name: "Gift Maps"
2714 answer: "kiwi"
2715 }
2716 }
2717 to {
2718 room {
2719 map: "the_fuzzy"
2720 name: "Main Area"
2721 }
2722 }
2723 oneway: true
2724}
2725connections {
2726 from {
2727 port {
2728 map: "the_fuzzy"
2729 room: "Main Area"
2730 name: "WORLDPORT"
2731 }
2732 }
2733 to {
2734 room {
2735 map: "the_entry"
2736 name: "Starting Room"
2737 }
2738 }
2739 oneway: true
2740}
diff --git a/data/ids.yaml b/data/ids.yaml index 8ef294a..dc82306 100644 --- a/data/ids.yaml +++ b/data/ids.yaml
@@ -2022,6 +2022,30 @@ maps:
2022 panels: 2022 panels:
2023 CACTUS: 410 2023 CACTUS: 410
2024 TAIL: 411 2024 TAIL: 411
2025 the_fuzzy:
2026 rooms:
2027 Main Area:
2028 panels:
2029 ACHIEVES: 3033
2030 BEFORE: 3028
2031 BOTH: 3036
2032 Blank: 3022
2033 CAGED: 3027
2034 COMBINED: 3032
2035 DICE: 3026
2036 FIRST: 3035
2037 FORGED: 3030
2038 LOTTO: 3024
2039 OTHERS: 3031
2040 TOED: 3029
2041 TUTU: 3023
2042 UNVEILED: 3034
2043 WHERETO: 3025
2044 Mastery:
2045 masteries:
2046 MASTERY: 3037
2047 doors:
2048 Black Panels: 3021
2025 the_gallery: 2049 the_gallery:
2026 rooms: 2050 rooms:
2027 Back Room: 2051 Back Room:
@@ -2871,8 +2895,9 @@ maps:
2871 HIDE (2): 1021 2895 HIDE (2): 1021
2872 MORE: 1022 2896 MORE: 1022
2873 doors: 2897 doors:
2874 Left/Turn Door: 984 2898 Left Only Puzzles: 3020
2875 Turn/Shop Door: 985 2899 Shop Only Puzzles: 3019
2900 Turn Only Puzzles: 3018
2876 the_repetitive: 2901 the_repetitive:
2877 rooms: 2902 rooms:
2878 Anti Room: 2903 Anti Room:
@@ -4153,6 +4178,7 @@ special:
4153 Job Symbol: 2798 4178 Job Symbol: 2798
4154 Lingo Symbol: 2799 4179 Lingo Symbol: 2799
4155 Null Symbol: 2800 4180 Null Symbol: 2800
4181 Numbers: 3038
4156 Planet Symbol: 2801 4182 Planet Symbol: 2801
4157 Pyramid Symbol: 2802 4183 Pyramid Symbol: 2802
4158 Question Symbol: 2803 4184 Question Symbol: 2803
diff --git a/data/maps/control_center/doors.txtpb b/data/maps/control_center/doors.txtpb index cac1937..1422301 100644 --- a/data/maps/control_center/doors.txtpb +++ b/data/maps/control_center/doors.txtpb
@@ -86,21 +86,7 @@ doors {
86doors { 86doors {
87 name: "White Ending Door" 87 name: "White Ending Door"
88 type: EVENT 88 type: EVENT
89 # This is the only time a door depends on endings. However, it's nice to do it 89 white_ending: true
90 # this way instead of just checking for ending room access because this lets
91 # us use events, which makes the playthrough more readable.
92 endings: "MINT"
93 endings: "ORANGE"
94 endings: "GREEN"
95 endings: "GRAY"
96 endings: "PLUM"
97 endings: "YELLOW"
98 endings: "GOLD"
99 endings: "BLACK"
100 endings: "CYAN"
101 endings: "PURPLE"
102 endings: "RED"
103 endings: "BLUE"
104} 90}
105doors { 91doors {
106 name: "Repetitive Entrance" 92 name: "Repetitive Entrance"
diff --git a/data/maps/the_entry/metadata.txtpb b/data/maps/the_entry/metadata.txtpb index bdcdf83..da2194b 100644 --- a/data/maps/the_entry/metadata.txtpb +++ b/data/maps/the_entry/metadata.txtpb
@@ -12,9 +12,11 @@ excluded_nodes: "Panels/Back Left/backleft_4_proxied_2"
12# This is a proxy related to the first panel and it doesn't seem useful. 12# This is a proxy related to the first panel and it doesn't seem useful.
13excluded_nodes: "Panels/Entry/entry_proxied_fake" 13excluded_nodes: "Panels/Entry/entry_proxied_fake"
14# The gift map entrance is created by the mod. 14# The gift map entrance is created by the mod.
15custom_nodes: "Components/GiftMapEntrance/GongusPanel"
15custom_nodes: "Components/GiftMapEntrance/HatkirbyPanel" 16custom_nodes: "Components/GiftMapEntrance/HatkirbyPanel"
16custom_nodes: "Components/GiftMapEntrance/IcelyPanel" 17custom_nodes: "Components/GiftMapEntrance/IcelyPanel"
17custom_nodes: "Components/GiftMapEntrance/KirbyPanel" 18custom_nodes: "Components/GiftMapEntrance/KirbyPanel"
19custom_nodes: "Components/GiftMapEntrance/KiwiPanel"
18custom_nodes: "Components/GiftMapEntrance/Panel" 20custom_nodes: "Components/GiftMapEntrance/Panel"
19custom_nodes: "Components/GiftMapEntrance/QPanel" 21custom_nodes: "Components/GiftMapEntrance/QPanel"
20custom_nodes: "Components/GiftMapEntrance/SouveyPanel" 22custom_nodes: "Components/GiftMapEntrance/SouveyPanel"
diff --git a/data/maps/the_entry/rooms/Starting Room.txtpb b/data/maps/the_entry/rooms/Starting Room.txtpb index 9c73766..d01d807 100644 --- a/data/maps/the_entry/rooms/Starting Room.txtpb +++ b/data/maps/the_entry/rooms/Starting Room.txtpb
@@ -55,9 +55,11 @@ panels {
55 # The puzzle solution doesn't matter. We'll change it to the player's name 55 # The puzzle solution doesn't matter. We'll change it to the player's name
56 # for fun. 56 # for fun.
57 symbols: QUESTION 57 symbols: QUESTION
58 proxies { answer: "gongus" path: "Components/GiftMapEntrance/GongusPanel" }
58 proxies { answer: "hatkirby" path: "Components/GiftMapEntrance/HatkirbyPanel" } 59 proxies { answer: "hatkirby" path: "Components/GiftMapEntrance/HatkirbyPanel" }
59 proxies { answer: "icely" path: "Components/GiftMapEntrance/IcelyPanel" } 60 proxies { answer: "icely" path: "Components/GiftMapEntrance/IcelyPanel" }
60 proxies { answer: "kirby" path: "Components/GiftMapEntrance/KirbyPanel" } 61 proxies { answer: "kirby" path: "Components/GiftMapEntrance/KirbyPanel" }
62 proxies { answer: "kiwi" path: "Components/GiftMapEntrance/KiwiPanel" }
61 proxies { answer: "q" path: "Components/GiftMapEntrance/QPanel" } 63 proxies { answer: "q" path: "Components/GiftMapEntrance/QPanel" }
62 proxies { answer: "souvey" path: "Components/GiftMapEntrance/SouveyPanel" } 64 proxies { answer: "souvey" path: "Components/GiftMapEntrance/SouveyPanel" }
63 proxies { answer: "star" path: "Components/GiftMapEntrance/StarPanel" } 65 proxies { answer: "star" path: "Components/GiftMapEntrance/StarPanel" }
diff --git a/data/maps/the_fuzzy/connections.txtpb b/data/maps/the_fuzzy/connections.txtpb new file mode 100644 index 0000000..ea39f34 --- /dev/null +++ b/data/maps/the_fuzzy/connections.txtpb
@@ -0,0 +1,5 @@
1connections {
2 from_room: "Main Area"
3 to_room: "Mastery"
4 door { name: "Mastery Door" }
5}
diff --git a/data/maps/the_fuzzy/doors.txtpb b/data/maps/the_fuzzy/doors.txtpb new file mode 100644 index 0000000..0f89b80 --- /dev/null +++ b/data/maps/the_fuzzy/doors.txtpb
@@ -0,0 +1,12 @@
1doors {
2 name: "Black Panels"
3 type: LOCATION_ONLY
4 panels { room: "Main Area" name: "WHERETO" }
5 panels { room: "Main Area" name: "COMBINED" }
6 location_room: "Main Area"
7}
8doors {
9 name: "Mastery Door"
10 type: EVENT
11 panels { room: "Main Area" name: "OTHERS" }
12}
diff --git a/data/maps/the_fuzzy/metadata.txtpb b/data/maps/the_fuzzy/metadata.txtpb new file mode 100644 index 0000000..b4178c7 --- /dev/null +++ b/data/maps/the_fuzzy/metadata.txtpb
@@ -0,0 +1,4 @@
1display_name: "The Fuzzy"
2type: GIFT_MAP
3# The map's mastery is created at runtime.
4custom_nodes: "Components/Collectables/collectable"
diff --git a/data/maps/the_fuzzy/rooms/Main Area.txtpb b/data/maps/the_fuzzy/rooms/Main Area.txtpb new file mode 100644 index 0000000..9c06df8 --- /dev/null +++ b/data/maps/the_fuzzy/rooms/Main Area.txtpb
@@ -0,0 +1,119 @@
1name: "Main Area"
2panels {
3 name: "Blank"
4 path: "Panels/Room_1/panel_1"
5 clue: ""
6 answer: "2475"
7 symbols: LINGO
8 symbols: QUESTION
9}
10panels {
11 name: "TUTU"
12 path: "Panels/Room_1/panel_2"
13 clue: "tutu"
14 answer: "22"
15 symbols: ZERO
16 symbols: EVAL
17}
18panels {
19 name: "LOTTO"
20 path: "Panels/Room_1/panel_3"
21 clue: "lotto"
22 answer: "22222222"
23 symbols: ZERO
24 symbols: EVAL
25}
26panels {
27 name: "WHERETO"
28 path: "Panels/Room_1/panel_10"
29 clue: "whereto"
30 answer: "sides"
31 symbols: QUESTION
32}
33panels {
34 name: "DICE"
35 path: "Panels/Room_1/panel_11"
36 clue: "dice"
37 answer: "4935"
38 symbols: QUESTION
39}
40panels {
41 name: "CAGED"
42 path: "Panels/Room_1/panel_12"
43 clue: "caged"
44 answer: "31754"
45 symbols: QUESTION
46}
47panels {
48 name: "BEFORE"
49 path: "Panels/Room_1/panel_13"
50 clue: "before"
51 answer: "100"
52 symbols: ZERO
53 symbols: EVAL
54}
55panels {
56 name: "TOED"
57 path: "Panels/Room_1/panel_14"
58 clue: "toed"
59 answer: "108"
60 symbols: ZERO
61 symbols: EVAL
62}
63panels {
64 name: "FORGED"
65 path: "Panels/Room_1/panel_15"
66 clue: "forged"
67 answer: "3016"
68 symbols: ZERO
69 symbols: EVAL
70}
71panels {
72 name: "OTHERS"
73 path: "Panels/Room_1/panel_4"
74 clue: "others"
75 answer: "34390869"
76 symbols: QUESTION
77}
78panels {
79 name: "COMBINED"
80 path: "Panels/Room_1/panel_9"
81 clue: "combined"
82 answer: "added"
83 symbols: SUN
84}
85panels {
86 name: "ACHIEVES"
87 path: "Panels/Room_1/panel_5"
88 clue: "achieves"
89 answer: "4214"
90 symbols: QUESTION
91}
92panels {
93 name: "UNVEILED"
94 path: "Panels/Room_1/panel_6"
95 clue: "unveiled"
96 answer: "12122021"
97 symbols: QUESTION
98}
99panels {
100 name: "FIRST"
101 path: "Panels/Room_1/panel_8"
102 clue: "first"
103 answer: "1"
104 symbols: QUESTION
105}
106panels {
107 name: "BOTH"
108 path: "Panels/Room_1/panel_7"
109 clue: "both"
110 answer: "2"
111 symbols: QUESTION
112}
113ports {
114 name: "WORLDPORT"
115 display_name: "Entrance"
116 path: "Components/Warps/worldport"
117 destination { x: 0 y: 0 z: 9 }
118 rotation: 0
119}
diff --git a/data/maps/the_fuzzy/rooms/Mastery.txtpb b/data/maps/the_fuzzy/rooms/Mastery.txtpb new file mode 100644 index 0000000..bbe8742 --- /dev/null +++ b/data/maps/the_fuzzy/rooms/Mastery.txtpb
@@ -0,0 +1,5 @@
1name: "Mastery"
2masteries {
3 name: "MASTERY"
4 path: "Components/Collectables/collectable"
5}
diff --git a/data/maps/the_relentless/doors.txtpb b/data/maps/the_relentless/doors.txtpb index 11f6369..727599f 100644 --- a/data/maps/the_relentless/doors.txtpb +++ b/data/maps/the_relentless/doors.txtpb
@@ -1,17 +1,42 @@
1doors { 1doors {
2 name: "Left/Turn Door" 2 name: "Turn Only Puzzles"
3 type: LOCATION_ONLY
4 panels { room: "Turn Room" name: "HIDE (1)" }
5 panels { room: "Turn Room" name: "HIDE (2)" }
6 panels { room: "Turn Room" name: "MORE" }
7 location_room: "Turn Room"
8}
9doors {
10 name: "Shop Only Puzzles"
11 type: LOCATION_ONLY
12 panels { room: "Shop Room" name: "LEFT (1)" }
13 panels { room: "Shop Room" name: "LEFT (2)" }
14 panels { room: "Shop Room" name: "EXIT (1)" }
15 panels { room: "Shop Room" name: "EXIT (2)" }
16 panels { room: "Shop Room" name: "EXIT (3)" }
17 location_room: "Shop Room"
18}
19doors {
20 name: "Left Only Puzzles"
3 type: LOCATION_ONLY 21 type: LOCATION_ONLY
4 panels { room: "Left Room" name: "HIDE" } 22 panels { room: "Left Room" name: "HIDE" }
5 panels { room: "Left Room" name: "LEFT" } 23 panels { room: "Left Room" name: "LEFT" }
6 panels { room: "Left Room" name: "MORE" } 24 panels { room: "Left Room" name: "MORE" }
25 location_room: "Left Room"
26}
27doors {
28 name: "Left/Turn Door"
29 type: EVENT
30 panels { room: "Left Room" name: "HIDE" }
31 panels { room: "Left Room" name: "LEFT" }
32 panels { room: "Left Room" name: "MORE" }
7 panels { room: "Turn Room" name: "HIDE (1)" } 33 panels { room: "Turn Room" name: "HIDE (1)" }
8 panels { room: "Turn Room" name: "HIDE (2)" } 34 panels { room: "Turn Room" name: "HIDE (2)" }
9 panels { room: "Turn Room" name: "MORE" } 35 panels { room: "Turn Room" name: "MORE" }
10 location_room: "Turn Room"
11} 36}
12doors { 37doors {
13 name: "Turn/Shop Door" 38 name: "Turn/Shop Door"
14 type: LOCATION_ONLY 39 type: EVENT
15 panels { room: "Turn Room" name: "HIDE (1)" } 40 panels { room: "Turn Room" name: "HIDE (1)" }
16 panels { room: "Turn Room" name: "HIDE (2)" } 41 panels { room: "Turn Room" name: "HIDE (2)" }
17 panels { room: "Turn Room" name: "MORE" } 42 panels { room: "Turn Room" name: "MORE" }
@@ -20,7 +45,6 @@ doors {
20 panels { room: "Shop Room" name: "EXIT (1)" } 45 panels { room: "Shop Room" name: "EXIT (1)" }
21 panels { room: "Shop Room" name: "EXIT (2)" } 46 panels { room: "Shop Room" name: "EXIT (2)" }
22 panels { room: "Shop Room" name: "EXIT (3)" } 47 panels { room: "Shop Room" name: "EXIT (3)" }
23 location_room: "Turn Room"
24} 48}
25doors { 49doors {
26 name: "All Doors" 50 name: "All Doors"
diff --git a/data/metadata.txtpb b/data/metadata.txtpb index c5c48c2..eb3fe54 100644 --- a/data/metadata.txtpb +++ b/data/metadata.txtpb
@@ -52,3 +52,5 @@ special_names: "Anti W"
52special_names: "Anti X" 52special_names: "Anti X"
53special_names: "Anti Y" 53special_names: "Anti Y"
54special_names: "Anti Z" 54special_names: "Anti Z"
55# Numbers for The Fuzzy
56special_names: "Numbers"
diff --git a/proto/data.proto b/proto/data.proto index 0f668f2..be18100 100644 --- a/proto/data.proto +++ b/proto/data.proto
@@ -149,7 +149,7 @@ message Door {
149 repeated KeyholderAnswer keyholders = 13; 149 repeated KeyholderAnswer keyholders = 13;
150 repeated uint64 rooms = 14; 150 repeated uint64 rooms = 14;
151 repeated uint64 doors = 15; 151 repeated uint64 doors = 15;
152 repeated uint64 endings = 16; 152 optional bool white_ending = 16;
153 optional bool double_letters = 18; 153 optional bool double_letters = 18;
154 repeated string senders = 19; 154 repeated string senders = 19;
155 155
diff --git a/proto/human.proto b/proto/human.proto index df33a5e..a5f7a7c 100644 --- a/proto/human.proto +++ b/proto/human.proto
@@ -104,7 +104,7 @@ message HumanDoor {
104 repeated KeyholderIdentifier keyholders = 10; 104 repeated KeyholderIdentifier keyholders = 10;
105 repeated RoomIdentifier rooms = 11; 105 repeated RoomIdentifier rooms = 11;
106 repeated DoorIdentifier doors = 12; 106 repeated DoorIdentifier doors = 12;
107 repeated string endings = 13; 107 optional bool white_ending = 13;
108 optional bool double_letters = 15; 108 optional bool double_letters = 15;
109 109
110 // Sender nodes to be added to the list of requirements for triggering the 110 // Sender nodes to be added to the list of requirements for triggering the
diff --git a/tools/datapacker/main.cpp b/tools/datapacker/main.cpp index e3ab100..bda4ee4 100644 --- a/tools/datapacker/main.cpp +++ b/tools/datapacker/main.cpp
@@ -411,8 +411,8 @@ class DataPacker {
411 container_.FindOrAddDoor(map_name, di.name(), current_map_name)); 411 container_.FindOrAddDoor(map_name, di.name(), current_map_name));
412 } 412 }
413 413
414 for (const std::string& ending_name : h_door.endings()) { 414 if (h_door.has_white_ending()) {
415 door.add_endings(container_.FindOrAddEnding(ending_name)); 415 door.set_white_ending(h_door.white_ending());
416 } 416 }
417 417
418 if (h_door.has_control_center_color()) { 418 if (h_door.has_control_center_color()) {
diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp index 5a1c095..407d951 100644 --- a/tools/validator/human_processor.cpp +++ b/tools/validator/human_processor.cpp
@@ -373,11 +373,6 @@ class HumanProcessor {
373 DoorInfo& other_door_info = info_.doors[complete_door_identifier]; 373 DoorInfo& other_door_info = info_.doors[complete_door_identifier];
374 other_door_info.doors_referenced_by.push_back(door_identifier); 374 other_door_info.doors_referenced_by.push_back(door_identifier);
375 } 375 }
376
377 for (const std::string& ei : h_door.endings()) {
378 EndingInfo& ending_info = info_.endings[ei];
379 ending_info.doors_referenced_by.push_back(door_identifier);
380 }
381 } 376 }
382 377
383 void ProcessConnectionsFile(std::filesystem::path path, 378 void ProcessConnectionsFile(std::filesystem::path path,
diff --git a/tools/validator/structs.h b/tools/validator/structs.h index a6787cf..51215e9 100644 --- a/tools/validator/structs.h +++ b/tools/validator/structs.h
@@ -107,8 +107,6 @@ struct LetterInfo {
107struct EndingInfo { 107struct EndingInfo {
108 std::vector<RoomIdentifier> defined_in; 108 std::vector<RoomIdentifier> defined_in;
109 bool has_id = false; 109 bool has_id = false;
110
111 std::vector<DoorIdentifier> doors_referenced_by;
112}; 110};
113 111
114struct PanelNameInfo { 112struct PanelNameInfo {
diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp index 43d842d..e1fc138 100644 --- a/tools/validator/validator.cpp +++ b/tools/validator/validator.cpp
@@ -111,7 +111,7 @@ class Validator {
111 return false; 111 return false;
112 } 112 }
113 113
114 if (h_door.keyholders_size() > 0 || h_door.endings_size() > 0 || 114 if (h_door.keyholders_size() > 0 || h_door.white_ending() ||
115 h_door.complete_at() > 0) { 115 h_door.complete_at() > 0) {
116 return true; 116 return true;
117 } 117 }
@@ -488,12 +488,6 @@ class Validator {
488 std::cout << "Ending " << ending_name 488 std::cout << "Ending " << ending_name
489 << " has no definition, but was referenced:" << std::endl; 489 << " has no definition, but was referenced:" << std::endl;
490 490
491 for (const DoorIdentifier& door_identifier :
492 ending_info.doors_referenced_by) {
493 std::cout << " DOOR " << door_identifier.ShortDebugString()
494 << std::endl;
495 }
496
497 if (ending_info.has_id) { 491 if (ending_info.has_id) {
498 std::cout << " An AP ID is present." << std::endl; 492 std::cout << " An AP ID is present." << std::endl;
499 } 493 }