From 740b76ebd56292152f396476f9cdd905c500e245 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 18 Apr 2024 11:45:33 -0500 Subject: [PATCH] Lingo: The Pilgrim Update (#2884) * An option was added to enable or disable the pilgrimage, and it defaults to disabled. When disabled, the client will prevent you from performing a pilgrimage (i.e. the yellow border will not appear when you enter the 1 sunwarp). The sun painting is added to the item pool when pilgrimage is disabled, as otherwise there is no way into the Pilgrim Antechamber. Inversely, the sun painting is no longer in the item pool when pilgrimage is enabled (even if door shuffle is on), requiring you to perform a pilgrimage to get to that room. * The canonical pilgrimage has been deprecated. Instead, there is logic for determining whether a pilgrimage is possible. * Two options were added that allow the player to decide whether paintings and/or Crossroads - Roof Access are permitted during the pilgrimage. Both default to disabled. These options apply both to logical expectations in the generator, and are also enforced by the game client. * An option was added to control how sunwarps are accessed. The default is for them to always be accessible, like in the base game. It is also possible to disable them entirely (which is not possible when pilgrimage is enabled), or lock them behind items similar to door shuffle. It can either be one item that unlocks all sunwarps at the same time, six progressive items that unlock the sunwarps from 1 to 6, or six individual items that unlock the sunwarps in any order. This option is independent from door shuffle. * An option was added that shuffles sunwarps. This acts similarly to painting shuffle. The 12 sunwarps are re-ordered and re-paired. Sunwarps that were previously entrances or exits do not need to stay entrances or exits. Performing a pilgrimage requires proceeding through the sunwarps in the new order, rather than the original one. * Pilgrimage was added as a win condition. It requires you to solve the blue PILGRIM panel in the Pilgrim Antechamber. --- worlds/lingo/__init__.py | 6 +- worlds/lingo/data/LL1.yaml | 587 ++++++++++++++++------- worlds/lingo/data/generated.dat | Bin 130691 -> 135088 bytes worlds/lingo/data/ids.yaml | 35 +- worlds/lingo/datatypes.py | 19 +- worlds/lingo/items.py | 39 +- worlds/lingo/locations.py | 2 +- worlds/lingo/options.py | 47 +- worlds/lingo/player_logic.py | 104 ++-- worlds/lingo/regions.py | 105 +++- worlds/lingo/rules.py | 4 + worlds/lingo/static_logic.py | 8 +- worlds/lingo/test/TestOptions.py | 20 + worlds/lingo/test/TestPilgrimage.py | 114 +++++ worlds/lingo/test/TestSunwarps.py | 213 ++++++++ worlds/lingo/utils/pickle_static_data.py | 96 ++-- worlds/lingo/utils/validate_config.rb | 48 +- 17 files changed, 1151 insertions(+), 296 deletions(-) create mode 100644 worlds/lingo/test/TestPilgrimage.py create mode 100644 worlds/lingo/test/TestSunwarps.py diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index 25be1669..537b149f 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -132,7 +132,8 @@ class LingoWorld(World): def fill_slot_data(self): slot_options = [ "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", - "mastery_achievements", "level_2_requirement", "location_checks", "early_color_hallways" + "enable_pilgrimage", "sunwarp_access", "mastery_achievements", "level_2_requirement", "location_checks", + "early_color_hallways", "pilgrimage_allows_roof_access", "pilgrimage_allows_paintings", "shuffle_sunwarps" ] slot_data = { @@ -143,6 +144,9 @@ class LingoWorld(World): if self.options.shuffle_paintings: slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping + if self.options.shuffle_sunwarps: + slot_data["sunwarp_permutation"] = self.player_logic.sunwarp_mapping + return slot_data def get_filler_item_name(self) -> str: diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index f2d2a9ff..c33cad39 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -54,6 +54,8 @@ # this door will open the doors listed here. # - painting_id: An internal ID of a painting that should be moved upon # receiving this door. + # - warp_id: An internal ID or IDs of warps that should be disabled + # until receiving this door. # - panels: These are the panels that canonically open this door. If # there is only one panel for the door, then that panel is a # check. If there is more than one panel, then that entire @@ -73,10 +75,6 @@ # will be covered by a single item. # - include_reduce: Door checks are assumed to be EXCLUDED when reduce checks # is on. This option includes the check anyway. - # - junk_item: If on, the item for this door will be considered a junk - # item instead of a progression item. Only use this for - # doors that could never gate progression regardless of - # options and state. # - event: Denotes that the door is event only. This is similar to # setting both skip_location and skip_item. # @@ -106,9 +104,42 @@ # Use "req_blocked_when_no_doors" instead if it would be # fine in door shuffle mode. # - move: Denotes that the painting is able to move. + # + # sunwarps is an array of sunwarps in the room. This is used for sunwarp + # shuffling. + # - dots: The number of dots on this sunwarp. + # - direction: "enter" or "exit" + # - entrance_indicator_pos: Coordinates for where the entrance indicator + # should be placed if this becomes an entrance. + # - orientation: One of north/south/east/west. Starting Room: entrances: - Menu: True + Menu: + warp: True + Outside The Wise: + painting: True + Rhyme Room (Circle): + painting: True + Rhyme Room (Target): + painting: True + Wondrous Lobby: + painting: True + Orange Tower Third Floor: + painting: True + Color Hunt: + painting: True + Owl Hallway: + painting: True + The Wondrous: + room: The Wondrous + door: Exit + painting: True + Orange Tower Sixth Floor: + painting: True + Orange Tower Basement: + painting: True + The Colorful: + painting: True panels: HI: id: Entry Room/Panel_hi_hi @@ -416,7 +447,7 @@ The Traveled: door: Traveled Entrance Roof: True # through the sunwarp - Outside The Undeterred: # (NOTE: used in hardcoded pilgrimage) + Outside The Undeterred: room: Outside The Undeterred door: Green Painting painting: True @@ -500,6 +531,11 @@ paintings: - id: maze_painting orientation: west + sunwarps: + - dots: 1 + direction: enter + entrance_indicator_pos: [18, 2.5, -17.01] + orientation: north Dead End Area: entrances: Hidden Room: @@ -526,12 +562,52 @@ paintings: - id: smile_painting_6 orientation: north - Pilgrim Antechamber: - # Let's not shuffle the paintings yet. + Sunwarps: + # This is a special, meta-ish room. entrances: - # The pilgrimage is hardcoded in rules.py - Starting Room: - door: Sun Painting + Menu: True + doors: + 1 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_1 + door_group: Sunwarps + skip_location: True + item_name: "1 Sunwarp" + 2 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_2 + door_group: Sunwarps + skip_location: True + item_name: 2 Sunwarp + 3 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_3 + door_group: Sunwarps + skip_location: True + item_name: "3 Sunwarp" + 4 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_4 + door_group: Sunwarps + skip_location: True + item_name: 4 Sunwarp + 5 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_5 + door_group: Sunwarps + skip_location: True + item_name: "5 Sunwarp" + 6 Sunwarp: + warp_id: Teleporter Warps/Sunwarp_enter_6 + door_group: Sunwarps + skip_location: True + item_name: "6 Sunwarp" + progression: + Progressive Pilgrimage: + - 1 Sunwarp + - 2 Sunwarp + - 3 Sunwarp + - 4 Sunwarp + - 5 Sunwarp + - 6 Sunwarp + Pilgrim Antechamber: + # The entrances to this room are special. When pilgrimage is enabled, we use a special access rule to determine + # whether a pilgrimage can succeed. When pilgrimage is disabled, the sun painting will be added to the pool. panels: HOT CRUST: id: Lingo Room/Panel_shortcut @@ -541,6 +617,7 @@ id: Lingo Room/Panel_pilgrim colors: blue tag: midblue + check: True MASTERY: id: Master Room/Panel_mastery_mastery14 tag: midwhite @@ -636,11 +713,19 @@ - THIS Crossroads: entrances: - Hub Room: True # The sunwarp means that we never need the ORDER door - Color Hallways: True + Hub Room: + - room: Sunwarps + door: 1 Sunwarp + sunwarp: True + - room: Hub Room + door: Crossroads Entrance + Color Hallways: + warp: True The Tenacious: door: Tenacious Entrance - Orange Tower Fourth Floor: True # through IRK HORN + Orange Tower Fourth Floor: + - warp: True # through IRK HORN + - door: Tower Entrance Amen Name Area: room: Lost Area door: Exit @@ -760,7 +845,6 @@ - SWORD Eye Wall: id: Shuffle Room Area Doors/Door_behind - junk_item: True door_group: Crossroads Doors panels: - BEND HI @@ -795,6 +879,11 @@ door: Eye Wall - id: smile_painting_4 orientation: south + sunwarps: + - dots: 1 + direction: exit + entrance_indicator_pos: [ -17, 2.5, -41.01 ] + orientation: north Lost Area: entrances: Outside The Agreeable: @@ -1036,11 +1125,12 @@ - LEAF - FEEL Outside The Agreeable: - # Let's ignore the blue warp thing for now because the lookout is a dead - # end. Later on it could be filler checks. entrances: - # We don't have to list Lost Area because of Crossroads. - Crossroads: True + Crossroads: + warp: True + Lost Area: + room: Lost Area + door: Exit The Tenacious: door: Tenacious Entrance The Agreeable: @@ -1053,12 +1143,11 @@ Starting Room: door: Painting Shortcut painting: True - Hallway Room (2): True - Hallway Room (3): True - Hallway Room (4): True + Hallway Room (1): + warp: True Hedge Maze: True # through the door to the sectioned-off part of the hedge maze - Cellar: - door: Lookout Entrance + Compass Room: + warp: True panels: MASSACRED: id: Palindrome Room/Panel_massacred_sacred @@ -1104,11 +1193,6 @@ required_door: room: Outside The Undeterred door: Fives - OUT: - id: Hallway Room/Panel_out_out - check: True - exclude_reduce: True - tag: midwhite HIDE: id: Maze Room/Panel_hide_seek_4 colors: black @@ -1117,52 +1201,6 @@ id: Maze Room/Panel_daze_maze colors: purple tag: midpurp - WALL: - id: Hallway Room/Panel_castle_1 - colors: blue - tag: quad bot blue - link: qbb CASTLE - KEEP: - id: Hallway Room/Panel_castle_2 - colors: blue - tag: quad bot blue - link: qbb CASTLE - BAILEY: - id: Hallway Room/Panel_castle_3 - colors: blue - tag: quad bot blue - link: qbb CASTLE - TOWER: - id: Hallway Room/Panel_castle_4 - colors: blue - tag: quad bot blue - link: qbb CASTLE - NORTH: - id: Cross Room/Panel_north_missing - colors: green - tag: forbid - required_panel: - - room: Outside The Bold - panel: SOUND - - room: Outside The Bold - panel: YEAST - - room: Outside The Bold - panel: WET - DIAMONDS: - id: Cross Room/Panel_diamonds_missing - colors: green - tag: forbid - required_room: Suits Area - FIRE: - id: Cross Room/Panel_fire_missing - colors: green - tag: forbid - required_room: Elements Area - WINTER: - id: Cross Room/Panel_winter_missing - colors: green - tag: forbid - required_room: Orange Tower Fifth Floor doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_massacred_sacred @@ -1194,15 +1232,49 @@ panels: - room: Color Hunt panel: PURPLE - Hallway Door: - id: Red Blue Purple Room Area Doors/Door_room_2 - door_group: Hallway Room Doors - location_name: Hallway Room - First Room - panels: - - WALL - - KEEP - - BAILEY - - TOWER + paintings: + - id: eyes_yellow_painting + orientation: east + sunwarps: + - dots: 6 + direction: enter + entrance_indicator_pos: [ 3, 2.5, -55.01 ] + orientation: north + Compass Room: + entrances: + Outside The Agreeable: + warp: True + Cellar: + door: Lookout Entrance + warp: True + panels: + NORTH: + id: Cross Room/Panel_north_missing + colors: green + tag: forbid + required_panel: + - room: Outside The Bold + panel: SOUND + - room: Outside The Bold + panel: YEAST + - room: Outside The Bold + panel: WET + DIAMONDS: + id: Cross Room/Panel_diamonds_missing + colors: green + tag: forbid + required_room: Suits Area + FIRE: + id: Cross Room/Panel_fire_missing + colors: green + tag: forbid + required_room: Elements Area + WINTER: + id: Cross Room/Panel_winter_missing + colors: green + tag: forbid + required_room: Orange Tower Fifth Floor + doors: Lookout Entrance: id: Cross Room Doors/Door_missing location_name: Outside The Agreeable - Lookout Panels @@ -1212,21 +1284,8 @@ - DIAMONDS - FIRE paintings: - - id: panda_painting - orientation: south - - id: eyes_yellow_painting - orientation: east - id: pencil_painting7 orientation: north - progression: - Progressive Hallway Room: - - Hallway Door - - room: Hallway Room (2) - door: Exit - - room: Hallway Room (3) - door: Exit - - room: Hallway Room (4) - door: Exit Dread Hallway: entrances: Outside The Agreeable: @@ -1321,7 +1380,8 @@ Hub Room: room: Hub Room door: Shortcut to Hedge Maze - Color Hallways: True + Color Hallways: + warp: True The Agreeable: room: The Agreeable door: Shortcut to Hedge Maze @@ -1465,7 +1525,8 @@ orientation: north The Fearless (First Floor): entrances: - The Perceptive: True + The Perceptive: + warp: True panels: SPAN: id: Naps Room/Panel_naps_span @@ -1508,6 +1569,7 @@ The Fearless (First Floor): room: The Fearless (First Floor) door: Second Floor + warp: True panels: NONE: id: Naps Room/Panel_one_many @@ -1557,6 +1619,7 @@ The Fearless (First Floor): room: The Fearless (Second Floor) door: Third Floor + warp: True panels: Achievement: id: Countdown Panels/Panel_fearless_fearless @@ -1585,7 +1648,8 @@ Hedge Maze: room: Hedge Maze door: Observant Entrance - The Incomparable: True + The Incomparable: + warp: True panels: Achievement: id: Countdown Panels/Panel_observant_observant @@ -1709,7 +1773,8 @@ - SIX The Incomparable: entrances: - The Observant: True # Assuming that access to The Observant includes access to the right entrance + The Observant: + warp: True Eight Room: True Eight Alcove: door: Eight Door @@ -1911,9 +1976,11 @@ Outside The Wanderer: room: Outside The Wanderer door: Tower Entrance + warp: True Orange Tower Second Floor: room: Orange Tower door: Second Floor + warp: True Directional Gallery: door: Salt Pepper Door Roof: True # through the sunwarp @@ -1944,15 +2011,23 @@ - SALT - room: Directional Gallery panel: PEPPER + sunwarps: + - dots: 4 + direction: enter + entrance_indicator_pos: [ -32, 2.5, -14.99 ] + orientation: south Orange Tower Second Floor: entrances: Orange Tower First Floor: room: Orange Tower door: Second Floor + warp: True Orange Tower Third Floor: room: Orange Tower door: Third Floor - Outside The Undeterred: True + warp: True + Outside The Undeterred: + warp: True Orange Tower Third Floor: entrances: Knight Night Exit: @@ -1961,16 +2036,22 @@ Orange Tower Second Floor: room: Orange Tower door: Third Floor + warp: True Orange Tower Fourth Floor: room: Orange Tower door: Fourth Floor - Hot Crusts Area: True # sunwarp - Bearer Side Area: # This is complicated because of The Bearer's topology + warp: True + Hot Crusts Area: + room: Sunwarps + door: 2 Sunwarp + sunwarp: True + Bearer Side Area: room: Bearer Side Area door: Shortcut to Tower Rhyme Room (Smiley): door: Rhyme Room Entrance - Art Gallery: True # mark this as a warp in the sunwarps branch + Art Gallery: + warp: True panels: RED: id: Color Arrow Room/Panel_red_afar @@ -2019,14 +2100,25 @@ orientation: east - id: flower_painting_5 orientation: south + sunwarps: + - dots: 2 + direction: exit + entrance_indicator_pos: [ 24.01, 2.5, 38 ] + orientation: west + - dots: 3 + direction: enter + entrance_indicator_pos: [ 28.01, 2.5, 29 ] + orientation: west Orange Tower Fourth Floor: entrances: Orange Tower Third Floor: room: Orange Tower door: Fourth Floor + warp: True Orange Tower Fifth Floor: room: Orange Tower door: Fifth Floor + warp: True Hot Crusts Area: door: Hot Crusts Door Crossroads: @@ -2034,7 +2126,10 @@ door: Tower Entrance - room: Crossroads door: Tower Back Entrance - Courtyard: True + Courtyard: + - warp: True + - room: Crossroads + door: Tower Entrance Roof: True # through the sunwarp panels: RUNT (1): @@ -2067,6 +2162,11 @@ id: Shuffle Room Area Doors/Door_hotcrust_shortcuts panels: - HOT CRUSTS + sunwarps: + - dots: 5 + direction: enter + entrance_indicator_pos: [ -20, 3, -64.01 ] + orientation: north Hot Crusts Area: entrances: Orange Tower Fourth Floor: @@ -2084,28 +2184,31 @@ paintings: - id: smile_painting_8 orientation: north + sunwarps: + - dots: 2 + direction: enter + entrance_indicator_pos: [ -26, 3.5, -80.01 ] + orientation: north Orange Tower Fifth Floor: entrances: Orange Tower Fourth Floor: room: Orange Tower door: Fifth Floor + warp: True Orange Tower Sixth Floor: room: Orange Tower door: Sixth Floor + warp: True Cellar: room: Room Room door: Cellar Exit + warp: True Welcome Back Area: door: Welcome Back - Art Gallery: - room: Art Gallery - door: Exit - The Bearer: - room: Art Gallery - door: Exit Outside The Initiated: room: Art Gallery door: Exit + warp: True panels: SIZE (Small): id: Entry Room/Panel_size_small @@ -2185,6 +2288,7 @@ Orange Tower Fifth Floor: room: Orange Tower door: Sixth Floor + warp: True The Scientific: painting: True paintings: @@ -2213,6 +2317,7 @@ Orange Tower Sixth Floor: room: Orange Tower door: Seventh Floor + warp: True panels: THE END: id: EndPanel/Panel_end_end @@ -2389,7 +2494,10 @@ Courtyard: entrances: Roof: True - Orange Tower Fourth Floor: True + Orange Tower Fourth Floor: + - warp: True + - room: Crossroads + door: Tower Entrance Arrow Garden: painting: True Starting Room: @@ -2757,15 +2865,24 @@ entrances: Starting Room: door: Shortcut to Starting Room - Hub Room: True - Outside The Wondrous: True - Outside The Undeterred: True - Outside The Agreeable: True - Outside The Wanderer: True - The Observant: True - Art Gallery: True - The Scientific: True - Cellar: True + Hub Room: + warp: True + Outside The Wondrous: + warp: True + Outside The Undeterred: + warp: True + Outside The Agreeable: + warp: True + Outside The Wanderer: + warp: True + The Observant: + warp: True + Art Gallery: + warp: True + The Scientific: + warp: True + Cellar: + warp: True Orange Tower Fifth Floor: room: Orange Tower Fifth Floor door: Welcome Back @@ -2833,10 +2950,21 @@ Knight Night Exit: room: Knight Night (Final) door: Exit - Orange Tower Third Floor: True # sunwarp + Orange Tower Third Floor: + room: Sunwarps + door: 3 Sunwarp + sunwarp: True Orange Tower Fifth Floor: room: Art Gallery door: Exit + warp: True + Art Gallery: + room: Art Gallery + door: Exit + warp: True + The Bearer: + room: Art Gallery + door: Exit Eight Alcove: door: Eight Door The Optimistic: True @@ -3007,6 +3135,11 @@ orientation: east - id: smile_painting_1 orientation: north + sunwarps: + - dots: 3 + direction: exit + entrance_indicator_pos: [ 89.99, 2.5, 1 ] + orientation: east The Initiated: entrances: Outside The Initiated: @@ -3130,6 +3263,7 @@ door: Traveled Entrance Color Hallways: door: Color Hallways Entrance + warp: True panels: Achievement: id: Countdown Panels/Panel_traveled_traveled @@ -3220,22 +3354,32 @@ The Traveled: room: The Traveled door: Color Hallways Entrance - Outside The Bold: True - Outside The Undeterred: True - Crossroads: True - Hedge Maze: True - The Optimistic: True # backside - Directional Gallery: True # backside - Yellow Backside Area: True + Outside The Bold: + warp: True + Outside The Undeterred: + warp: True + Crossroads: + warp: True + Hedge Maze: + warp: True + The Optimistic: + warp: True # backside + Directional Gallery: + warp: True # backside + Yellow Backside Area: + warp: True The Bearer: room: The Bearer door: Backside Door + warp: True The Observant: room: The Observant door: Backside Door + warp: True Outside The Bold: entrances: - Color Hallways: True + Color Hallways: + warp: True Color Hunt: room: Color Hunt door: Shortcut to The Steady @@ -3253,7 +3397,7 @@ door: Painting Shortcut painting: True Room Room: True # trapdoor - Outside The Agreeable: + Compass Room: painting: True panels: UNOPEN: @@ -3455,13 +3599,22 @@ tag: botred Outside The Undeterred: entrances: - Color Hallways: True - Orange Tower First Floor: True # sunwarp - Orange Tower Second Floor: True - The Artistic (Smiley): True - The Artistic (Panda): True - The Artistic (Apple): True - The Artistic (Lattice): True + Color Hallways: + warp: True + Orange Tower First Floor: + room: Sunwarps + door: 4 Sunwarp + sunwarp: True + Orange Tower Second Floor: + warp: True + The Artistic (Smiley): + warp: True + The Artistic (Panda): + warp: True + The Artistic (Apple): + warp: True + The Artistic (Lattice): + warp: True Yellow Backside Area: painting: True Number Hunt: @@ -3651,6 +3804,11 @@ door: Green Painting - id: blueman_painting_2 orientation: east + sunwarps: + - dots: 4 + direction: exit + entrance_indicator_pos: [ -89.01, 2.5, 4 ] + orientation: east The Undeterred: entrances: Outside The Undeterred: @@ -3928,7 +4086,10 @@ door: Eights Directional Gallery: entrances: - Outside The Agreeable: True # sunwarp + Outside The Agreeable: + room: Sunwarps + door: 6 Sunwarp + sunwarp: True Orange Tower First Floor: room: Orange Tower First Floor door: Salt Pepper Door @@ -4096,11 +4257,19 @@ orientation: south - id: cherry_painting orientation: east + sunwarps: + - dots: 6 + direction: exit + entrance_indicator_pos: [ -39, 2.5, -7.01 ] + orientation: north Color Hunt: entrances: Outside The Bold: door: Shortcut to The Steady - Orange Tower Fourth Floor: True # sunwarp + Orange Tower Fourth Floor: + room: Sunwarps + door: 5 Sunwarp + sunwarp: True Roof: True # through ceiling of sunwarp Champion's Rest: room: Outside The Initiated @@ -4159,6 +4328,11 @@ required_door: room: Outside The Initiated door: Entrance + sunwarps: + - dots: 5 + direction: exit + entrance_indicator_pos: [ 54, 2.5, 69.99 ] + orientation: north Champion's Rest: entrances: Color Hunt: @@ -4192,7 +4366,7 @@ entrances: Outside The Bold: door: Entrance - Orange Tower Fifth Floor: + Outside The Initiated: room: Art Gallery door: Exit The Bearer (East): True @@ -4640,7 +4814,8 @@ tag: midyellow The Steady (Lime): entrances: - The Steady (Sunflower): True + The Steady (Sunflower): + warp: True The Steady (Emerald): room: The Steady door: Reveal @@ -4662,7 +4837,8 @@ orientation: south The Steady (Lemon): entrances: - The Steady (Emerald): True + The Steady (Emerald): + warp: True The Steady (Orange): room: The Steady door: Reveal @@ -5019,8 +5195,10 @@ Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: Fore Door + warp: True Knight Night (Right Lower Segment): door: Segment Door + warp: True panels: RUST (1): id: Appendix Room/Panel_rust_trust @@ -5049,9 +5227,11 @@ Knight Night (Right Upper Segment): room: Knight Night (Right Upper Segment) door: Segment Door + warp: True Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: New Door + warp: True panels: ADJUST: id: Appendix Room/Panel_adjust_readjusted @@ -5097,9 +5277,11 @@ Knight Night (Outer Ring): room: Knight Night (Outer Ring) door: To End + warp: True Knight Night (Right Upper Segment): room: Knight Night (Outer Ring) door: To End + warp: True panels: TRUSTED: id: Appendix Room/Panel_trusted_readjusted @@ -5295,7 +5477,7 @@ entrances: Orange Tower Sixth Floor: painting: True - Outside The Agreeable: + Hallway Room (1): painting: True The Artistic (Smiley): room: The Artistic (Smiley) @@ -5746,7 +5928,8 @@ painting: True Wondrous Lobby: door: Exit - Directional Gallery: True + Directional Gallery: + warp: True panels: NEAR: id: Shuffle Room/Panel_near_near @@ -5781,7 +5964,8 @@ tag: midwhite Wondrous Lobby: entrances: - Directional Gallery: True + Directional Gallery: + warp: True The Eyes They See: room: The Eyes They See door: Exit @@ -5790,10 +5974,12 @@ orientation: east Outside The Wondrous: entrances: - Wondrous Lobby: True + Wondrous Lobby: + warp: True The Wondrous (Doorknob): door: Wondrous Entrance - The Wondrous (Window): True + The Wondrous (Window): + warp: True panels: SHRINK: id: Wonderland Room/Panel_shrink_shrink @@ -5815,7 +6001,9 @@ painting: True The Wondrous (Chandelier): painting: True - The Wondrous (Table): True # There is a way that doesn't use the painting + The Wondrous (Table): + - painting: True + - warp: True doors: Painting Shortcut: painting_id: @@ -5901,7 +6089,8 @@ required: True The Wondrous: entrances: - The Wondrous (Table): True + The Wondrous (Table): + warp: True Arrow Garden: door: Exit panels: @@ -5967,11 +6156,70 @@ paintings: - id: flower_painting_6 orientation: south - Hallway Room (2): + Hallway Room (1): entrances: Outside The Agreeable: - room: Outside The Agreeable - door: Hallway Door + warp: True + Hallway Room (2): + warp: True + Hallway Room (3): + warp: True + Hallway Room (4): + warp: True + panels: + OUT: + id: Hallway Room/Panel_out_out + check: True + exclude_reduce: True + tag: midwhite + WALL: + id: Hallway Room/Panel_castle_1 + colors: blue + tag: quad bot blue + link: qbb CASTLE + KEEP: + id: Hallway Room/Panel_castle_2 + colors: blue + tag: quad bot blue + link: qbb CASTLE + BAILEY: + id: Hallway Room/Panel_castle_3 + colors: blue + tag: quad bot blue + link: qbb CASTLE + TOWER: + id: Hallway Room/Panel_castle_4 + colors: blue + tag: quad bot blue + link: qbb CASTLE + doors: + Exit: + id: Red Blue Purple Room Area Doors/Door_room_2 + door_group: Hallway Room Doors + location_name: Hallway Room - First Room + panels: + - WALL + - KEEP + - BAILEY + - TOWER + paintings: + - id: panda_painting + orientation: south + progression: + Progressive Hallway Room: + - Exit + - room: Hallway Room (2) + door: Exit + - room: Hallway Room (3) + door: Exit + - room: Hallway Room (4) + door: Exit + Hallway Room (2): + entrances: + Hallway Room (1): + room: Hallway Room (1) + door: Exit + warp: True Elements Area: True panels: WISE: @@ -6009,6 +6257,7 @@ Hallway Room (2): room: Hallway Room (2) door: Exit + warp: True # No entrance from Elements Area. The winding hallway does not connect. panels: TRANCE: @@ -6046,6 +6295,7 @@ Hallway Room (3): room: Hallway Room (3) door: Exit + warp: True Elements Area: True panels: WHEEL: @@ -6068,6 +6318,7 @@ Hallway Room (4): room: Hallway Room (4) door: Exit + # If this door is open, then a non-warp entrance from the first hallway room is available The Artistic (Smiley): room: Hallway Room (4) door: Exit @@ -6112,6 +6363,7 @@ entrances: Orange Tower First Floor: door: Tower Entrance + warp: True Rhyme Room (Cross): room: Rhyme Room (Cross) door: Exit @@ -6139,6 +6391,7 @@ Outside The Wanderer: room: Outside The Wanderer door: Wanderer Entrance + warp: True panels: Achievement: id: Countdown Panels/Panel_1234567890_wanderlust @@ -6180,12 +6433,17 @@ tag: midorange Art Gallery: entrances: - Orange Tower Third Floor: True - Art Gallery (Second Floor): True - Art Gallery (Third Floor): True - Art Gallery (Fourth Floor): True - Orange Tower Fifth Floor: + Orange Tower Third Floor: + warp: True + Art Gallery (Second Floor): + warp: True + Art Gallery (Third Floor): + warp: True + Art Gallery (Fourth Floor): + warp: True + Outside The Initiated: door: Exit + warp: True panels: EIGHT: id: Backside Room/Panel_eight_eight_6 @@ -6766,6 +7024,7 @@ Rhyme Room (Smiley): # one-way room: Rhyme Room (Smiley) door: Door to Target + warp: True Rhyme Room (Looped Square): room: Rhyme Room (Looped Square) door: Door to Target @@ -6829,7 +7088,8 @@ # For pretty much the same reason, I don't want to shuffle the paintings in # here. entrances: - Orange Tower Fourth Floor: True + Orange Tower Fourth Floor: + warp: True panels: DOOR (1): id: Panel Room/Panel_room_door_1 @@ -7037,9 +7297,11 @@ Orange Tower Fifth Floor: room: Room Room door: Cellar Exit - Outside The Agreeable: - room: Outside The Agreeable + warp: True + Compass Room: + room: Compass Room door: Lookout Entrance + warp: True Outside The Wise: entrances: Orange Tower Sixth Floor: @@ -7077,6 +7339,7 @@ Outside The Wise: room: Outside The Wise door: Wise Entrance + warp: True # The Wise is so full of warps panels: Achievement: id: Countdown Panels/Panel_intelligent_wise diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index c957e5d51c895e444f1d5245bb9708a0a799cd14..304109ca2840005f25efda7653198ff8ddb96092 100644 GIT binary patch literal 135088 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 VN|2s@APVa$lY!lU^DCFP{9o7VyI24K literal 130691 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 diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index d3307dea..918af7ab 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -140,13 +140,9 @@ panels: PURPLE: 444502 FIVE (1): 444503 FIVE (2): 444504 - OUT: 444505 HIDE: 444506 DAZE: 444507 - WALL: 444508 - KEEP: 444509 - BAILEY: 444510 - TOWER: 444511 + Compass Room: NORTH: 444512 DIAMONDS: 444513 FIRE: 444514 @@ -689,6 +685,12 @@ panels: Arrow Garden: MASTERY: 444948 SHARP: 444949 + Hallway Room (1): + OUT: 444505 + WALL: 444508 + KEEP: 444509 + BAILEY: 444510 + TOWER: 444511 Hallway Room (2): WISE: 444950 CLOCK: 444951 @@ -995,6 +997,19 @@ doors: Traveled Entrance: item: 444433 location: 444438 + Sunwarps: + 1 Sunwarp: + item: 444581 + 2 Sunwarp: + item: 444588 + 3 Sunwarp: + item: 444586 + 4 Sunwarp: + item: 444585 + 5 Sunwarp: + item: 444587 + 6 Sunwarp: + item: 444584 Pilgrim Antechamber: Sun Painting: item: 444436 @@ -1067,9 +1082,7 @@ doors: location: 444501 Purple Barrier: item: 444457 - Hallway Door: - item: 444459 - location: 445214 + Compass Room: Lookout Entrance: item: 444579 location: 445271 @@ -1342,6 +1355,10 @@ doors: Exit: item: 444552 location: 444947 + Hallway Room (1): + Exit: + item: 444459 + location: 445214 Hallway Room (2): Exit: item: 444553 @@ -1452,9 +1469,11 @@ door_groups: Colorful Doors: 444498 Directional Gallery Doors: 444531 Artistic Doors: 444545 + Sunwarps: 444582 progression: Progressive Hallway Room: 444461 Progressive Fearless: 444470 Progressive Orange Tower: 444482 Progressive Art Gallery: 444563 Progressive Colorful: 444580 + Progressive Pilgrimage: 444583 diff --git a/worlds/lingo/datatypes.py b/worlds/lingo/datatypes.py index e9bf0a37..e466558f 100644 --- a/worlds/lingo/datatypes.py +++ b/worlds/lingo/datatypes.py @@ -1,3 +1,4 @@ +from enum import Enum, Flag, auto from typing import List, NamedTuple, Optional @@ -11,10 +12,18 @@ class RoomAndPanel(NamedTuple): panel: str +class EntranceType(Flag): + NORMAL = auto() + PAINTING = auto() + SUNWARP = auto() + WARP = auto() + CROSSROADS_ROOF_ACCESS = auto() + + class RoomEntrance(NamedTuple): room: str # source room door: Optional[RoomAndDoor] - painting: bool + type: EntranceType class Room(NamedTuple): @@ -22,6 +31,12 @@ class Room(NamedTuple): entrances: List[RoomEntrance] +class DoorType(Enum): + NORMAL = 1 + SUNWARP = 2 + SUN_PAINTING = 3 + + class Door(NamedTuple): name: str item_name: str @@ -34,7 +49,7 @@ class Door(NamedTuple): event: bool door_group: Optional[str] include_reduce: bool - junk_item: bool + type: DoorType item_group: Optional[str] diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py index 7c7928cb..67eaceab 100644 --- a/worlds/lingo/items.py +++ b/worlds/lingo/items.py @@ -1,8 +1,14 @@ -from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING +from enum import Enum +from typing import Dict, List, NamedTuple, Set from BaseClasses import Item, ItemClassification -from .static_logic import DOORS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, \ - get_door_item_id, get_progressive_item_id, get_special_item_id +from .static_logic import DOORS_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, get_door_item_id, \ + get_progressive_item_id, get_special_item_id + + +class ItemType(Enum): + NORMAL = 1 + COLOR = 2 class ItemData(NamedTuple): @@ -11,7 +17,7 @@ class ItemData(NamedTuple): """ code: int classification: ItemClassification - mode: Optional[str] + type: ItemType has_doors: bool painting_ids: List[str] @@ -34,36 +40,29 @@ def load_item_data(): for color in ["Black", "Red", "Blue", "Yellow", "Green", "Orange", "Gray", "Brown", "Purple"]: ALL_ITEM_TABLE[color] = ItemData(get_special_item_id(color), ItemClassification.progression, - "colors", [], []) + ItemType.COLOR, False, []) ITEMS_BY_GROUP.setdefault("Colors", []).append(color) - door_groups: Dict[str, List[str]] = {} + door_groups: Set[str] = set() for room_name, doors in DOORS_BY_ROOM.items(): for door_name, door in doors.items(): if door.skip_item is True or door.event is True: continue - if door.door_group is None: - door_mode = "doors" - else: - door_mode = "complex door" - door_groups.setdefault(door.door_group, []) - - if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]: - door_mode = "special" + if door.door_group is not None: + door_groups.add(door.door_group) ALL_ITEM_TABLE[door.item_name] = \ - ItemData(get_door_item_id(room_name, door_name), - ItemClassification.filler if door.junk_item else ItemClassification.progression, door_mode, + ItemData(get_door_item_id(room_name, door_name), ItemClassification.progression, ItemType.NORMAL, door.has_doors, door.painting_ids) ITEMS_BY_GROUP.setdefault("Doors", []).append(door.item_name) if door.item_group is not None: ITEMS_BY_GROUP.setdefault(door.item_group, []).append(door.item_name) - for group, group_door_ids in door_groups.items(): + for group in door_groups: ALL_ITEM_TABLE[group] = ItemData(get_door_group_item_id(group), - ItemClassification.progression, "door group", True, []) + ItemClassification.progression, ItemType.NORMAL, True, []) ITEMS_BY_GROUP.setdefault("Doors", []).append(group) special_items: Dict[str, ItemClassification] = { @@ -77,7 +76,7 @@ def load_item_data(): for item_name, classification in special_items.items(): ALL_ITEM_TABLE[item_name] = ItemData(get_special_item_id(item_name), classification, - "special", False, []) + ItemType.NORMAL, False, []) if classification == ItemClassification.filler: ITEMS_BY_GROUP.setdefault("Junk", []).append(item_name) @@ -86,7 +85,7 @@ def load_item_data(): for item_name in PROGRESSIVE_ITEMS: ALL_ITEM_TABLE[item_name] = ItemData(get_progressive_item_id(item_name), - ItemClassification.progression, "special", False, []) + ItemClassification.progression, ItemType.NORMAL, False, []) # Initialize the item data at module scope. diff --git a/worlds/lingo/locations.py b/worlds/lingo/locations.py index 92ee3094..a6e53e76 100644 --- a/worlds/lingo/locations.py +++ b/worlds/lingo/locations.py @@ -56,7 +56,7 @@ def load_location_data(): for room_name, doors in DOORS_BY_ROOM.items(): for door_name, door in doors.items(): - if door.skip_location or door.event or door.panels is None: + if door.skip_location or door.event or not door.panels: continue location_name = door.location_name diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py index 293992ab..05fb4ed9 100644 --- a/worlds/lingo/options.py +++ b/worlds/lingo/options.py @@ -61,15 +61,55 @@ class ShufflePaintings(Toggle): display_name = "Shuffle Paintings" +class EnablePilgrimage(Toggle): + """If on, you are required to complete a pilgrimage in order to access the Pilgrim Antechamber. + If off, the pilgrimage will be deactivated, and the sun painting will be added to the pool, even if door shuffle is off.""" + display_name = "Enable Pilgrimage" + + +class PilgrimageAllowsRoofAccess(DefaultOnToggle): + """If on, you may use the Crossroads roof access during a pilgrimage (and you may be expected to do so). + Otherwise, pilgrimage will be deactivated when going up the stairs.""" + display_name = "Allow Roof Access for Pilgrimage" + + +class PilgrimageAllowsPaintings(DefaultOnToggle): + """If on, you may use paintings during a pilgrimage (and you may be expected to do so). + Otherwise, pilgrimage will be deactivated when going through a painting.""" + display_name = "Allow Paintings for Pilgrimage" + + +class SunwarpAccess(Choice): + """Determines how access to sunwarps works. + On "normal", all sunwarps are enabled from the start. + On "disabled", all sunwarps are disabled. Pilgrimage must be disabled when this is used. + On "unlock", sunwarps start off disabled, and all six activate once you receive an item. + On "individual", sunwarps start off disabled, and each has a corresponding item that unlocks it. + On "progressive", sunwarps start off disabled, and they unlock in order using a progressive item.""" + display_name = "Sunwarp Access" + option_normal = 0 + option_disabled = 1 + option_unlock = 2 + option_individual = 3 + option_progressive = 4 + + +class ShuffleSunwarps(Toggle): + """If on, the pairing and ordering of the sunwarps in the game will be randomized.""" + display_name = "Shuffle Sunwarps" + + class VictoryCondition(Choice): """Change the victory condition. On "the_end", the goal is to solve THE END at the top of the tower. On "the_master", the goal is to solve THE MASTER at the top of the tower, after getting the number of achievements specified in the Mastery Achievements option. - On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option.""" + On "level_2", the goal is to solve LEVEL 2 in the second room, after solving the number of panels specified in the Level 2 Requirement option. + On "pilgrimage", the goal is to solve PILGRIM in the Pilgrim Antechamber, typically after performing a Pilgrimage.""" display_name = "Victory Condition" option_the_end = 0 option_the_master = 1 option_level_2 = 2 + option_pilgrimage = 3 class MasteryAchievements(Range): @@ -140,6 +180,11 @@ class LingoOptions(PerGameCommonOptions): shuffle_colors: ShuffleColors shuffle_panels: ShufflePanels shuffle_paintings: ShufflePaintings + enable_pilgrimage: EnablePilgrimage + pilgrimage_allows_roof_access: PilgrimageAllowsRoofAccess + pilgrimage_allows_paintings: PilgrimageAllowsPaintings + sunwarp_access: SunwarpAccess + shuffle_sunwarps: ShuffleSunwarps victory_condition: VictoryCondition mastery_achievements: MasteryAchievements level_2_requirement: Level2Requirement diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 966f5a16..96e9869d 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -1,12 +1,13 @@ from enum import Enum from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING -from .datatypes import Door, RoomAndDoor, RoomAndPanel -from .items import ALL_ITEM_TABLE, ItemData +from .datatypes import Door, DoorType, RoomAndDoor, RoomAndPanel +from .items import ALL_ITEM_TABLE, ItemType from .locations import ALL_LOCATION_TABLE, LocationClassification -from .options import LocationChecks, ShuffleDoors, VictoryCondition +from .options import LocationChecks, ShuffleDoors, SunwarpAccess, VictoryCondition from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ - PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS + PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, \ + SUNWARP_ENTRANCES, SUNWARP_EXITS if TYPE_CHECKING: from . import LingoWorld @@ -58,21 +59,6 @@ def should_split_progression(progression_name: str, world: "LingoWorld") -> Prog return ProgressiveItemBehavior.PROGRESSIVE -def should_include_item(item: ItemData, world: "LingoWorld") -> bool: - if item.mode == "colors": - return world.options.shuffle_colors > 0 - elif item.mode == "doors": - return world.options.shuffle_doors != ShuffleDoors.option_none - elif item.mode == "complex door": - return world.options.shuffle_doors == ShuffleDoors.option_complex - elif item.mode == "door group": - return world.options.shuffle_doors == ShuffleDoors.option_simple - elif item.mode == "special": - return False - else: - return True - - class LingoPlayerLogic: """ Defines logic after a player's options have been applied @@ -99,6 +85,10 @@ class LingoPlayerLogic: mastery_reqs: List[AccessRequirements] counting_panel_reqs: Dict[str, List[Tuple[AccessRequirements, int]]] + sunwarp_mapping: List[int] + sunwarp_entrances: List[str] + sunwarp_exits: List[str] + def add_location(self, room: str, name: str, code: Optional[int], panels: List[RoomAndPanel], world: "LingoWorld"): """ Creates a location. This function determines the access requirements for the location by combining and @@ -132,6 +122,7 @@ class LingoPlayerLogic: self.real_items.append(progressive_item_name) else: self.set_door_item(room_name, door_data.name, door_data.item_name) + self.real_items.append(door_data.item_name) def __init__(self, world: "LingoWorld"): self.item_by_door = {} @@ -148,6 +139,7 @@ class LingoPlayerLogic: self.door_reqs = {} self.mastery_reqs = [] self.counting_panel_reqs = {} + self.sunwarp_mapping = [] door_shuffle = world.options.shuffle_doors color_shuffle = world.options.shuffle_colors @@ -161,15 +153,37 @@ class LingoPlayerLogic: "be enough locations for all of the door items.") # Create door items, where needed. - if door_shuffle != ShuffleDoors.option_none: - for room_name, room_data in DOORS_BY_ROOM.items(): - for door_name, door_data in room_data.items(): - if door_data.skip_item is False and door_data.event is False: + door_groups: Set[str] = set() + for room_name, room_data in DOORS_BY_ROOM.items(): + for door_name, door_data in room_data.items(): + if door_data.skip_item is False and door_data.event is False: + if door_data.type == DoorType.NORMAL and door_shuffle != ShuffleDoors.option_none: if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple: # Grouped doors are handled differently if shuffle doors is on simple. self.set_door_item(room_name, door_name, door_data.door_group) + door_groups.add(door_data.door_group) else: self.handle_non_grouped_door(room_name, door_data, world) + elif door_data.type == DoorType.SUNWARP: + if world.options.sunwarp_access == SunwarpAccess.option_unlock: + self.set_door_item(room_name, door_name, "Sunwarps") + door_groups.add("Sunwarps") + elif world.options.sunwarp_access == SunwarpAccess.option_individual: + self.set_door_item(room_name, door_name, door_data.item_name) + self.real_items.append(door_data.item_name) + elif world.options.sunwarp_access == SunwarpAccess.option_progressive: + self.set_door_item(room_name, door_name, "Progressive Pilgrimage") + self.real_items.append("Progressive Pilgrimage") + elif door_data.type == DoorType.SUN_PAINTING: + if not world.options.enable_pilgrimage: + self.set_door_item(room_name, door_name, door_data.item_name) + self.real_items.append(door_data.item_name) + + self.real_items += door_groups + + # Create color items, if needed. + if color_shuffle: + self.real_items += [name for name, item in ALL_ITEM_TABLE.items() if item.type == ItemType.COLOR] # Create events for each achievement panel, so that we can determine when THE MASTER is accessible. for room_name, room_data in PANELS_BY_ROOM.items(): @@ -206,6 +220,11 @@ class LingoPlayerLogic: if world.options.level_2_requirement == 1: raise Exception("The Level 2 requirement must be at least 2 when LEVEL 2 is the victory condition.") + elif victory_condition == VictoryCondition.option_pilgrimage: + self.victory_condition = "Pilgrim Antechamber - PILGRIM" + self.add_location("Pilgrim Antechamber", "PILGRIM (Solved)", None, + [RoomAndPanel("Pilgrim Antechamber", "PILGRIM")], world) + self.event_loc_to_item["PILGRIM (Solved)"] = "Victory" # Create groups of counting panel access requirements for the LEVEL 2 check. self.create_panel_hunt_events(world) @@ -225,28 +244,22 @@ class LingoPlayerLogic: self.add_location(location_data.room, location_name, location_data.code, location_data.panels, world) self.real_locations.append(location_name) - # Instantiate all real items. - for name, item in ALL_ITEM_TABLE.items(): - if should_include_item(item, world): - self.real_items.append(name) + if world.options.enable_pilgrimage and world.options.sunwarp_access == SunwarpAccess.option_disabled: + raise Exception("Sunwarps cannot be disabled when pilgrimage is enabled.") - # Calculate the requirements for the fake pilgrimage. - fake_pilgrimage = [ - ["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"], - ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], - ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], - ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], - ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Entrance"], ["Art Gallery", "Exit"], - ["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"] - ] - pilgrimage_reqs = AccessRequirements() - for door in fake_pilgrimage: - door_object = DOORS_BY_ROOM[door[0]][door[1]] - if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none: - pilgrimage_reqs.merge(self.calculate_door_requirements(door[0], door[1], world)) - else: - pilgrimage_reqs.doors.add(RoomAndDoor(door[0], door[1])) - self.door_reqs.setdefault("Pilgrim Antechamber", {})["Pilgrimage"] = pilgrimage_reqs + if world.options.shuffle_sunwarps: + if world.options.sunwarp_access == SunwarpAccess.option_disabled: + raise Exception("Sunwarps cannot be shuffled if they are disabled.") + + self.sunwarp_mapping = list(range(0, 12)) + world.random.shuffle(self.sunwarp_mapping) + + sunwarp_rooms = SUNWARP_ENTRANCES + SUNWARP_EXITS + self.sunwarp_entrances = [sunwarp_rooms[i] for i in self.sunwarp_mapping[0:6]] + self.sunwarp_exits = [sunwarp_rooms[i] for i in self.sunwarp_mapping[6:12]] + else: + self.sunwarp_entrances = SUNWARP_ENTRANCES + self.sunwarp_exits = SUNWARP_EXITS # Create the paintings mapping, if painting shuffle is on. if painting_shuffle: @@ -277,10 +290,11 @@ class LingoPlayerLogic: # Starting Room - Exit Door gives access to OPEN and TRACE. good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] - if not color_shuffle: + if not color_shuffle and not world.options.enable_pilgrimage: # HOT CRUST and THIS. good_item_options.append("Pilgrim Room - Sun Painting") + if not color_shuffle: if door_shuffle == ShuffleDoors.option_simple: # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. good_item_options.append("Welcome Back Doors") diff --git a/worlds/lingo/regions.py b/worlds/lingo/regions.py index 5fddabd6..4b357db2 100644 --- a/worlds/lingo/regions.py +++ b/worlds/lingo/regions.py @@ -1,10 +1,11 @@ from typing import Dict, Optional, TYPE_CHECKING from BaseClasses import Entrance, ItemClassification, Region -from .datatypes import Room, RoomAndDoor +from .datatypes import EntranceType, Room, RoomAndDoor from .items import LingoItem from .locations import LingoLocation -from .rules import lingo_can_use_entrance, make_location_lambda +from .options import SunwarpAccess +from .rules import lingo_can_do_pilgrimage, lingo_can_use_entrance, make_location_lambda from .static_logic import ALL_ROOMS, PAINTINGS if TYPE_CHECKING: @@ -25,8 +26,20 @@ def create_region(room: Room, world: "LingoWorld") -> Region: return new_region +def is_acceptable_pilgrimage_entrance(entrance_type: EntranceType, world: "LingoWorld") -> bool: + allowed_entrance_types = EntranceType.NORMAL + + if world.options.pilgrimage_allows_paintings: + allowed_entrance_types |= EntranceType.PAINTING + + if world.options.pilgrimage_allows_roof_access: + allowed_entrance_types |= EntranceType.CROSSROADS_ROOF_ACCESS + + return bool(entrance_type & allowed_entrance_types) + + def connect_entrance(regions: Dict[str, Region], source_region: Region, target_region: Region, description: str, - door: Optional[RoomAndDoor], world: "LingoWorld"): + door: Optional[RoomAndDoor], entrance_type: EntranceType, pilgrimage: bool, world: "LingoWorld"): connection = Entrance(world.player, description, source_region) connection.access_rule = lambda state: lingo_can_use_entrance(state, target_region.name, door, world) @@ -38,6 +51,21 @@ def connect_entrance(regions: Dict[str, Region], source_region: Region, target_r if door.door not in world.player_logic.item_by_door.get(effective_room, {}): for region in world.player_logic.calculate_door_requirements(effective_room, door.door, world).rooms: world.multiworld.register_indirect_condition(regions[region], connection) + + if not pilgrimage and world.options.enable_pilgrimage and is_acceptable_pilgrimage_entrance(entrance_type, world)\ + and source_region.name != "Menu": + for part in range(1, 6): + pilgrimage_descriptor = f" (Pilgrimage Part {part})" + pilgrim_source_region = regions[f"{source_region.name}{pilgrimage_descriptor}"] + pilgrim_target_region = regions[f"{target_region.name}{pilgrimage_descriptor}"] + + effective_door = door + if effective_door is not None: + effective_room = target_region.name if door.room is None else door.room + effective_door = RoomAndDoor(effective_room, door.door) + + connect_entrance(regions, pilgrim_source_region, pilgrim_target_region, + f"{description}{pilgrimage_descriptor}", effective_door, entrance_type, True, world) def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str, world: "LingoWorld") -> None: @@ -48,7 +76,8 @@ def connect_painting(regions: Dict[str, Region], warp_enter: str, warp_exit: str source_region = regions[source_painting.room] entrance_name = f"{source_painting.room} to {target_painting.room} ({source_painting.id} Painting)" - connect_entrance(regions, source_region, target_region, entrance_name, source_painting.required_door, world) + connect_entrance(regions, source_region, target_region, entrance_name, source_painting.required_door, + EntranceType.PAINTING, False, world) def create_regions(world: "LingoWorld") -> None: @@ -63,11 +92,26 @@ def create_regions(world: "LingoWorld") -> None: for room in ALL_ROOMS: regions[room.name] = create_region(room, world) + if world.options.enable_pilgrimage: + for part in range(1, 6): + pilgrimage_region_name = f"{room.name} (Pilgrimage Part {part})" + regions[pilgrimage_region_name] = Region(pilgrimage_region_name, world.player, world.multiworld) + # Connect all created regions now that they exist. + allowed_entrance_types = EntranceType.NORMAL | EntranceType.WARP | EntranceType.CROSSROADS_ROOF_ACCESS + + if not painting_shuffle: + # Don't use the vanilla painting connections if we are shuffling paintings. + allowed_entrance_types |= EntranceType.PAINTING + + if world.options.sunwarp_access != SunwarpAccess.option_disabled and not world.options.shuffle_sunwarps: + # Don't connect sunwarps if sunwarps are disabled or if we're shuffling sunwarps. + allowed_entrance_types |= EntranceType.SUNWARP + for room in ALL_ROOMS: for entrance in room.entrances: - # Don't use the vanilla painting connections if we are shuffling paintings. - if entrance.painting and painting_shuffle: + effective_entrance_type = entrance.type & allowed_entrance_types + if not effective_entrance_type: continue entrance_name = f"{entrance.room} to {room.name}" @@ -77,17 +121,56 @@ def create_regions(world: "LingoWorld") -> None: else: entrance_name += f" (through {room.name} - {entrance.door.door})" - connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, entrance.door, world) + effective_door = entrance.door + if entrance.type == EntranceType.SUNWARP and world.options.sunwarp_access == SunwarpAccess.option_normal: + effective_door = None - # Add the fake pilgrimage. - connect_entrance(regions, regions["Outside The Agreeable"], regions["Pilgrim Antechamber"], "Pilgrimage", - RoomAndDoor("Pilgrim Antechamber", "Pilgrimage"), world) + connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, effective_door, + effective_entrance_type, False, world) + + if world.options.enable_pilgrimage: + # Connect the start of the pilgrimage. We check for all sunwarp items here. + pilgrim_start_from = regions[world.player_logic.sunwarp_entrances[0]] + pilgrim_start_to = regions[f"{world.player_logic.sunwarp_exits[0]} (Pilgrimage Part 1)"] + + if world.options.sunwarp_access >= SunwarpAccess.option_unlock: + pilgrim_start_from.connect(pilgrim_start_to, f"Pilgrimage Part 1", + lambda state: lingo_can_do_pilgrimage(state, world)) + else: + pilgrim_start_from.connect(pilgrim_start_to, f"Pilgrimage Part 1") + + # Create connections between each segment of the pilgrimage. + for i in range(1, 6): + from_room = f"{world.player_logic.sunwarp_entrances[i]} (Pilgrimage Part {i})" + to_room = f"{world.player_logic.sunwarp_exits[i]} (Pilgrimage Part {i+1})" + if i == 5: + to_room = "Pilgrim Antechamber" + + regions[from_room].connect(regions[to_room], f"Pilgrimage Part {i+1}") + else: + connect_entrance(regions, regions["Starting Room"], regions["Pilgrim Antechamber"], "Sun Painting", + RoomAndDoor("Pilgrim Antechamber", "Sun Painting"), EntranceType.PAINTING, False, world) if early_color_hallways: - regions["Starting Room"].connect(regions["Outside The Undeterred"], "Early Color Hallways") + connect_entrance(regions, regions["Starting Room"], regions["Outside The Undeterred"], "Early Color Hallways", + None, EntranceType.PAINTING, False, world) if painting_shuffle: for warp_enter, warp_exit in world.player_logic.painting_mapping.items(): connect_painting(regions, warp_enter, warp_exit, world) + if world.options.shuffle_sunwarps: + for i in range(0, 6): + if world.options.sunwarp_access == SunwarpAccess.option_normal: + effective_door = None + else: + effective_door = RoomAndDoor("Sunwarps", f"{i + 1} Sunwarp") + + source_region = regions[world.player_logic.sunwarp_entrances[i]] + target_region = regions[world.player_logic.sunwarp_exits[i]] + + entrance_name = f"{source_region.name} to {target_region.name} ({i + 1} Sunwarp)" + connect_entrance(regions, source_region, target_region, entrance_name, effective_door, EntranceType.SUNWARP, + False, world) + world.multiworld.regions += regions.values() diff --git a/worlds/lingo/rules.py b/worlds/lingo/rules.py index 4e12938a..9cc11fda 100644 --- a/worlds/lingo/rules.py +++ b/worlds/lingo/rules.py @@ -17,6 +17,10 @@ def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, return _lingo_can_open_door(state, effective_room, door.door, world) +def lingo_can_do_pilgrimage(state: CollectionState, world: "LingoWorld"): + return all(_lingo_can_open_door(state, "Sunwarps", f"{i} Sunwarp", world) for i in range(1, 7)) + + def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld"): return _lingo_can_satisfy_requirements(state, location.access, world) diff --git a/worlds/lingo/static_logic.py b/worlds/lingo/static_logic.py index 1da265df..c7ee0010 100644 --- a/worlds/lingo/static_logic.py +++ b/worlds/lingo/static_logic.py @@ -1,10 +1,9 @@ import os import pkgutil +import pickle from io import BytesIO from typing import Dict, List, Set -import pickle - from .datatypes import Door, Painting, Panel, Progression, Room ALL_ROOMS: List[Room] = [] @@ -21,6 +20,9 @@ PAINTING_EXITS: int = 0 REQUIRED_PAINTING_ROOMS: List[str] = [] REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = [] +SUNWARP_ENTRANCES: List[str] = [] +SUNWARP_EXITS: List[str] = [] + SPECIAL_ITEM_IDS: Dict[str, int] = {} PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} @@ -99,6 +101,8 @@ def load_static_data_from_file(): PAINTING_EXITS = pickdata["PAINTING_EXITS"] REQUIRED_PAINTING_ROOMS.extend(pickdata["REQUIRED_PAINTING_ROOMS"]) REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.extend(pickdata["REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS"]) + SUNWARP_ENTRANCES.extend(pickdata["SUNWARP_ENTRANCES"]) + SUNWARP_EXITS.extend(pickdata["SUNWARP_EXITS"]) SPECIAL_ITEM_IDS.update(pickdata["SPECIAL_ITEM_IDS"]) PANEL_LOCATION_IDS.update(pickdata["PANEL_LOCATION_IDS"]) DOOR_LOCATION_IDS.update(pickdata["DOOR_LOCATION_IDS"]) diff --git a/worlds/lingo/test/TestOptions.py b/worlds/lingo/test/TestOptions.py index 17696778..fce07431 100644 --- a/worlds/lingo/test/TestOptions.py +++ b/worlds/lingo/test/TestOptions.py @@ -29,3 +29,23 @@ class TestAllPanelHunt(LingoTestBase): "level_2_requirement": "800", "early_color_hallways": "true" } + + +class TestShuffleSunwarps(LingoTestBase): + options = { + "shuffle_doors": "none", + "shuffle_colors": "false", + "victory_condition": "pilgrimage", + "shuffle_sunwarps": "true", + "sunwarp_access": "normal" + } + + +class TestShuffleSunwarpsAccess(LingoTestBase): + options = { + "shuffle_doors": "none", + "shuffle_colors": "false", + "victory_condition": "pilgrimage", + "shuffle_sunwarps": "true", + "sunwarp_access": "individual" + } \ No newline at end of file diff --git a/worlds/lingo/test/TestPilgrimage.py b/worlds/lingo/test/TestPilgrimage.py new file mode 100644 index 00000000..3cc91940 --- /dev/null +++ b/worlds/lingo/test/TestPilgrimage.py @@ -0,0 +1,114 @@ +from . import LingoTestBase + + +class TestDisabledPilgrimage(LingoTestBase): + options = { + "enable_pilgrimage": "false", + "shuffle_colors": "false" + } + + def test_access(self): + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + self.collect_by_name("Pilgrim Room - Sun Painting") + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestPilgrimageWithRoofAndPaintings(LingoTestBase): + options = { + "enable_pilgrimage": "true", + "shuffle_colors": "false", + "shuffle_doors": "complex", + "pilgrimage_allows_roof_access": "true", + "pilgrimage_allows_paintings": "true", + "early_color_hallways": "false" + } + + def test_access(self): + doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance", + "Outside The Undeterred - Green Painting"] + + for door in doors: + print(door) + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(door) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestPilgrimageNoRoofYesPaintings(LingoTestBase): + options = { + "enable_pilgrimage": "true", + "shuffle_colors": "false", + "shuffle_doors": "complex", + "pilgrimage_allows_roof_access": "false", + "pilgrimage_allows_paintings": "true", + "early_color_hallways": "false" + } + + def test_access(self): + doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance", + "Outside The Undeterred - Green Painting", "Crossroads - Tower Entrance", + "Orange Tower Fourth Floor - Hot Crusts Door", "Orange Tower First Floor - Shortcut to Hub Room", + "Starting Room - Street Painting"] + + for door in doors: + print(door) + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(door) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestPilgrimageNoRoofNoPaintings(LingoTestBase): + options = { + "enable_pilgrimage": "true", + "shuffle_colors": "false", + "shuffle_doors": "complex", + "pilgrimage_allows_roof_access": "false", + "pilgrimage_allows_paintings": "false", + "early_color_hallways": "false" + } + + def test_access(self): + doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance", + "Outside The Undeterred - Green Painting", "Orange Tower First Floor - Shortcut to Hub Room", + "Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room", + "Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door", + "Color Hunt - Shortcut to The Steady", "The Bearer - Entrance", + "Orange Tower Fifth Floor - Quadruple Intersection", "The Tenacious - Shortcut to Hub Room", + "Outside The Agreeable - Tenacious Entrance", "Crossroads - Tower Entrance", + "Orange Tower Fourth Floor - Hot Crusts Door"] + + for door in doors: + print(door) + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(door) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestPilgrimageYesRoofNoPaintings(LingoTestBase): + options = { + "enable_pilgrimage": "true", + "shuffle_colors": "false", + "shuffle_doors": "complex", + "pilgrimage_allows_roof_access": "true", + "pilgrimage_allows_paintings": "false", + "early_color_hallways": "false" + } + + def test_access(self): + doors = ["Second Room - Exit Door", "Crossroads - Roof Access", "Hub Room - Crossroads Entrance", + "Outside The Undeterred - Green Painting", "Orange Tower First Floor - Shortcut to Hub Room", + "Starting Room - Street Painting", "Outside The Initiated - Shortcut to Hub Room", + "Directional Gallery - Shortcut to The Undeterred", "Orange Tower First Floor - Salt Pepper Door", + "Color Hunt - Shortcut to The Steady", "The Bearer - Entrance", + "Orange Tower Fifth Floor - Quadruple Intersection"] + + for door in doors: + print(door) + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(door) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) diff --git a/worlds/lingo/test/TestSunwarps.py b/worlds/lingo/test/TestSunwarps.py new file mode 100644 index 00000000..e8e913c4 --- /dev/null +++ b/worlds/lingo/test/TestSunwarps.py @@ -0,0 +1,213 @@ +from . import LingoTestBase + + +class TestVanillaDoorsNormalSunwarps(LingoTestBase): + options = { + "shuffle_doors": "none", + "shuffle_colors": "true", + "sunwarp_access": "normal" + } + + def test_access(self): + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + + self.collect_by_name("Yellow") + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestSimpleDoorsNormalSunwarps(LingoTestBase): + options = { + "shuffle_doors": "simple", + "sunwarp_access": "normal" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + + self.collect_by_name(["Crossroads - Tower Entrances", "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestSimpleDoorsDisabledSunwarps(LingoTestBase): + options = { + "shuffle_doors": "simple", + "sunwarp_access": "disabled" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + + self.collect_by_name(["Hub Room - Crossroads Entrance", "Crossroads - Tower Entrancse", + "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestSimpleDoorsUnlockSunwarps(LingoTestBase): + options = { + "shuffle_doors": "simple", + "sunwarp_access": "unlock" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name(["Crossroads - Tower Entrances", "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + self.collect_by_name("Sunwarps") + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestComplexDoorsNormalSunwarps(LingoTestBase): + options = { + "shuffle_doors": "complex", + "sunwarp_access": "normal" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + + self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestComplexDoorsDisabledSunwarps(LingoTestBase): + options = { + "shuffle_doors": "complex", + "sunwarp_access": "disabled" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + + self.collect_by_name(["Hub Room - Crossroads Entrance", "Crossroads - Tower Entrance", + "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestComplexDoorsIndividualSunwarps(LingoTestBase): + options = { + "shuffle_doors": "complex", + "sunwarp_access": "individual" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("1 Sunwarp") + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + self.collect_by_name("2 Sunwarp") + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + self.collect_by_name("3 Sunwarp") + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestComplexDoorsProgressiveSunwarps(LingoTestBase): + options = { + "shuffle_doors": "complex", + "sunwarp_access": "progressive" + } + + def test_access(self): + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name("Second Room - Exit Door") + self.assertFalse(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + progressive_pilgrimage = self.get_items_by_name("Progressive Pilgrimage") + self.collect(progressive_pilgrimage[0]) + self.assertTrue(self.multiworld.state.can_reach("Crossroads", "Region", self.player)) + + self.collect_by_name(["Crossroads - Tower Entrance", "Orange Tower Fourth Floor - Hot Crusts Door"]) + self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + self.collect(progressive_pilgrimage[1]) + self.assertTrue(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) + self.assertFalse(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + self.collect(progressive_pilgrimage[2]) + self.assertTrue(self.multiworld.state.can_reach("Outside The Initiated", "Region", self.player)) + + +class TestUnlockSunwarpPilgrimage(LingoTestBase): + options = { + "sunwarp_access": "unlock", + "shuffle_colors": "false", + "enable_pilgrimage": "true" + } + + def test_access(self): + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + self.collect_by_name("Sunwarps") + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestIndividualSunwarpPilgrimage(LingoTestBase): + options = { + "sunwarp_access": "individual", + "shuffle_colors": "false", + "enable_pilgrimage": "true" + } + + def test_access(self): + for i in range(1, 7): + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect_by_name(f"{i} Sunwarp") + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + + +class TestProgressiveSunwarpPilgrimage(LingoTestBase): + options = { + "sunwarp_access": "progressive", + "shuffle_colors": "false", + "enable_pilgrimage": "true" + } + + def test_access(self): + for item in self.get_items_by_name("Progressive Pilgrimage"): + self.assertFalse(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) + self.collect(item) + + self.assertTrue(self.can_reach_location("Pilgrim Antechamber - PILGRIM")) diff --git a/worlds/lingo/utils/pickle_static_data.py b/worlds/lingo/utils/pickle_static_data.py index 5d6fa1e6..10ec69be 100644 --- a/worlds/lingo/utils/pickle_static_data.py +++ b/worlds/lingo/utils/pickle_static_data.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set +from typing import Dict, List, Set, Optional import os import sys @@ -6,7 +6,8 @@ import sys sys.path.append(os.path.join("worlds", "lingo")) sys.path.append(".") sys.path.append("..") -from datatypes import Door, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel, RoomEntrance +from datatypes import Door, DoorType, EntranceType, Painting, Panel, Progression, Room, RoomAndDoor, RoomAndPanel,\ + RoomEntrance import hashlib import pickle @@ -28,6 +29,9 @@ PAINTING_EXITS: int = 0 REQUIRED_PAINTING_ROOMS: List[str] = [] REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = [] +SUNWARP_ENTRANCES: List[str] = ["", "", "", "", "", ""] +SUNWARP_EXITS: List[str] = ["", "", "", "", "", ""] + SPECIAL_ITEM_IDS: Dict[str, int] = {} PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {} DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {} @@ -96,41 +100,51 @@ def load_static_data(ll1_path, ids_path): PAINTING_EXITS = len(PAINTING_EXIT_ROOMS) -def process_entrance(source_room, doors, room_obj): +def process_single_entrance(source_room: str, room_name: str, door_obj) -> RoomEntrance: global PAINTING_ENTRANCES, PAINTING_EXIT_ROOMS + entrance_type = EntranceType.NORMAL + if "painting" in door_obj and door_obj["painting"]: + entrance_type = EntranceType.PAINTING + elif "sunwarp" in door_obj and door_obj["sunwarp"]: + entrance_type = EntranceType.SUNWARP + elif "warp" in door_obj and door_obj["warp"]: + entrance_type = EntranceType.WARP + elif source_room == "Crossroads" and room_name == "Roof": + entrance_type = EntranceType.CROSSROADS_ROOF_ACCESS + + if "painting" in door_obj and door_obj["painting"]: + PAINTING_EXIT_ROOMS.add(room_name) + PAINTING_ENTRANCES += 1 + + if "door" in door_obj: + return RoomEntrance(source_room, RoomAndDoor( + door_obj["room"] if "room" in door_obj else None, + door_obj["door"] + ), entrance_type) + else: + return RoomEntrance(source_room, None, entrance_type) + + +def process_entrance(source_room, doors, room_obj): # If the value of an entrance is just True, that means that the entrance is always accessible. if doors is True: - room_obj.entrances.append(RoomEntrance(source_room, None, False)) + room_obj.entrances.append(RoomEntrance(source_room, None, EntranceType.NORMAL)) elif isinstance(doors, dict): # If the value of an entrance is a dictionary, that means the entrance requires a door to be accessible, is a # painting-based entrance, or both. - if "painting" in doors and "door" not in doors: - PAINTING_EXIT_ROOMS.add(room_obj.name) - PAINTING_ENTRANCES += 1 - - room_obj.entrances.append(RoomEntrance(source_room, None, True)) - else: - if "painting" in doors and doors["painting"]: - PAINTING_EXIT_ROOMS.add(room_obj.name) - PAINTING_ENTRANCES += 1 - - room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( - doors["room"] if "room" in doors else None, - doors["door"] - ), doors["painting"] if "painting" in doors else False)) + room_obj.entrances.append(process_single_entrance(source_room, room_obj.name, doors)) else: # If the value of an entrance is a list, then there are multiple possible doors that can give access to the - # entrance. + # entrance. If there are multiple connections with the same door (or lack of door) that differ only by entrance + # type, coalesce them into one entrance. + entrances: Dict[Optional[RoomAndDoor], EntranceType] = {} for door in doors: - if "painting" in door and door["painting"]: - PAINTING_EXIT_ROOMS.add(room_obj.name) - PAINTING_ENTRANCES += 1 + entrance = process_single_entrance(source_room, room_obj.name, door) + entrances[entrance.door] = entrances.get(entrance.door, EntranceType(0)) | entrance.type - room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor( - door["room"] if "room" in door else None, - door["door"] - ), door["painting"] if "painting" in door else False)) + for door, entrance_type in entrances.items(): + room_obj.entrances.append(RoomEntrance(source_room, door, entrance_type)) def process_panel(room_name, panel_name, panel_data): @@ -250,11 +264,6 @@ def process_door(room_name, door_name, door_data): else: include_reduce = False - if "junk_item" in door_data: - junk_item = door_data["junk_item"] - else: - junk_item = False - if "door_group" in door_data: door_group = door_data["door_group"] else: @@ -276,7 +285,7 @@ def process_door(room_name, door_name, door_data): panels.append(RoomAndPanel(None, panel)) else: skip_location = True - panels = None + panels = [] # The location name associated with a door can be explicitly specified in the configuration. If it is not, then the # name is generated using a combination of all of the panels that would ordinarily open the door. This can get quite @@ -312,8 +321,14 @@ def process_door(room_name, door_name, door_data): else: painting_ids = [] + door_type = DoorType.NORMAL + if door_name.endswith(" Sunwarp"): + door_type = DoorType.SUNWARP + elif room_name == "Pilgrim Antechamber" and door_name == "Sun Painting": + door_type = DoorType.SUN_PAINTING + door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, has_doors, - painting_ids, event, door_group, include_reduce, junk_item, item_group) + painting_ids, event, door_group, include_reduce, door_type, item_group) DOORS_BY_ROOM[room_name][door_name] = door_obj @@ -377,6 +392,15 @@ def process_painting(room_name, painting_data): PAINTINGS[painting_id] = painting_obj +def process_sunwarp(room_name, sunwarp_data): + global SUNWARP_ENTRANCES, SUNWARP_EXITS + + if sunwarp_data["direction"] == "enter": + SUNWARP_ENTRANCES[sunwarp_data["dots"] - 1] = room_name + else: + SUNWARP_EXITS[sunwarp_data["dots"] - 1] = room_name + + def process_progression(room_name, progression_name, progression_doors): global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM @@ -422,6 +446,10 @@ def process_room(room_name, room_data): for painting_data in room_data["paintings"]: process_painting(room_name, painting_data) + if "sunwarps" in room_data: + for sunwarp_data in room_data["sunwarps"]: + process_sunwarp(room_name, sunwarp_data) + if "progression" in room_data: for progression_name, progression_doors in room_data["progression"].items(): process_progression(room_name, progression_name, progression_doors) @@ -468,6 +496,8 @@ if __name__ == '__main__': "PAINTING_EXITS": PAINTING_EXITS, "REQUIRED_PAINTING_ROOMS": REQUIRED_PAINTING_ROOMS, "REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS": REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, + "SUNWARP_ENTRANCES": SUNWARP_ENTRANCES, + "SUNWARP_EXITS": SUNWARP_EXITS, "SPECIAL_ITEM_IDS": SPECIAL_ITEM_IDS, "PANEL_LOCATION_IDS": PANEL_LOCATION_IDS, "DOOR_LOCATION_IDS": DOOR_LOCATION_IDS, diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index ae0ac61c..831fee2a 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -37,12 +37,14 @@ configured_panels = Set[] mentioned_rooms = Set[] mentioned_doors = Set[] mentioned_panels = Set[] +mentioned_sunwarp_entrances = Set[] +mentioned_sunwarp_exits = Set[] door_groups = {} -directives = Set["entrances", "panels", "doors", "paintings", "progression"] +directives = Set["entrances", "panels", "doors", "paintings", "sunwarps", "progression"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"] -door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "junk_item", "event"] +door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"] painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] non_counting = 0 @@ -67,17 +69,17 @@ config.each do |room_name, room| entrances = [] if entrance.kind_of? Hash - if entrance.keys() != ["painting"] then - entrances = [entrance] - end + entrances = [entrance] elsif entrance.kind_of? Array entrances = entrance end entrances.each do |e| - entrance_room = e.include?("room") ? e["room"] : room_name - mentioned_rooms.add(entrance_room) - mentioned_doors.add(entrance_room + " - " + e["door"]) + if e.include?("door") then + entrance_room = e.include?("room") ? e["room"] : room_name + mentioned_rooms.add(entrance_room) + mentioned_doors.add(entrance_room + " - " + e["door"]) + end end end @@ -204,8 +206,8 @@ config.each do |room_name, room| end end - if not door.include?("id") and not door.include?("painting_id") and not door["skip_item"] and not door["event"] then - puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors or paintings" + if not door.include?("id") and not door.include?("painting_id") and not door.include?("warp_id") and not door["skip_item"] and not door["event"] then + puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors, paintings, or warps" end if door.include?("panels") @@ -292,6 +294,32 @@ config.each do |room_name, room| end end + (room["sunwarps"] || []).each do |sunwarp| + if sunwarp.include? "dots" and sunwarp.include? "direction" then + if sunwarp["dots"] < 1 or sunwarp["dots"] > 6 then + puts "#{room_name} :::: Contains a sunwarp with an invalid dots value" + end + + if sunwarp["direction"] == "enter" then + if mentioned_sunwarp_entrances.include? sunwarp["dots"] then + puts "Multiple #{sunwarp["dots"]} sunwarp entrances were found" + else + mentioned_sunwarp_entrances.add(sunwarp["dots"]) + end + elsif sunwarp["direction"] == "exit" then + if mentioned_sunwarp_exits.include? sunwarp["dots"] then + puts "Multiple #{sunwarp["dots"]} sunwarp exits were found" + else + mentioned_sunwarp_exits.add(sunwarp["dots"]) + end + else + puts "#{room_name} :::: Contains a sunwarp with an invalid direction value" + end + else + puts "#{room_name} :::: Contains a sunwarp without a dots and direction" + end + end + (room["progression"] || {}).each do |progression_name, door_list| door_list.each do |door| if door.kind_of? Hash then