From 7d79cff66fbc62cb4b123e28fead264f56938c97 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 16 Jun 2022 03:04:45 +0200 Subject: [PATCH] The Witness - 0.3.3 features and fixes (#617) New option: "Early Secret Area" (Opens a door to the Challenge Area from the start of the game) New option: Victory Conditions "Mountaintop Box Short" and "Mountaintop Box Long" New options: Number of Lasers of Mountain, Number of Lasers for Challenge New option & item: Add some number of "Puzzle Skips", which let you skip one puzzle in the game Many logic fixes --- worlds/witness/Disable_Unrandomized.txt | 5 +- worlds/witness/Door_Shuffle.txt | 30 +++++++ worlds/witness/Early_UTM.txt | 5 ++ worlds/witness/Options.py | 78 ++++++++++++++--- worlds/witness/WitnessItems.txt | 4 + worlds/witness/WitnessLogic.txt | 82 +++++++++--------- worlds/witness/__init__.py | 44 +++++++--- worlds/witness/items.py | 40 +++++++-- worlds/witness/locations.py | 31 ++++--- worlds/witness/player_logic.py | 108 ++++++++++++++++++++---- worlds/witness/rules.py | 23 ++--- worlds/witness/static_logic.py | 71 ++++++++++++++-- worlds/witness/utils.py | 14 ++- 13 files changed, 410 insertions(+), 125 deletions(-) create mode 100644 worlds/witness/Door_Shuffle.txt create mode 100644 worlds/witness/Early_UTM.txt diff --git a/worlds/witness/Disable_Unrandomized.txt b/worlds/witness/Disable_Unrandomized.txt index f5c60de6..cad3804f 100644 --- a/worlds/witness/Disable_Unrandomized.txt +++ b/worlds/witness/Disable_Unrandomized.txt @@ -1,5 +1,6 @@ Event Items: Shadows Laser Activation - 0x00021,0x17D28,0x17C71 +Keep Laser Activation - 0x03317 Bunker Laser Activation - 0x00061,0x17D01,0x17C42 Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9,0x17CA4 Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x00B8D,0x17CF7 @@ -26,7 +27,7 @@ Disabled Locations: 0x00055 (Orchard Apple Tree 3) 0x032F7 (Orchard Apple Tree 4) 0x032FF (Orchard Apple Tree 5) -0x168B5 (Shadows Lower Avoid 1) +0x198B5 (Shadows Lower Avoid 1) 0x198BD (Shadows Lower Avoid 2) 0x198BF (Shadows Lower Avoid 3) 0x19771 (Shadows Lower Avoid 4) @@ -53,6 +54,8 @@ Disabled Locations: 0x019E7 (Keep Hedge Maze 3) 0x01A0F (Keep Hedge Maze 4) 0x0360E (Laser Hedges) +0x00B10 (Monastery Door Open Left) +0x00C92 (Monastery Door Open Right) 0x00290 (Monastery Rhombic Avoid 1) 0x00038 (Monastery Rhombic Avoid 2) 0x00037 (Monastery Rhombic Avoid 3) diff --git a/worlds/witness/Door_Shuffle.txt b/worlds/witness/Door_Shuffle.txt new file mode 100644 index 00000000..7d48064c --- /dev/null +++ b/worlds/witness/Door_Shuffle.txt @@ -0,0 +1,30 @@ +100 - 0x01A54 - None - Glass Factory Entry Door +105 - 0x000B0 - 0x0343A - Door to Symmetry Island Lower +107 - 0x1C349 - 0x00076 - Door to Symmetry Island Upper +110 - 0x0C339 - 0x09F94 - Door to Desert Flood Light Room +111 - 0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B - None - Desert Flood Room Flood Controls +120 - 0x03678 - None - Quarry Mill Ramp Control +122 - 0x03679 - 0x014E8 - Quarry Mill Elevator Control +125 - 0x03852 - 0x034D4,0x021D5 - Quarry Boathouse Ramp Height Control +127 - 0x03858 - 0x021AE - Quarry Boathouse Ramp Horizontal Control +131 - 0x334DB,0x334DC - None - Shadows Door Timer +150 - 0x00B10 - None - Monastery Entry Door Left +151 - 0x00C92 - None - Monastery Entry Door Right +162 - 0x28998 - None - Town Door to RGB House +163 - 0x28A0D - 0x28998 - Town Door to Church +166 - 0x28A79 - None - Town Maze Panel (Drop-Down Staircase) +169 - 0x17F5F - None - Windmill Door +200 - 0x0288C - None - Treehouse First & Second Door +202 - 0x0A182 - None - Treehouse Third Door +205 - 0x2700B - None - Treehouse Laser House Door Timer +208 - 0x17CBC - None - Treehouse Shortcut Drop-Down Bridge +175 - 0x17CAB - 0x002C7 - Jungle Popup Wall +180 - 0x17C2E - None - Bunker Entry Door +183 - 0x0A099 - 0x09DAF - Inside Bunker Door to Bunker Proper +186 - 0x0A079 - None - Bunker Elevator Control +190 - 0x0056E - None - Swamp Entry Door +192 - 0x00609,0x18488 - 0x181A9 - Swamp Sliding Bridge +195 - 0x181F5 - None - Swamp Rotating Bridge +197 - 0x17C0A - None - Swamp Maze Control +300 - 0x0042D - None - Mountaintop River Shape Panel (Shortcut to Secret Area) +310 - 0x17CDF,0x17CC8,0x17CA6,0x09DB8,0x17C95,0x0A054 - None - Boat diff --git a/worlds/witness/Early_UTM.txt b/worlds/witness/Early_UTM.txt new file mode 100644 index 00000000..57da491e --- /dev/null +++ b/worlds/witness/Early_UTM.txt @@ -0,0 +1,5 @@ +Event Items: +Shortcut to Secret Area Opens - 0x0042D + +Region Changes: +Inside Mountain Secret Area (Inside Mountain Secret Area) - Inside Mountain Path to Secret Area - 0x00FF8 - Main Island - 0x021D7 | 0x0042D - Main Island - 0x17CF2 \ No newline at end of file diff --git a/worlds/witness/Options.py b/worlds/witness/Options.py index ce590810..72e85145 100644 --- a/worlds/witness/Options.py +++ b/worlds/witness/Options.py @@ -1,6 +1,6 @@ -from typing import Dict +from typing import Dict, Union from BaseClasses import MultiWorld -from Options import Toggle, DefaultOnToggle, Option, Range +from Options import Toggle, DefaultOnToggle, Option, Range, Choice # class HardMode(Toggle): @@ -18,6 +18,23 @@ class DisableNonRandomizedPuzzles(DefaultOnToggle): display_name = "Disable non randomized puzzles" +class EarlySecretArea(Toggle): + """The Mountainside shortcut to the Mountain Secret Area is open from the start. + (Otherwise known as "UTM", "Caves" or the "Challenge Area")""" + display_name = "Early Secret Area" + + +class ShuffleSymbols(DefaultOnToggle): + """You will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols.""" + display_name = "Shuffle Symbols" + + +class ShuffleDoors(Toggle): + """Many doors around the island will have their panels turned off initially. + You will need to find the items that power the panels to open those doors.""" + display_name = "Shuffle Doors" + + class ShuffleDiscardedPanels(Toggle): """Discarded Panels will have items on them. Solving certain Discarded Panels may still be necessary!""" @@ -40,10 +57,31 @@ class ShuffleHardLocations(Toggle): display_name = "Shuffle Hard Locations" -class ChallengeVictoryCondition(Toggle): - """The victory condition now becomes beating the Challenge area, - instead of the final elevator.""" - display_name = "Victory on beating the Challenge" +class VictoryCondition(Choice): + """Change the victory condition from the original game's ending (elevator) to beating the Challenge + or solving the mountaintop box, either using the short solution + (7 lasers or whatever you've changed it to) or the long solution (11 lasers or whatever you've changed it to).""" + display_name = "Victory Condition" + option_elevator = 0 + option_challenge = 1 + option_mountain_box_short = 2 + option_mountain_box_long = 3 + + +class MountainLasers(Range): + """Sets the amount of beams required to enter the final area.""" + display_name = "Required Lasers for Mountain Entry" + range_start = 1 + range_end = 7 + default = 7 + + +class ChallengeLasers(Range): + """Sets the amount of beams required to enter the secret area through the Mountain Bottom Layer Discard.""" + display_name = "Required Lasers for Challenge" + range_start = 1 + range_end = 11 + default = 11 class TrapPercentage(Range): @@ -54,16 +92,30 @@ class TrapPercentage(Range): default = 20 -the_witness_options: Dict[str, Option] = { +class PuzzleSkipAmount(Range): + """Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. + Works on most panels in the game - The only big exception is The Challenge.""" + display_name = "Puzzle Skips" + range_start = 0 + range_end = 20 + default = 5 + + +the_witness_options: Dict[str, type] = { # "hard_mode": HardMode, - # "unlock_symbols": UnlockSymbols, "disable_non_randomized_puzzles": DisableNonRandomizedPuzzles, "shuffle_discarded_panels": ShuffleDiscardedPanels, "shuffle_vault_boxes": ShuffleVaultBoxes, "shuffle_uncommon": ShuffleUncommonLocations, "shuffle_hard": ShuffleHardLocations, - "challenge_victory": ChallengeVictoryCondition, - "trap_percentage": TrapPercentage + "victory_condition": VictoryCondition, + "trap_percentage": TrapPercentage, + "early_secret_area": EarlySecretArea, + # "shuffle_symbols": ShuffleSymbols, + # "shuffle_doors": ShuffleDoors, + "mountain_lasers": MountainLasers, + "challenge_lasers": ChallengeLasers, + "puzzle_skip_amount": PuzzleSkipAmount, } @@ -71,10 +123,12 @@ def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool: return get_option_value(world, player, name) > 0 -def get_option_value(world: MultiWorld, player: int, name: str) -> int: +def get_option_value(world: MultiWorld, player: int, name: str) -> Union[bool, int]: option = getattr(world, name, None) if option is None: return 0 - return int(option[player].value) + if issubclass(the_witness_options[name], Toggle) or issubclass(the_witness_options[name], DefaultOnToggle): + return bool(option[player].value) + return option[player].value diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt index 75504974..9700a0e4 100644 --- a/worlds/witness/WitnessItems.txt +++ b/worlds/witness/WitnessItems.txt @@ -13,6 +13,10 @@ Progression: 71 - Black/White Squares 72 - Colored Squares +Usefuls: +101 - Functioning Brain +510 - Puzzle Skip + Boosts: 500 - Speed Boost diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt index f163f4e0..e4e63dc4 100644 --- a/worlds/witness/WitnessLogic.txt +++ b/worlds/witness/WitnessLogic.txt @@ -11,7 +11,7 @@ Tutorial (Tutorial) - First Hallway - 0x00182: 0x03629 (Gate Open) - 0x002C2 & 0x0A3B5 & 0x0A3B2 - True 0x03505 (Gate Close) - 0x2FAF6 - True 0x0C335 (Pillar) - True - Triangles - True -0x0C373 (Patio Floor) - 0x0C335 - True +0x0C373 (Patio Floor) - 0x0C335 - Dots Outside Tutorial (Outside Tutorial) - Tutorial - 0x03629: 0x033D4 (Vault) - True - Dots & Squares & Black/White Squares @@ -52,10 +52,10 @@ Inside Glass Factory (Glass Factory) - Outside Glass Factory - 0x01A54: 0x00084 (Melting 1) - 0x00083 - Symmetry 0x00082 (Melting 2) - 0x00084 - Symmetry 0x0343A (Melting 3) - 0x00082 - Symmetry -0x17CC8 (Boat Spawn) - True - Boat +0x17CC8 (Boat Spawn) - 0x0005C - Boat Outside Symmetry Island (Symmetry Island) - Main Island - True: -0x000B0 (Door to Symmetry Island Lower) - True - Dots +0x000B0 (Door to Symmetry Island Lower) - 0x0343A - Dots Symmetry Island Lower (Symmetry Island) - Outside Symmetry Island - 0x000B0: 0x00022 (Black Dots 1) - True - Symmetry & Dots @@ -138,17 +138,16 @@ Desert Water Levels Room (Desert) - Desert Pond Room - 0x0A249: 0x1831D (Raise Water Level Far Right) - True - True 0x1C2B1 (Raise Water Level Near Left) - True - True 0x1831B (Raise Water Level Near Right) - True - True -0x04D18 (Flood Reflection 1) - True - Reflection -0x01205 (Flood Reflection 2) - 0x04D18 - Reflection -0x181AB (Flood Reflection 3) - 0x01205 - Reflection -0x0117A (Flood Reflection 4) - 0x181AB - Reflection -0x17ECA (Flood Reflection 5) - 0x0117A - Reflection -0x18076 (Flood Reflection 6) - 0x17ECA - Reflection +0x04D18 (Flood Reflection 1) - 0x1C260 & 0x1831C - Reflection +0x01205 (Flood Reflection 2) - 0x04D18 & 0x1C260 & 0x1831C - Reflection +0x181AB (Flood Reflection 3) - 0x01205 & 0x1C260 & 0x1831C - Reflection +0x0117A (Flood Reflection 4) - 0x181AB & 0x1C260 & 0x1831C - Reflection +0x17ECA (Flood Reflection 5) - 0x0117A & 0x1C260 & 0x1831C - Reflection +0x18076 (Flood Reflection 6) - 0x17ECA & 0x1C260 & 0x1831C - Reflection Desert Elevator Room (Desert) - Desert Water Levels Room - 0x18076: 0x17C31 (Final Transparent Reflection) - True - Reflection 0x012D7 (Final Reflection) - 0x17C31 & 0x0A015 - Reflection -0x012D7 (Final Reflection) - 0x17C31 & 0x0A015 - Reflection 0x0A015 (Final Reflection Control) - 0x17C31 - True 0x0A15C (Final Bent Reflection 1) - True - Reflection 0x09FFF (Final Bent Reflection 2) - 0x0A15C - Reflection @@ -187,8 +186,8 @@ Quarry Mill (Quarry Mill) - Quarry - 0x01E59 & 0x01E5A: 0x03686 (Eraser and Squares 7) - 0x3C12D - Squares & Colored Squares & Eraser 0x014E9 (Eraser and Squares 8) - 0x03686 - Squares & Colored Squares & Eraser 0x03677 (Stair Control) - 0x014E8 - Squares & Colored Squares & Eraser -0x3C125 (Big Squares & Dots & and Eraser) - 0x0367C - Squares & Black/White Squares & Dots & Eraser -0x0367C (Small Squares & Dots & and Eraser) - 0x014E9 - Squares & Colored Squares & Dots & Eraser +0x3C125 (Big Squares & Dots & Eraser) - 0x0367C - Squares & Black/White Squares & Dots & Eraser +0x0367C (Small Squares & Dots & Eraser) - 0x014E9 - Squares & Colored Squares & Dots & Eraser 0x17CAC (Door to Outside Quarry Stairs) - True - True Quarry Boathouse (Quarry Boathouse) - Quarry - True: @@ -209,13 +208,13 @@ Quarry Boathouse (Quarry Boathouse) - Quarry - True: 0x09DB5 (Stars and Colored Eraser 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser 0x09DB1 (Stars and Colored Eraser 6) - 0x09DB5 - Stars & Stars + Same Colored Symbol & Eraser 0x3C124 (Stars and Colored Eraser 7) - 0x09DB1 - Stars & Stars + Same Colored Symbol & Eraser -0x09DB3 (Stars & Eraser & and Shapers 1) - 0x3C124 - Stars & Eraser & Shapers -0x09DB4 (Stars & Eraser & and Shapers 2) - 0x09DB3 - Stars & Eraser & Shapers +0x09DB3 (Stars & Eraser & Shapers 1) - 0x3C124 - Stars & Eraser & Shapers +0x09DB4 (Stars & Eraser & Shapers 2) - 0x09DB3 - Stars & Eraser & Shapers 0x275FA (Hook Control) - 0x03858 - Shapers & Eraser 0x17CA6 (Boat Spawn) - True - Boat -0x0A3CB (Stars & Eraser & and Shapers 3) - 0x09DB4 - Stars & Eraser & Shapers -0x0A3CC (Stars & Eraser & and Shapers 4) - 0x0A3CB - Stars & Eraser & Shapers -0x0A3D0 (Stars & Eraser & and Shapers 5) - 0x0A3CC - Stars & Eraser & Shapers +0x0A3CB (Stars & Eraser & Shapers 3) - 0x09DB4 - Stars & Eraser & Shapers +0x0A3CC (Stars & Eraser & Shapers 4) - 0x0A3CB - Stars & Eraser & Shapers +0x0A3D0 (Stars & Eraser & Shapers 5) - 0x0A3CC - Stars & Eraser & Shapers Shadows (Shadows) - Main Island - True - Keep Glass Plates - 0x09E49: 0x334DB (Door Timer Outside) - True - True @@ -239,8 +238,8 @@ Shadows (Shadows) - Main Island - True - Keep Glass Plates - 0x09E49: Shadows Ledge (Shadows) - Shadows - 0x334DB | 0x334DC | 0x0A8DC: 0x334DC (Door Timer Inside) - True - True -0x168B5 (Lower Avoid 1) - True - Shadows Avoid -0x198BD (Lower Avoid 2) - 0x168B5 - Shadows Avoid +0x198B5 (Lower Avoid 1) - True - Shadows Avoid +0x198BD (Lower Avoid 2) - 0x198B5 - Shadows Avoid 0x198BF (Lower Avoid 3) - 0x198BD & 0x334DC - Shadows Avoid 0x19771 (Lower Avoid 4) - 0x198BF - Shadows Avoid 0x0A8DC (Lower Avoid 5) - 0x19771 - Shadows Avoid @@ -319,7 +318,7 @@ Town (Town) - Main Island - True - Theater - 0x0A168 | 0x33AB2: 0x28ABF (Full Dot Grid Shapers 3) - 0x28A33 - Shapers & Rotated Shapers & Dots 0x28AC0 (Full Dot Grid Shapers 4) - 0x28ABF - Rotated Shapers & Dots 0x28AC1 (Full Dot Grid Shapers 5) - 0x28AC0 - Rotated Shapers & Dots -0x28AD9 (Shapers & Dots & and Eraser) - 0x28AC1 - Rotated Shapers & Dots & Eraser +0x28AD9 (Shapers & Dots & Eraser) - 0x28AC1 - Rotated Shapers & Dots & Eraser 0x17F5F (Windmill Door) - True - Dots RGB House (Town) - Town - 0x28998: @@ -415,7 +414,7 @@ Swamp Entry Area (Swamp) - Outside Swamp - 0x0056E: 0x00985 (Combinable Shapers 6) - 0x00986 - Shapers 0x00987 (Combinable Shapers 7) - 0x00985 - Shapers 0x181A9 (Combinable Shapers 8) - 0x00987 - Shapers -0x00609 (Slide Bridge) - 0x181A9 - Shapers +0x00609 (Sliding Bridge) - 0x181A9 - Shapers Swamp Near Platform (Swamp) - Swamp Entry Area - 0x00609 | 0x18488: 0x00999 (Broken Shapers 1) - 0x00990 - Broken Shapers @@ -447,24 +446,25 @@ Swamp Rotating Bridge Near Side (Swamp) - Swamp Near Platform - 0x009A1: 0x014D4 (Red Underwater Negative Shapers 3) - 0x00596 - Shapers & Negative Shapers 0x014D1 (Red Underwater Negative Shapers 4) - 0x00596 - Shapers & Negative Shapers -Swamp Near Boat (Swamp) - Swamp Rotating Bridge Near Side - 0x009A1 - Swamp Platform - 0x17C0D & 0x17C0E: -0x181F5 (Rotating Bridge) - True - Rotated Shapers, Shapers +Swamp Near Boat (Swamp) - Swamp Rotating Bridge Near Side - 0x0000A - Swamp Platform - 0x17C0D & 0x17C0E: +0x181F5 (Rotating Bridge) - True - Rotated Shapers & Shapers 0x09DB8 (Boat Spawn) - True - Boat 0x003B2 (More Rotated Shapers 1) - 0x0000A - Rotated Shapers 0x00A1E (More Rotated Shapers 2) - 0x003B2 - Rotated Shapers 0x00C2E (More Rotated Shapers 3) - 0x00A1E - Rotated Shapers 0x00E3A (More Rotated Shapers 4) - 0x00C2E - Rotated Shapers -0x009A6 (Underwater Back Optional) - 0x00E3A - Shapers +0x009A6 (Underwater Back Optional) - 0x00E3A & 0x181F5 - Shapers 0x009AB (Blue Underwater Negative Shapers 1) - 0x00E3A - Shapers & Negative Shapers 0x009AD (Blue Underwater Negative Shapers 2) - 0x009AB - Shapers & Negative Shapers 0x009AE (Blue Underwater Negetive Shapers 3) - 0x009AD - Shapers & Negative Shapers 0x009AF (Blue Underwater Negative Shapers 4) - 0x009AE - Shapers & Negative Shapers 0x00006 (Blue Underwater Negative Shapers 5) - 0x009AF - Shapers & Negative Shapers & Broken Negative Shapers -0x17E2B (Long Bridge Control) - True - Rotated Shapers +0x17E2B (Long Bridge Control) - True - Rotated Shapers & Shapers Swamp Maze (Swamp) - Swamp Rotating Bridge Near Side - 0x00001 & 0x014D2 & 0x014D4 & 0x014D1 - Outside Swamp - 0x17C05 & 0x17C02: -0x17C04 (Maze Control) - True - Shapers & Negative Shapers & Rotated Shapers & Environment -0x03615 (Laser) - 0x17C04 - True +0x17C0A (Maze Control) - True - Shapers & Negative Shapers & Rotated Shapers & Environment +0x17E07 (Maze Control Other Side) - True - Shapers & Negative Shapers & Rotated Shapers & Environment +0x03615 (Laser) - 0x17C0A & 0x17E07 - True 0x17C05 (Near Laser Shortcut Door Left) - True - Rotated Shapers 0x17C02 (Near Laser Shortcut Door Right) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers @@ -543,7 +543,8 @@ Treehouse Bridge Platform (Treehouse) - Treehouse Beyond Yellow Bridge - 0x17DA2 Mountaintop (Mountaintop) - Main Island - True: 0x0042D (River Shape) - True - True -0x09F7F (Box Open) - 7 Lasers - True +0x09F7F (Box Short) - 7 Lasers - True +0xFFF00 (Box Long) - 11 Lasers & 0x17C34 - True 0x17C34 (Trap Door Triple Exit) - 0x09F7F - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol 0x17C42 (Discard) - True - Triangles 0x002A6 (Vault) - True - Symmetry & Colored Dots & Squares & Black/White Squares & Dots @@ -577,7 +578,7 @@ Inside Mountain Second Layer (Inside Mountain) - Inside Mountain Top Layer Bridg 0x09FD6 (Color Cycle 3) - 0x09FD4 - Color Cycle & RGB & Stars & Squares & Colored Squares & Stars + Same Colored Symbol 0x09FD7 (Color Cycle 4) - 0x09FD6 - Color Cycle & RGB & Stars & Squares & Colored Squares & Stars + Same Colored Symbol & Shapers & Colored Shapers 0x09FD8 (Color Cycle 5) - 0x09FD7 - Color Cycle & RGB & Squares & Colored Squares & Symmetry & Colored Dots -0x09E86 (Light Bridge Controller 2) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Colored Rotated Shapers & Eraser & Two Lines +0x09E86 (Light Bridge Controller 2) - 0x09FD8 - Stars & Stars + Same Colored Symbol & Colored Rotated Shapers & Rotated Shapers & Eraser & Two Lines Inside Mountain Second Layer Beyond Bridge (Inside Mountain) - Inside Mountain Second Layer - 0x09E86: 0x09FCC (Same Solution 1) - True - Dots & Same Solution @@ -586,7 +587,7 @@ Inside Mountain Second Layer Beyond Bridge (Inside Mountain) - Inside Mountain S 0x09FD0 (Same Solution 4) - 0x09FCF - Rotated Shapers & Same Solution 0x09FD1 (Same Solution 5) - 0x09FD0 - Stars & Squares & Colored Squares & Stars + Same Colored Symbol & Same Solution 0x09FD2 (Same Solution 6) - 0x09FD1 - Shapers & Same Solution -0x09ED8 (Light Bridge Controller 3) - 0x09FD2 - Stars & Stars + Same Colored Symbol & Colored Rotated Shapers & Eraser & Two Lines +0x09ED8 (Light Bridge Controller 3) - 0x09FD2 - Stars & Stars + Same Colored Symbol & Colored Rotated Shapers & Rotated Shapers & Eraser & Two Lines Inside Mountain Second Layer Elevator (Inside Mountain) - Inside Mountain Second Layer - 0x09ED8 & 0x09E86: 0x09EEB (Elevator Control Panel) - True - Dots @@ -600,10 +601,9 @@ Inside Mountain Third Layer (Inside Mountain) - Inside Mountain Second Layer Ele 0x09FDA (Giant Puzzle) - 0x09FC1 & 0x09F8E & 0x09F01 & 0x09EFF - Shapers & Symmetry Inside Mountain Bottom Layer (Inside Mountain) - Inside Mountain Third Layer - 0x09FDA - Inside Mountain Path to Secret Area - 0x334E1: -0x17FA2 (Bottom Layer Discard) - 11 Lasers & 0x09F7F - Triangles & Environment +0x17FA2 (Bottom Layer Discard) - 0xFFF00 - Triangles & Environment 0x01983 (Door to Final Room Left) - True - Shapers & Stars -0x01987 (Door to Final Room Right) - True - Squares & Colored Squares - +0x01987 (Door to Final Room Right) - True - Squares & Colored Squares & Dots Inside Mountain Path to Secret Area (Inside Mountain) - Inside Mountain Bottom Layer - 0x17FA2: 0x00FF8 (Door to Secret Area) - True - Triangles & Black/White Squares & Squares @@ -636,17 +636,17 @@ Inside Mountain Secret Area (Inside Mountain Secret Area) - Inside Mountain Path 0x32962 (Rotated Broken Shapers) - True - Rotated Shapers & Broken Rotated Shapers 0x32966 (Stars and Squares) - True - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol 0x01A31 (Rainbow Squares) - True - Color Cycle & RGB & Squares & Colored Squares -0x00B71 (Squares & Stars and Colored Eraser) - True - Colored Eraser & Squares & Colored Squares & Stars & Stars + Same Colored Symbol +0x00B71 (Squares & Stars and Colored Eraser) - True - Colored Eraser & Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Eraser 0x09DD5 (Lone Pillar) - True - Pillar & Triangles 0x0A16E (Door to Challenge) - 0x09DD5 - Stars & Shapers & Colored Shapers & Stars + Same Colored Symbol 0x288EA (Wooden Beam Shapers) - True - Environment & Shapers 0x288FC (Wooden Beam Squares and Shapers) - True - Environment & Squares & Black/White Squares & Shapers & Rotated Shapers -0x289E7 (Wooden Beam Shapers and Squares) - True - Environment & Stars & Squares & Black/White Squares +0x289E7 (Wooden Beam Stars and Squares) - True - Environment & Stars & Squares & Black/White Squares 0x288AA (Wooden Beam Shapers and Stars) - True - Environment & Stars & Shapers 0x17FB9 (Upstairs Dot Grid Negative Shapers) - True - Shapers & Dots & Negative Shapers -0x0A16B (Upstairs Dot Grid Squares) - True - Squares & Black/White Squares & Colored Squares & Dots +0x0A16B (Upstairs Dot Grid Gap Dots) - True - Dots 0x0A2CE (Upstairs Dot Grid Stars) - 0x0A16B - Stars & Dots -0x0A2D7 (Upstairs Dot Grid Triangles) - 0x0A2CE - Triangles & Dots +0x0A2D7 (Upstairs Dot Grid Stars & Squares) - 0x0A2CE - Dots & Black/White Squares & Stars + Same Colored Symbol & Stars 0x0A2DD (Upstairs Dot Grid Shapers) - 0x0A2D7 - Shapers & Dots 0x0A2EA (Upstairs Dot Grid Rotated Shapers) - 0x0A2DD - Rotated Shapers & Dots 0x0008F (Upstairs Invisible Dots 1) - True - Dots & Invisible Dots @@ -662,7 +662,7 @@ Inside Mountain Secret Area (Inside Mountain Secret Area) - Inside Mountain Path 0x00029 (Upstairs Invisible Dot Symmetry 3) - 0x00028 - Dots & Invisible Dots & Symmetry Challenge (Challenge) - Inside Mountain Secret Area - 0x0A16E: -0x0A332 (Start Timer) - True - True +0x0A332 (Start Timer) - 11 Lasers - True 0x0088E (Small Basic) - 0x0A332 - True 0x00BAF (Big Basic) - 0x0088E - True 0x00BF3 (Square) - 0x00BAF - Squares & Black/White Squares @@ -694,11 +694,11 @@ Final Room (Inside Mountain Final Room) - Inside Mountain Bottom Layer - 0x01983 0x0383A (Stars Pillar) - True - Stars & Pillar 0x09E56 (Stars and Dots Pillar) - 0x0383A - Stars & Dots & Pillar 0x09E5A (Dot Grid Pillar) - 0x09E56 - Dots & Pillar -0x33961 (Sparse Dots Pillar) - 0x09E5A - Dots & Pillar +0x33961 (Sparse Dots Pillar) - 0x09E5A - Dots & Symmetry & Pillar 0x0383D (Dot Maze Pillar) - True - Dots & Pillar 0x0383F (Squares Pillar) - 0x0383D - Squares & Black/White Squares & Pillar 0x03859 (Shapers Pillar) - 0x0383F - Shapers & Pillar -0x339BB (Squares and Stars) - 0x03859 - Squares & Black/White Squares & Stars & Pillar +0x339BB (Squares and Stars) - 0x03859 - Squares & Black/White Squares & Stars & Symmetry & Pillar Elevator (Inside Mountain Final Room) - Final Room - 0x339BB & 0x33961: 0x3D9A6 (Elevator Door Closer Left) - True - True @@ -709,4 +709,4 @@ Elevator (Inside Mountain Final Room) - Final Room - 0x339BB & 0x33961: 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True 0x3D9A9 (Elevator Start) - 0x3D9AA | 0x3D9A8 - True -Boat (Boat) - Main Island - 0x17CDF | 0x17CC8 | 0x17CA6 | 0x09DB8 | 0x17C95 - Inside Glass Factory - 0x17CDF | 0x17CC8 | 0x17CA6 | 0x09DB8 | 0x17C95 - Quarry Boathouse - 0x17CDF | 0x17CC8 | 0x17CA6 | 0x09DB8 | 0x17C95 - Swamp Near Boat - 0x17CDF | 0x17CC8 | 0x17CA6 | 0x09DB8 | 0x17C95 - Treehouse Entry Area - 0x17CDF | 0x17CC8 | 0x17CA6 | 0x09DB8 | 0x17C95: +Boat (Boat) - Main Island - 0x17CDF | 0x17CC8 & 0x0005C | 0x17CA6 | 0x09DB8 | 0x17C95 | 0x0A054 - Inside Glass Factory - 0x17CDF & 0x0005C | 0x17CC8 & 0x0005C | 0x17CA6 & 0x0005C | 0x09DB8 & 0x0005C | 0x17C95 & 0x0005C | 0x0A054 & 0x0005C - Quarry Boathouse - 0x17CA6 - Swamp Near Boat - 0x17CDF | 0x17CC8 & 0x0005C | 0x17CA6 | 0x09DB8 | 0x17C95 | 0x0A054 - Treehouse Entry Area - 0x17CDF | 0x17CC8 & 0x0005C | 0x17CA6 | 0x09DB8 | 0x17C95 | 0x0A054: diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 76132938..58e78da2 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -11,7 +11,7 @@ from .locations import WitnessPlayerLocations, StaticWitnessLocations from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems from .rules import set_rules from .regions import WitnessRegions -from .Options import is_option_enabled, the_witness_options +from .Options import is_option_enabled, the_witness_options, get_option_value from .utils import best_junk_to_add_based_on_weights @@ -50,7 +50,9 @@ class WitnessWorld(World): return { 'seed': self.world.random.randint(0, 1000000), 'victory_location': int(self.player_logic.VICTORY_LOCATION, 16), - 'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID + 'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID, + 'doorhex_to_id': self.player_logic.DOOR_DICT_FOR_CLIENT, + 'door_connections_to_sever': self.player_logic.DOOR_CONNECTIONS_TO_SEVER } def generate_early(self): @@ -67,20 +69,36 @@ class WitnessWorld(World): items_by_name = dict() for item in self.items.ITEM_TABLE: witness_item = self.create_item(item) - if item not in self.items.EVENT_ITEM_TABLE: + if item in self.items.PROGRESSION_TABLE: pool.append(witness_item) items_by_name[item] = witness_item - # Put good item on first check - random_good_item = self.world.random.choice(self.items.GOOD_ITEMS) - first_check = self.world.get_location( - "Tutorial Gate Open", self.player - ) - first_check.place_locked_item(items_by_name[random_good_item]) - pool.remove(items_by_name[random_good_item]) + less_junk = 0 + + # Put good item on first check if symbol shuffle is on + # symbols = is_option_enabled(self.world, self.player, "shuffle_symbols") + symbols = True + + if symbols: + random_good_item = self.world.random.choice(self.items.GOOD_ITEMS) + first_check = self.world.get_location( + "Tutorial Gate Open", self.player + ) + first_check.place_locked_item(items_by_name[random_good_item]) + pool.remove(items_by_name[random_good_item]) + + less_junk = 1 + + for item in self.items.EXTRA_AMOUNTS: + witness_item = self.create_item(item) + for i in range(0, self.items.EXTRA_AMOUNTS[item]): + if len(pool) < len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) - less_junk: + pool.append(witness_item) # Put in junk items to fill the rest - junk_size = len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - 1 + junk_size = len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - less_junk + + print(junk_size) for i in range(0, junk_size): pool.append(self.create_item(self.get_filler_item_name())) @@ -107,7 +125,7 @@ class WitnessWorld(World): slot_data["hard_mode"] = False for option_name in the_witness_options: - slot_data[option_name] = is_option_enabled( + slot_data[option_name] = get_option_value( self.world, self.player, option_name ) @@ -124,6 +142,8 @@ class WitnessWorld(World): name, item.progression, item.code, player=self.player ) new_item.trap = item.trap + if item.never_exclude: + new_item.never_exclude = True return new_item def get_filler_item_name(self) -> str: # Used by itemlinks diff --git a/worlds/witness/items.py b/worlds/witness/items.py index 47bbd1fb..0b4824e5 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/items.py @@ -6,7 +6,7 @@ from typing import Dict, NamedTuple, Optional from BaseClasses import Item, MultiWorld from . import StaticWitnessLogic, WitnessPlayerLocations, WitnessPlayerLogic -from .Options import get_option_value, is_option_enabled +from .Options import get_option_value, is_option_enabled, the_witness_options from fractions import Fraction @@ -18,6 +18,7 @@ class ItemData(NamedTuple): progression: bool event: bool = False trap: bool = False + never_exclude: bool = False class WitnessItem(Item): @@ -50,7 +51,7 @@ class StaticWitnessItems: def __init__(self): item_tab = dict() - for item in StaticWitnessLogic.ALL_ITEMS: + for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS.union(StaticWitnessLogic.ALL_DOOR_ITEMS): if item[0] == "11 Lasers" or item == "7 Lasers": continue @@ -64,6 +65,9 @@ class StaticWitnessItems: for item in StaticWitnessLogic.ALL_BOOSTS: item_tab[item[0]] = ItemData(158000 + item[1], False, False) + for item in StaticWitnessLogic.ALL_USEFULS: + item_tab[item[0]] = ItemData(158000 + item[1], False, False, False, True) + item_tab = dict(sorted( item_tab.items(), key=lambda single_item: single_item[1].code @@ -83,11 +87,35 @@ class WitnessPlayerItems: """Adds event items after logic changes due to options""" self.EVENT_ITEM_TABLE = dict() self.ITEM_TABLE = copy.copy(StaticWitnessItems.ALL_ITEM_TABLE) + self.PROGRESSION_TABLE = dict() - self.GOOD_ITEMS = [ - "Dots", "Black/White Squares", "Stars", - "Shapers", "Symmetry" - ] + self.EXTRA_AMOUNTS = { + "Functioning Brain": 1, + "Puzzle Skip": get_option_value(world, player, "puzzle_skip_amount") + } + + for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS.union(StaticWitnessLogic.ALL_DOOR_ITEMS): + if item not in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME: + del self.ITEM_TABLE[item[0]] + else: + self.PROGRESSION_TABLE[item[0]] = self.ITEM_TABLE[item[0]] + + symbols = is_option_enabled(world, player, "shuffle_symbols") + + if "shuffle_symbols" not in the_witness_options.keys(): + symbols = True + + doors = is_option_enabled(world, player, "shuffle_doors") + + if doors and symbols: + self.GOOD_ITEMS = [ + "Dots", "Black/White Squares", "Symmetry" + ] + elif symbols: + self.GOOD_ITEMS = [ + "Dots", "Black/White Squares", "Stars", + "Shapers", "Symmetry" + ] if is_option_enabled(world, player, "shuffle_discarded_panels"): self.GOOD_ITEMS.append("Triangles") diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index 0bfaf011..380c64c0 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -19,6 +19,12 @@ class StaticWitnessLocations: "Laser": 700, } + EXTRA_LOCATIONS = { + "Tutorial Front Left", + "Tutorial Back Left", + "Tutorial Back Right", + } + GENERAL_LOCATIONS = { "Tutorial Gate Open", @@ -50,11 +56,12 @@ class StaticWitnessLocations: "Quarry Mill Eraser and Dots 6", "Quarry Mill Eraser and Squares 8", - "Quarry Mill Small Squares & Dots & and Eraser", + "Quarry Mill Small Squares & Dots & Eraser", "Quarry Boathouse Intro Shapers", + "Quarry Boathouse Intro Stars", "Quarry Boathouse Eraser and Shapers 5", - "Quarry Boathouse Stars & Eraser & and Shapers 2", - "Quarry Boathouse Stars & Eraser & and Shapers 5", + "Quarry Boathouse Stars & Eraser & Shapers 2", + "Quarry Boathouse Stars & Eraser & Shapers 5", "Quarry Discard", "Quarry Laser", @@ -82,7 +89,7 @@ class StaticWitnessLocations: "Town Rooftop Discard", "Town Symmetry Squares 5 + Dots", "Town Full Dot Grid Shapers 5", - "Town Shapers & Dots & and Eraser", + "Town Shapers & Dots & Eraser", "Town Laser", "Theater Discard", @@ -138,7 +145,7 @@ class StaticWitnessLocations: UNCOMMON_LOCATIONS = { "Mountaintop River Shape", "Tutorial Patio Floor", - "Quarry Mill Big Squares & Dots & and Eraser", + "Quarry Mill Big Squares & Dots & Eraser", "Theater Tutorial Video", "Theater Desert Video", "Theater Jungle Video", @@ -165,11 +172,11 @@ class StaticWitnessLocations: "Inside Mountain Secret Area Lone Pillar", "Inside Mountain Secret Area Wooden Beam Shapers", "Inside Mountain Secret Area Wooden Beam Squares and Shapers", - "Inside Mountain Secret Area Wooden Beam Shapers and Squares", + "Inside Mountain Secret Area Wooden Beam Stars and Squares", "Inside Mountain Secret Area Wooden Beam Shapers and Stars", "Inside Mountain Secret Area Upstairs Invisible Dots 8", "Inside Mountain Secret Area Upstairs Invisible Dot Symmetry 3", - "Inside Mountain Secret Area Upstairs Dot Grid Shapers", + "Inside Mountain Secret Area Upstairs Dot Grid Negative Shapers", "Inside Mountain Secret Area Upstairs Dot Grid Rotated Shapers", "Challenge Vault Box", @@ -241,6 +248,13 @@ class WitnessPlayerLocations: if is_option_enabled(world, player, "shuffle_hard"): self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.HARD_LOCATIONS + if is_option_enabled(world, player, "shuffle_symbols") and is_option_enabled(world, player, "shuffle_doors"): + if is_option_enabled(world, player, "disable_non_randomized_puzzles"): + # This particular combination of logic settings leads to logic so restrictive that generation can fail + # Hence, we add some extra sphere 0 locations + + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.EXTRA_LOCATIONS + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - { @@ -259,9 +273,6 @@ class WitnessPlayerLocations: event_locations = { p for p in player_logic.NECESSARY_EVENT_PANELS - if StaticWitnessLogic.CHECKS_BY_HEX[p]["checkName"] - not in self.CHECK_LOCATIONS - or p in player_logic.ALWAYS_EVENT_HEX_CODES } self.EVENT_LOCATION_TABLE = { diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 82a6d07b..689403dc 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -18,13 +18,22 @@ When the world has parsed its options, a second function is called to finalize t import copy from BaseClasses import MultiWorld from .static_logic import StaticWitnessLogic -from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda -from .Options import is_option_enabled +from .utils import define_new_region, get_disable_unrandomized_list, parse_lambda, get_early_utm_list +from .Options import is_option_enabled, get_option_value, the_witness_options class WitnessPlayerLogic: """WITNESS LOGIC CLASS""" + def update_door_dict(self, panel_hex): + item_id = StaticWitnessLogic.ALL_DOOR_ITEM_IDS_BY_HEX.get(panel_hex) + + if item_id is None: + return + + self.DOOR_DICT_FOR_CLIENT[panel_hex] = item_id + self.DOOR_CONNECTIONS_TO_SEVER.update(StaticWitnessLogic.CONNECTIONS_TO_SEVER_BY_DOOR_HEX[panel_hex]) + def reduce_req_within_region(self, panel_hex): """ Panels in this game often only turn on when other panels are solved. @@ -34,13 +43,27 @@ class WitnessPlayerLogic: Panels outside of the same region will still be checked manually. """ - if self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"] == frozenset({frozenset()}): - return self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"] + these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"] + + real_items = {item[0] for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME} + + these_items = frozenset({ + subset.intersection(real_items) + for subset in these_items + }) + + these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"] + + if StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex) in real_items: + self.update_door_dict(panel_hex) + + these_panels = frozenset({frozenset()}) + + if these_panels == frozenset({frozenset()}): + return these_items all_options = set() - these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"] - these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"] check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex] for option in these_panels: @@ -57,6 +80,9 @@ class WitnessPlayerLogic: elif dep_obj["region"]["name"] != check_obj["region"]["name"]: new_items = frozenset({frozenset([option_panel])}) self.EVENT_PANELS_FROM_PANELS.add(option_panel) + elif option_panel in self.ALWAYS_EVENT_NAMES_BY_HEX.keys(): + new_items = frozenset({frozenset([option_panel])}) + self.EVENT_PANELS_FROM_PANELS.add(option_panel) else: new_items = self.reduce_req_within_region(option_panel) @@ -105,7 +131,7 @@ class WitnessPlayerLogic: line_split = line.split(" - ") required_items = parse_lambda(line_split[2]) - items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_ITEMS} + items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_SYMBOL_ITEMS} required_items = frozenset( subset.intersection(items_actually_in_the_game) for subset in required_items @@ -121,7 +147,14 @@ class WitnessPlayerLogic: return if adj_type == "Disabled Locations": - self.COMPLETELY_DISABLED_CHECKS.add(line[:7]) + panel_hex = line[:7] + + self.COMPLETELY_DISABLED_CHECKS.add(panel_hex) + + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = { + item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME + if item[0] != StaticWitnessLogic.DOOR_NAMES_BY_HEX.get(panel_hex) + } return @@ -139,10 +172,14 @@ class WitnessPlayerLogic: """Makes logic adjustments based on options""" adjustment_linesets_in_order = [] - if is_option_enabled(world, player, "challenge_victory"): - self.VICTORY_LOCATION = "0x0356B" - else: + if get_option_value(world, player, "victory_condition") == 0: self.VICTORY_LOCATION = "0x3D9A9" + elif get_option_value(world, player, "victory_condition") == 1: + self.VICTORY_LOCATION = "0x0356B" + elif get_option_value(world, player, "victory_condition") == 2: + self.VICTORY_LOCATION = "0x09F7F" + elif get_option_value(world, player, "victory_condition") == 3: + self.VICTORY_LOCATION = "0xFFF00" self.COMPLETELY_DISABLED_CHECKS.add( self.VICTORY_LOCATION @@ -151,6 +188,20 @@ class WitnessPlayerLogic: if is_option_enabled(world, player, "disable_non_randomized_puzzles"): adjustment_linesets_in_order.append(get_disable_unrandomized_list()) + if is_option_enabled(world, player, "shuffle_symbols") or "shuffle_symbols" not in the_witness_options.keys(): + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_SYMBOL_ITEMS) + + if is_option_enabled(world, player, "shuffle_doors"): + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.update(StaticWitnessLogic.ALL_DOOR_ITEMS) + + if is_option_enabled(world, player, "early_secret_area"): + adjustment_linesets_in_order.append(get_early_utm_list()) + else: + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = { + item for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME + if item[0] != "Mountaintop River Shape Power On" + } + for adjustment_lineset in adjustment_linesets_in_order: current_adjustment_type = None @@ -182,6 +233,17 @@ class WitnessPlayerLogic: pair = (name, self.EVENT_ITEM_NAMES[panel]) return pair + def _regions_are_adjacent(self, region1, region2): + for connection in self.CONNECTIONS_BY_REGION_NAME[region1]: + if connection[0] == region2: + return True + + for connection in self.CONNECTIONS_BY_REGION_NAME[region2]: + if connection[0] == region1: + return True + + return False + def make_event_panel_lists(self): """ Special event panel data structures @@ -197,7 +259,6 @@ class WitnessPlayerLogic: self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS) self.ORIGINAL_EVENT_PANELS.update(self.EVENT_PANELS_FROM_REGIONS) - self.NECESSARY_EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS) for panel in self.EVENT_PANELS_FROM_REGIONS: for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items(): @@ -213,6 +274,15 @@ class WitnessPlayerLogic: if panel not in region["panels"] | connected_r["panels"]: self.NECESSARY_EVENT_PANELS.add(panel) + for event_panel in self.EVENT_PANELS_FROM_PANELS: + for panel, panel_req in self.REQUIREMENTS_BY_HEX.items(): + if any([event_panel in item_set for item_set in panel_req]): + region1 = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"] + region2 = StaticWitnessLogic.CHECKS_BY_HEX[event_panel]["region"]["name"] + + if not self._regions_are_adjacent(region1, region2): + self.NECESSARY_EVENT_PANELS.add(event_panel) + for always_hex, always_item in self.ALWAYS_EVENT_NAMES_BY_HEX.items(): self.ALWAYS_EVENT_HEX_CODES.add(always_hex) self.NECESSARY_EVENT_PANELS.add(always_hex) @@ -226,6 +296,10 @@ class WitnessPlayerLogic: self.EVENT_PANELS_FROM_PANELS = set() self.EVENT_PANELS_FROM_REGIONS = set() + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set() + self.DOOR_DICT_FOR_CLIENT = dict() + self.DOOR_CONNECTIONS_TO_SEVER = set() + self.CONNECTIONS_BY_REGION_NAME = copy.copy(StaticWitnessLogic.STATIC_CONNECTIONS_BY_REGION_NAME) self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(StaticWitnessLogic.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) self.REQUIREMENTS_BY_HEX = dict() @@ -257,7 +331,9 @@ class WitnessPlayerLogic: "0x09ED8": "Inside Mountain Second Layer Both Light Bridges Solved", "0x0A3D0": "Quarry Laser Boathouse Requirement Met", "0x00596": "Swamp Red Water Drains", - "0x28B39": "Town Tower 4th Door Opens" + "0x28B39": "Town Tower 4th Door Opens", + "0x0343A": "Door to Symmetry Island Powers On", + "0xFFF00": "Inside Mountain Bottom Layer Discard Turns On" } self.ALWAYS_EVENT_NAMES_BY_HEX = { @@ -266,8 +342,8 @@ class WitnessPlayerLogic: "0x09F98": "Desert Laser Redirection", "0x03612": "Quarry Laser Activation", "0x19650": "Shadows Laser Activation", - "0x0360E": "Keep Laser Hedges Activation", - "0x03317": "Keep Laser Pressure Plates Activation", + "0x0360E": "Keep Laser Activation", + "0x03317": "Keep Laser Activation", "0x17CA4": "Monastery Laser Activation", "0x032F5": "Town Laser Activation", "0x03616": "Jungle Laser Activation", @@ -280,6 +356,8 @@ class WitnessPlayerLogic: "0x03481": "Tutorial Video Pattern Knowledge", "0x03702": "Jungle Video Pattern Knowledge", "0x2FAF6": "Theater Walkway Video Pattern Knowledge", + "0x09F7F": "Mountaintop Trap Door Turns On", + "0x17C34": "Mountain Access", } self.make_options_adjustments(world, player) diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py index e7ef30aa..1f13074a 100644 --- a/worlds/witness/rules.py +++ b/worlds/witness/rules.py @@ -7,7 +7,7 @@ depending on the items received from BaseClasses import MultiWorld from .player_logic import WitnessPlayerLogic -from .Options import is_option_enabled +from .Options import is_option_enabled, get_option_value from .locations import WitnessPlayerLocations from . import StaticWitnessLogic from ..AutoWorld import LogicMixin @@ -27,10 +27,7 @@ class WitnessLogic(LogicMixin): and self.has("Desert Laser Redirection", player)) lasers += int(self.has("Town Laser Activation", player)) lasers += int(self.has("Monastery Laser Activation", player)) - lasers += int(self.has("Keep Laser Pressure Plates Activation", player) and ( - is_option_enabled(world, player, "disable_non_randomized_puzzles") - or self.has("Keep Laser Hedges Activation", player) - )) + lasers += int(self.has("Keep Laser Activation", player)) lasers += int(self.has("Quarry Laser Activation", player)) lasers += int(self.has("Treehouse Laser Activation", player)) lasers += int(self.has("Jungle Laser Activation", player)) @@ -57,7 +54,6 @@ class WitnessLogic(LogicMixin): and check_name + " Solved" not in locat.EVENT_LOCATION_TABLE and not self._witness_safe_manual_panel_check(panel, world, player, player_logic, locat)): return False - return True def _witness_meets_item_requirements(self, panel, world, player, player_logic: WitnessPlayerLogic, locat): @@ -76,22 +72,15 @@ class WitnessLogic(LogicMixin): for item in option: if item == "7 Lasers": - if not self._witness_has_lasers(world, player, 7): + if not self._witness_has_lasers(world, player, get_option_value(world, player, "mountain_lasers")): valid_option = False break elif item == "11 Lasers": - if not self._witness_has_lasers(world, player, 11): + if not self._witness_has_lasers(world, player, get_option_value(world, player, "challenge_lasers")): valid_option = False break - elif item in player_logic.NECESSARY_EVENT_PANELS: - if StaticWitnessLogic.CHECKS_BY_HEX[item]["checkName"] + " Solved" in locat.EVENT_LOCATION_TABLE: - valid_option = self.has(player_logic.EVENT_ITEM_NAMES[item], player) - else: - valid_option = self.can_reach( - StaticWitnessLogic.CHECKS_BY_HEX[item]["checkName"], "Location", player - ) - if not valid_option: - break + elif item in player_logic.ORIGINAL_EVENT_PANELS: + valid_option = self._witness_can_solve_panel(item, world, player, player_logic, locat) elif not self.has(item, player): valid_option = False break diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index 680ca680..0625c48c 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -4,9 +4,14 @@ from .utils import define_new_region, parse_lambda class StaticWitnessLogic: - ALL_ITEMS = set() + ALL_SYMBOL_ITEMS = set() + ALL_USEFULS = set() ALL_TRAPS = set() ALL_BOOSTS = set() + ALL_DOOR_ITEM_IDS_BY_HEX = dict() + DOOR_NAMES_BY_HEX = dict() + ALL_DOOR_ITEMS = set() + CONNECTIONS_TO_SEVER_BY_DOOR_HEX = dict() EVENT_PANELS_FROM_REGIONS = set() @@ -25,13 +30,13 @@ class StaticWitnessLogic: path = os.path.join(os.path.dirname(__file__), "WitnessItems.txt") with open(path, "r", encoding="utf-8") as file: - current_set = self.ALL_ITEMS + current_set = self.ALL_SYMBOL_ITEMS for line in file.readlines(): line = line.strip() if line == "Progression:": - current_set = self.ALL_ITEMS + current_set = self.ALL_SYMBOL_ITEMS continue if line == "Boosts:": current_set = self.ALL_BOOSTS @@ -39,12 +44,34 @@ class StaticWitnessLogic: if line == "Traps:": current_set = self.ALL_TRAPS continue + if line == "Usefuls:": + current_set = self.ALL_USEFULS + continue if line == "": continue line_split = line.split(" - ") current_set.add((line_split[1], int(line_split[0]))) + + path = os.path.join(os.path.dirname(__file__), "Door_Shuffle.txt") + with open(path, "r", encoding="utf-8") as file: + for line in file.readlines(): + line = line.strip() + + line_split = line.split(" - ") + + hex_set_split = line_split[1].split(",") + + sever_list = line_split[2].split(",") + sever_set = {sever_panel for sever_panel in sever_list if sever_panel != "None"} + + for door_hex in hex_set_split: + self.ALL_DOOR_ITEM_IDS_BY_HEX[door_hex] = int(line_split[0]) + self.CONNECTIONS_TO_SEVER_BY_DOOR_HEX[door_hex] = sever_set + + if len(line_split) > 3: + self.DOOR_NAMES_BY_HEX[door_hex] = line_split[3] def read_logic_file(self): """ @@ -52,7 +79,7 @@ class StaticWitnessLogic: """ path = os.path.join(os.path.dirname(__file__), "WitnessLogic.txt") with open(path, "r", encoding="utf-8") as file: - current_region = "" + current_region = dict() discard_ids = 0 normal_panel_ids = 0 @@ -105,16 +132,44 @@ class StaticWitnessLogic: laser_ids += 1 else: location_type = "General" - location_id = normal_panel_ids - normal_panel_ids += 1 + + if check_hex == "0x012D7": # Compatibility + normal_panel_ids += 1 + + if check_hex == "0x17E07": # Compatibility + location_id = 112 + + elif check_hex == "0xFFF00": + location_id = 800 + + else: + location_id = normal_panel_ids + normal_panel_ids += 1 required_items = parse_lambda(required_item_lambda) - items_actually_in_the_game = {item[0] for item in self.ALL_ITEMS} - required_items = frozenset( + items_actually_in_the_game = {item[0] for item in self.ALL_SYMBOL_ITEMS} + required_items = set( subset.intersection(items_actually_in_the_game) for subset in required_items ) + doors_in_the_game = self.ALL_DOOR_ITEM_IDS_BY_HEX.keys() + if check_hex in doors_in_the_game: + door_name = current_region["shortName"] + " " + check_name + " Power On" + if check_hex in self.DOOR_NAMES_BY_HEX.keys(): + door_name = self.DOOR_NAMES_BY_HEX[check_hex] + + required_items = set( + subset.union(frozenset({door_name})) + for subset in required_items + ) + + self.ALL_DOOR_ITEMS.add( + (door_name, self.ALL_DOOR_ITEM_IDS_BY_HEX[check_hex]) + ) + + required_items = frozenset(required_items) + requirement = { "panels": parse_lambda(required_panel_lambda), "items": required_items diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py index 3ccaf5d1..df4b4371 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/utils.py @@ -89,10 +89,18 @@ def parse_lambda(lambda_string): return lambda_set -@cache_argsless -def get_disable_unrandomized_list(): - adjustment_file = "Disable_Unrandomized.txt" +def get_adjustment_file(adjustment_file): path = os.path.join(os.path.dirname(__file__), adjustment_file) with open(path) as f: return [line.strip() for line in f.readlines()] + + +@cache_argsless +def get_disable_unrandomized_list(): + return get_adjustment_file("Disable_Unrandomized.txt") + + +@cache_argsless +def get_early_utm_list(): + return get_adjustment_file("Early_UTM.txt") \ No newline at end of file