From 6af543ba049e3ba880b113907cd5222b205b8c05 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Mon, 8 Sep 2025 12:42:31 -0400 Subject: Add cyan door behavior option --- apworld/__init__.py | 1 + apworld/options.py | 22 ++++++++++++ apworld/player_logic.py | 36 +++++++++++++------ data/door_groups.txtpb | 52 ++++++++++++++++++++++++++++ data/ids.yaml | 1 + data/maps/the_darkroom/doors.txtpb | 4 +-- data/maps/the_graveyard/doors.txtpb | 2 ++ data/maps/the_parthenon/doors.txtpb | 7 ++++ data/maps/the_unkempt/doors.txtpb | 2 ++ data/maps/the_unkempt/rooms/Right Area.txtpb | 3 +- data/maps/the_unyielding/doors.txtpb | 3 ++ proto/data.proto | 3 ++ 12 files changed, 122 insertions(+), 14 deletions(-) diff --git a/apworld/__init__.py b/apworld/__init__.py index d6a3acb..c45e8b3 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py @@ -66,6 +66,7 @@ class Lingo2World(World): def fill_slot_data(self): slot_options = [ + "cyan_door_behavior", "daedalus_roof_access", "keyholder_sanity", "shuffle_control_center_colors", diff --git a/apworld/options.py b/apworld/options.py index dbf09e7..2197b0f 100644 --- a/apworld/options.py +++ b/apworld/options.py @@ -48,6 +48,27 @@ class KeyholderSanity(Toggle): display_name = "Keyholder Sanity" +class CyanDoorBehavior(Choice): + """ + Cyan-colored doors usually only open upon unlocking double letters. Some panels also only appear upon unlocking + double letters. This option determines how these unlocks should behave. + + - **Collect H2**: In the base game, H2 is the first double letter you are intended to collect, so cyan doors only + open when you collect the H2 pickup in The Repetitive. Collecting the actual pickup is still required even with + remote letter shuffle enabled. + - **Any Double Letter**: Cyan doors will open when you have unlocked any cyan letter on your keyboard. In letter + shuffle, this means receiving a cyan letter, not picking up a cyan letter collectable. + - **Item**: Cyan doors will be grouped together in a single item. + + Note that some cyan doors are impacted by door shuffle (e.g. the entrance to The Tower). When door shuffle is + enabled, these doors won't be affected by the value of this option. + """ + display_name = "Cyan Door Behavior" + option_collect_h2 = 0 + option_any_double_letter = 1 + option_item = 2 + + class DaedalusRoofAccess(Toggle): """ If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus @@ -82,5 +103,6 @@ class Lingo2Options(PerGameCommonOptions): shuffle_control_center_colors: ShuffleControlCenterColors shuffle_letters: ShuffleLetters keyholder_sanity: KeyholderSanity + cyan_door_behavior: CyanDoorBehavior daedalus_roof_access: DaedalusRoofAccess victory_condition: VictoryCondition diff --git a/apworld/player_logic.py b/apworld/player_logic.py index ce9a4e5..317d13b 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py @@ -3,7 +3,7 @@ from enum import IntEnum, auto from .generated import data_pb2 as data_pb2 from typing import TYPE_CHECKING, NamedTuple -from .options import VictoryCondition, ShuffleLetters +from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior if TYPE_CHECKING: from . import Lingo2World @@ -124,11 +124,13 @@ class Lingo2PlayerLogic: self.real_items.append(progressive.name) for door_group in world.static_logic.objects.door_groups: - if door_group.type == data_pb2.DoorGroupType.CONNECTOR and not self.world.options.shuffle_doors: - continue - - if (door_group.type == data_pb2.DoorGroupType.COLOR_CONNECTOR and - not self.world.options.shuffle_control_center_colors): + if door_group.type == data_pb2.DoorGroupType.CONNECTOR: + if not self.world.options.shuffle_doors: + continue + elif door_group.type == data_pb2.DoorGroupType.COLOR_CONNECTOR: + if not self.world.options.shuffle_control_center_colors: + continue + else: continue for door in door_group.doors: @@ -157,6 +159,19 @@ class Lingo2PlayerLogic: self.item_by_door[door.id] = (door_item_name, 1) self.real_items.append(door_item_name) + # We handle cyan_door_behavior = Item after door shuffle, because cyan doors that are impacted by door shuffle + # should be exempt from cyan_door_behavior. + if world.options.cyan_door_behavior == CyanDoorBehavior.option_item: + for door_group in world.static_logic.objects.door_groups: + if door_group.type != data_pb2.DoorGroupType.CYAN_DOORS: + continue + + for door in door_group.doors: + if not door in self.item_by_door: + self.item_by_door[door] = (door_group.name, 1) + + self.real_items.append(door_group.name) + for door in world.static_logic.objects.doors: if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]: self.locations_by_room.setdefault(door.room_id, []).append(PlayerLocation(door.ap_id, @@ -295,12 +310,13 @@ class Lingo2PlayerLogic: self.add_solution_reqs(reqs, door.control_center_color) if door.double_letters: - if self.world.options.shuffle_letters in [ShuffleLetters.option_vanilla, - ShuffleLetters.option_vanilla_cyan]: + if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: reqs.rooms.add("The Repetitive - Main Room") - elif self.world.options.shuffle_letters in [ShuffleLetters.option_progressive, - ShuffleLetters.option_item_cyan]: + elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: reqs.cyans = True + elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: + # There shouldn't be any locations that are cyan doors. + pass for keyholder_uses in door.keyholders: key_name = keyholder_uses.key.upper() diff --git a/data/door_groups.txtpb b/data/door_groups.txtpb index ca8ce54..bc8cdf6 100644 --- a/data/door_groups.txtpb +++ b/data/door_groups.txtpb @@ -82,3 +82,55 @@ door_groups { name: "Digital Entrance" } } +door_groups { + name: "Cyan Doors" + type: CYAN_DOORS + doors { + map: "daedalus" + name: "Eye Painting" + } + doors { + map: "the_bearer" + name: "Butterfly Entrance" + } + doors { + map: "the_darkroom" + name: "Double Letter Panel Blockers" + } + doors { + map: "the_entry" + name: "Starting Room West Wall North Door" + } + doors { + map: "the_entry" + name: "Flipped Pyramid Area Entrance" + } + doors { + map: "the_entry" + name: "Near D Room Painting" + } + doors { + map: "the_graveyard" + name: "Double Letters" + } + doors { + map: "the_great" + name: "Tower Entrance" + } + doors { + map: "the_parthenon" + name: "Double Letters" + } + doors { + map: "the_unkempt" + name: "Cyan Doors" + } + doors { + map: "the_unkempt" + name: "Control Center Orange Door" + } + doors { + map: "the_unyielding" + name: "Cyan Doors" + } +} diff --git a/data/ids.yaml b/data/ids.yaml index bd6cbc1..4e2cd66 100644 --- a/data/ids.yaml +++ b/data/ids.yaml @@ -3846,5 +3846,6 @@ door_groups: Control Center Orange Doors: 2786 Control Center Purple Doors: 2785 Control Center White Doors: 2784 + Cyan Doors: 2789 The Entry - Repetitive Entrance: 2782 The Repetitive - Plaza Entrance: 2783 diff --git a/data/maps/the_darkroom/doors.txtpb b/data/maps/the_darkroom/doors.txtpb index d7094ae..047c7d0 100644 --- a/data/maps/the_darkroom/doors.txtpb +++ b/data/maps/the_darkroom/doors.txtpb @@ -2,8 +2,8 @@ doors { name: "Double Letter Panel Blockers" type: EVENT - #receivers: "Panels/Room 1/panel_3/visibilityListener" - #receivers: "Panels/Room 2/panel_3/visibilityListener" + receivers: "Panels/Room 1/panel_3/visibilityListener" + receivers: "Panels/Room 2/panel_3/visibilityListener" double_letters: true } doors { diff --git a/data/maps/the_graveyard/doors.txtpb b/data/maps/the_graveyard/doors.txtpb index 5e5e929..a10d8f6 100644 --- a/data/maps/the_graveyard/doors.txtpb +++ b/data/maps/the_graveyard/doors.txtpb @@ -19,5 +19,7 @@ doors { doors { name: "Double Letters" type: EVENT + receivers: "Panels/panel_3/teleportListener" + receivers: "Components/Paintings/omrt/teleportListener" double_letters: true } diff --git a/data/maps/the_parthenon/doors.txtpb b/data/maps/the_parthenon/doors.txtpb index bb57d12..5187aea 100644 --- a/data/maps/the_parthenon/doors.txtpb +++ b/data/maps/the_parthenon/doors.txtpb @@ -1,6 +1,13 @@ doors { name: "Double Letters" type: EVENT + receivers: "Components/Doors/entry_11" + receivers: "Components/Doors/entry_5" + receivers: "Components/Doors/entry_6" + receivers: "Components/Doors/entry_7" + receivers: "Components/Doors/entry_8" + receivers: "Components/Doors/entry_9" + receivers: "Components/Doors/entry_10" double_letters: true } doors { diff --git a/data/maps/the_unkempt/doors.txtpb b/data/maps/the_unkempt/doors.txtpb index 9a13c82..2349913 100644 --- a/data/maps/the_unkempt/doors.txtpb +++ b/data/maps/the_unkempt/doors.txtpb @@ -21,6 +21,7 @@ doors { doors { name: "Cyan Doors" type: EVENT + receivers: "Components/Doors/entry_12" double_letters: true } doors { @@ -67,6 +68,7 @@ doors { type: CONTROL_CENTER_COLOR receivers: "Components/Doors/entry_6" receivers: "Components/Doors/entry_13" + receivers: "Panels/Assorted/panel_1/teleportListener" control_center_color: "orange" double_letters: true } diff --git a/data/maps/the_unkempt/rooms/Right Area.txtpb b/data/maps/the_unkempt/rooms/Right Area.txtpb index 1475fb0..03d7cea 100644 --- a/data/maps/the_unkempt/rooms/Right Area.txtpb +++ b/data/maps/the_unkempt/rooms/Right Area.txtpb @@ -159,6 +159,5 @@ panels { clue: "color" answer: "orange" symbols: EXAMPLE - # TODO: This is hidden in-game until double letters are unlocked AND "orange" - # is entered in the control center. + required_door { name: "Control Center Orange Door" } } diff --git a/data/maps/the_unyielding/doors.txtpb b/data/maps/the_unyielding/doors.txtpb index b9d0d77..a3c3999 100644 --- a/data/maps/the_unyielding/doors.txtpb +++ b/data/maps/the_unyielding/doors.txtpb @@ -499,5 +499,8 @@ doors { doors { name: "Cyan Doors" type: EVENT + receivers: "Components/Doors/entry_4" + receivers: "Panels/Miscellaneous/entry_2/teleportListener" + receivers: "Panels/Miscellaneous/entry_3/teleportListener" double_letters: true } diff --git a/proto/data.proto b/proto/data.proto index 84d14ef..24b98fe 100644 --- a/proto/data.proto +++ b/proto/data.proto @@ -41,6 +41,9 @@ enum DoorGroupType { // connections are not shuffled, but are not items at all when control center // colors are not shuffled. COLOR_CONNECTOR = 2; + + // Groups with this type become an item if cyan door behavior is set to item. + CYAN_DOORS = 3; } enum AxisDirection { -- cgit 1.4.1