diff --git a/README.md b/README.md index 935f34b5..98caa273 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Currently, the following games are supported: * ChecksFinder * ArchipIDLE * Hollow Knight +* The Witness For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/WebHostLib/static/assets/gameInfo/en_The Witness.md b/WebHostLib/static/assets/gameInfo/en_The Witness.md new file mode 100644 index 00000000..23543285 --- /dev/null +++ b/WebHostLib/static/assets/gameInfo/en_The Witness.md @@ -0,0 +1,28 @@ +# The Witness + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +Puzzles are randomly generated using the popular [Sigma Rando](https://github.com/sigma144/witness-randomizer). +They are made to be similar to the original game, but with different solutions. + +Ontop of that each puzzle symbol (Squares, Stars, Dots, etc.) is now an item. +Panels with puzzle symbols on them are now locked initially. + +## What is a "check" in The Witness? + +Solving the last panel in a row of panels or an important standalone panel will count as a check, and send out an item. + +## What "items" can you unlock in The Witness? + +Every puzzle symbol and many other puzzle mechanics are items. +This includes symbols such as "Dots", "Black/White Squares", "Colored Squares", "Stars", "Symmetry", "Shapers" (coll. "Tetris Pieces"), "Erasers" and many more. + +## The Jungle, Orchard, Forest and Color House aren't randomized. What gives? + +There are limitations to what can currently be randomized in The Witness. +There is an option to turn these non-randomized panels off, called "disable_non_randomized" in your yaml file. This will also slightly change the activation requirement of certain panels, detailed [here](https://github.com/sigma144/witness-randomizer/wiki/Activation-Triggers). \ No newline at end of file diff --git a/WebHostLib/static/assets/tutorial/The Witness/setup_en.md b/WebHostLib/static/assets/tutorial/The Witness/setup_en.md new file mode 100644 index 00000000..fe4ef56a --- /dev/null +++ b/WebHostLib/static/assets/tutorial/The Witness/setup_en.md @@ -0,0 +1,26 @@ +# The Witness Randomizer Setup + +## Required Software + +- [The Witness (Steam)](https://store.steampowered.com/app/210970/The_Witness/) +- [The Witness Archipalego Randomizer](https://github.com/JarnoWesthof/The-Witness-Randomizer-for-Archipelago) +- [ArchipelagoTextClient](https://github.com/ArchipelagoMW/Archipelago/releases) + +## Joining a MultiWorld Game + +This Randomizer can be very "moody" if you don't do everything in the correct order. +It is recommended to do every single one of these steps when you connect to a world. + +1. Launch The Witness +2. Start a fresh save (unless you have absolutely no other choice) +3. Do not move +4. Launch [The Witness Archipalego Randomizer](https://github.com/JarnoWesthof/The-Witness-Randomizer-for-Archipelago) +5. Enter the Archipelago Adress, Slot Name and Password +6. Press "Randomize" +7. Wait for the randomization to fully finish before moving in-game + +That's it! Have fun! + +## ArchipelagoTextClient + +Its recommended to have Archipelago's Text Client open on the side to keep track of what item you receive and send as The Witness has no in-game messages. \ No newline at end of file diff --git a/WebHostLib/static/assets/tutorial/tutorials.json b/WebHostLib/static/assets/tutorial/tutorials.json index 39ad81d4..2c88c125 100644 --- a/WebHostLib/static/assets/tutorial/tutorials.json +++ b/WebHostLib/static/assets/tutorial/tutorials.json @@ -574,5 +574,24 @@ ] } ] + }, + { + "gameTitle": "The Witness", + "tutorials": [ + { + "name": "Multiworld Setup Guide", + "description": "A guide to playing The Witness with Archipelago.", + "files": [ + { + "language": "English", + "filename": "The Witness/setup_en.md", + "link": "The Witness/setup/en", + "authors": [ + "NewSoupVi", "Jarno" + ] + } + ] + } + ] } ] diff --git a/docs/network diagram.jpg b/docs/network diagram.jpg index ad187d09..96f7d084 100644 Binary files a/docs/network diagram.jpg and b/docs/network diagram.jpg differ diff --git a/docs/network diagram.md b/docs/network diagram.md index 3f21d4ff..95f959b7 100644 --- a/docs/network diagram.md +++ b/docs/network diagram.md @@ -71,9 +71,11 @@ flowchart LR SM64[Super Mario 64 Ex] V6[VVVVVV] MT[Meritous] + TW[The Witness] APCLIENTPP <--> SOE APCLIENTPP <--> MT + APCLIENTPP <-- The Witness Randomizer --> TW APCPP <--> SM64 APCPP <--> V6 end diff --git a/docs/network diagram.svg b/docs/network diagram.svg index e7bbd2cf..927883a6 100644 --- a/docs/network diagram.svg +++ b/docs/network diagram.svg @@ -1 +1 @@ -
Factorio
Secret of Evermore
WebHost (archipelago.gg)
.NET
Java
Native
SMZ3
Super Metroid
Ocarina of Time
Final Fantasy 1
A Link to the Past
ChecksFinder
FNA/XNA
Unity
Minecraft
Secret of Evermore
WebSockets
Integrated
Various, depending on SNES device
LuaSockets
Integrated
LuaSockets
Integrated
Integrated
WebSockets
Various, depending on SNES device
Various, depending on SNES device
Various, depending on SNES device
WebSockets
WebSockets
Mod the Spire
TCP
Forge Mod Loader
WebSockets
TsRandomizer
RogueLegacyRandomizer
BepInEx
QModLoader (BepInEx)
HK Modding API
WebSockets
SQL
Subprocesses
SQL
Deposit Generated Worlds
Provide Generation Instructions
Subprocesses
Subprocesses
RCON
UDP
Integrated
Factorio Server
FactorioClient
Factorio Games
Factorio Mod Generated by AP
Factorio Modding API
SNES
Configurable (waitress, gunicorn, flask)
AutoHoster
PonyORM DB
WebHost
Flask WebContent
AutoGenerator
Mod with Archipelago.MultiClient.Net
Risk of Rain 2
Subnautica
Hollow Knight
Raft
Timespinner
Rogue Legacy
Mod with Archipelago.MultiClient.Java
Slay the Spire
Minecraft Forge Server
Any Java Minecraft Clients
Game using apclientpp Client Library
Game using Apcpp Client Library
Super Mario 64 Ex
VVVVVV
Meritous
ap-soeclient
SNES
SNES
OoTClient
Lua Connector
BizHawk with Ocarina of Time Loaded
FF1Client
Lua Connector
BizHawk with Final Fantasy Loaded
SNES
ChecksFinderClient
ChecksFinder
Archipelago Server
CommonClient.py
Super Nintendo Interface (SNI)
SNIClient
\ No newline at end of file +
Factorio
Secret of Evermore
WebHost (archipelago.gg)
.NET
Java
Native
SMZ3
Super Metroid
Ocarina of Time
Final Fantasy 1
A Link to the Past
ChecksFinder
FNA/XNA
Unity
Minecraft
Secret of Evermore
WebSockets
Integrated
Various, depending on SNES device
LuaSockets
Integrated
LuaSockets
Integrated
Integrated
WebSockets
Various, depending on SNES device
Various, depending on SNES device
The Witness Randomizer
Various, depending on SNES device
WebSockets
WebSockets
Mod the Spire
TCP
Forge Mod Loader
WebSockets
TsRandomizer
RogueLegacyRandomizer
BepInEx
QModLoader (BepInEx)
HK Modding API
WebSockets
SQL
Subprocesses
SQL
Deposit Generated Worlds
Provide Generation Instructions
Subprocesses
Subprocesses
RCON
UDP
Integrated
Factorio Server
FactorioClient
Factorio Games
Factorio Mod Generated by AP
Factorio Modding API
SNES
Configurable (waitress, gunicorn, flask)
AutoHoster
PonyORM DB
WebHost
Flask WebContent
AutoGenerator
Mod with Archipelago.MultiClient.Net
Risk of Rain 2
Subnautica
Hollow Knight
Raft
Timespinner
Rogue Legacy
Mod with Archipelago.MultiClient.Java
Slay the Spire
Minecraft Forge Server
Any Java Minecraft Clients
Game using apclientpp Client Library
Game using Apcpp Client Library
Super Mario 64 Ex
VVVVVV
Meritous
The Witness
ap-soeclient
SNES
SNES
OoTClient
Lua Connector
BizHawk with Ocarina of Time Loaded
FF1Client
Lua Connector
BizHawk with Final Fantasy Loaded
SNES
ChecksFinderClient
ChecksFinder
Archipelago Server
CommonClient.py
Super Nintendo Interface (SNI)
SNIClient
```
\ No newline at end of file diff --git a/worlds/witness/Disable_Unrandomized.txt b/worlds/witness/Disable_Unrandomized.txt new file mode 100644 index 00000000..f5c60de6 --- /dev/null +++ b/worlds/witness/Disable_Unrandomized.txt @@ -0,0 +1,104 @@ +Event Items: +Shadows Laser Activation - 0x00021,0x17D28,0x17C71 +Bunker Laser Activation - 0x00061,0x17D01,0x17C42 +Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9,0x17CA4 +Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x00B8D,0x17CF7 + +Requirement Changes: +0x17CA4 - True - True +0x28B39 - 0x2896A - Reflection +0x17CAB - True - True + +Region Changes: +Quarry (Quarry) - Outside Quarry - 0x17C09 - Quarry Mill - 0x275ED - Quarry Mill - 0x17CAC + +Disabled Locations: +0x03505 (Tutorial Gate Close) +0x0C335 (Tutorial Pillar) +0x0C373 (Tutorial Patio Floor) +0x009B8 (Symmetry Island Scenery Outlines 1) +0x003E8 (Symmetry Island Scenery Outlines 2) +0x00A15 (Symmetry Island Scenery Outlines 3) +0x00B53 (Symmetry Island Scenery Outlines 4) +0x00B8D (Symmetry Island Scenery Outlines 5) +0x00143 (Orchard Apple Tree 1) +0x0003B (Orchard Apple Tree 2) +0x00055 (Orchard Apple Tree 3) +0x032F7 (Orchard Apple Tree 4) +0x032FF (Orchard Apple Tree 5) +0x168B5 (Shadows Lower Avoid 1) +0x198BD (Shadows Lower Avoid 2) +0x198BF (Shadows Lower Avoid 3) +0x19771 (Shadows Lower Avoid 4) +0x0A8DC (Shadows Lower Avoid 5) +0x0AC74 (Shadows Lower Avoid 6) +0x0AC7A (Shadows Lower Avoid 7) +0x0A8E0 (Shadows Lower Avoid 8) +0x386FA (Shadows Environmental Avoid 1) +0x1C33F (Shadows Environmental Avoid 2) +0x196E2 (Shadows Environmental Avoid 3) +0x1972A (Shadows Environmental Avoid 4) +0x19809 (Shadows Environmental Avoid 5) +0x19806 (Shadows Environmental Avoid 6) +0x196F8 (Shadows Environmental Avoid 7) +0x1972F (Shadows Environmental Avoid 8) +0x19797 (Shadows Follow 1) +0x1979A (Shadows Follow 2) +0x197E0 (Shadows Follow 3) +0x197E8 (Shadows Follow 4) +0x197E5 (Shadows Follow 5) +0x19650 (Shadows Laser) +0x00139 (Keep Hedge Maze 1) +0x019DC (Keep Hedge Maze 2) +0x019E7 (Keep Hedge Maze 3) +0x01A0F (Keep Hedge Maze 4) +0x0360E (Laser Hedges) +0x00290 (Monastery Rhombic Avoid 1) +0x00038 (Monastery Rhombic Avoid 2) +0x00037 (Monastery Rhombic Avoid 3) +0x193A7 (Monastery Branch Avoid 1) +0x193AA (Monastery Branch Avoid 2) +0x193AB (Monastery Branch Follow 1) +0x193A6 (Monastery Branch Follow 2) +0x17CA4 (Monastery Laser) - 0x193A6 - True +0x18590 (Tree Outlines) - True - Symmetry & Environment +0x28AE3 (Vines Shadows Follow) - 0x18590 - Shadows Follow & Environment +0x28938 (Four-way Apple Tree) - 0x28AE3 - Environment +0x079DF (Triple Environmental Puzzle) - 0x28938 - Shadows Avoid & Environment & Reflection +0x28B39 (Hexagonal Reflection) - 0x079DF & 0x2896A - Reflection +0x03553 (Theater Tutorial Video) +0x03552 (Theater Desert Video) +0x0354E (Theater Jungle Video) +0x03549 (Theater Challenge Video) +0x0354F (Theater Shipwreck Video) +0x03545 (Theater Mountain Video) +0x002C4 (Waves 1) +0x00767 (Waves 2) +0x002C6 (Waves 3) +0x0070E (Waves 4) +0x0070F (Waves 5) +0x0087D (Waves 6) +0x002C7 (Waves 7) +0x15ADD (River Rhombic Avoid Vault) +0x03702 (River Vault Box) +0x17C2E (Door to Bunker) - True - Squares & Black/White Squares +0x09F7D (Bunker Drawn Squares 1) +0x09FDC (Bunker Drawn Squares 2) +0x09FF7 (Bunker Drawn Squares 3) +0x09F82 (Bunker Drawn Squares 4) +0x09FF8 (Bunker Drawn Squares 5) +0x09D9F (Bunker Drawn Squares 6) +0x09DA1 (Bunker Drawn Squares 7) +0x09DA2 (Bunker Drawn Squares 8) +0x09DAF (Bunker Drawn Squares 9) +0x0A010 (Bunker Drawn Squares through Tinted Glass 1) +0x0A01B (Bunker Drawn Squares through Tinted Glass 2) +0x0A01F (Bunker Drawn Squares through Tinted Glass 3) +0x0A099 (Door to Bunker Proper) +0x34BC5 (Bunker Drop-Down Door Open) +0x34BC6 (Bunker Drop-Down Door Close) +0x17E63 (Bunker Drop-Down Door Squares 1) +0x17E67 (Bunker Drop-Down Door Squares 2) +0x09DE0 (Bunker Laser) +0x0A079 (Bunker Elevator Control) +0x0042D (Mountaintop River Shape) \ No newline at end of file diff --git a/worlds/witness/Options.py b/worlds/witness/Options.py new file mode 100644 index 00000000..c150225d --- /dev/null +++ b/worlds/witness/Options.py @@ -0,0 +1,71 @@ +from typing import Dict +from BaseClasses import MultiWorld +from Options import Toggle, DefaultOnToggle, Option + + +# class HardMode(Toggle): +# "Play the randomizer in hardmode" +# display_name = "Hard Mode" + +# class UnlockSymbols(DefaultOnToggle): +# "All Puzzle symbols of a specific panel need to be unlocked before the panel can be used" +# display_name = "Unlock Symbols" + +class DisableNonRandomizedPuzzles(DefaultOnToggle): + """Disable puzzles that cannot be randomized. + Non randomized puzzles are Shadows, Monastery, and Greenhouse. + The lasers for those areas will be activated as you solve optional puzzles throughout the island.""" + display_name = "Disable non randomized puzzles" + + +class ShuffleDiscardedPanels(Toggle): + """Discarded Panels will have items on them. + Solving certain Discarded Panels may still be necessary!""" + display_name = "Shuffle Discarded Panels" + + +class ShuffleVaultBoxes(Toggle): + """Vault Boxes will have items on them.""" + display_name = "Shuffle Vault Boxes" + + +class ShuffleUncommonLocations(Toggle): + """Adds the following checks to the pool: + Mountaintop River Shape, Tutorial Patio Floor, Theater Videos""" + display_name = "Shuffle Uncommon Locations" + + +class ShuffleHardLocations(Toggle): + """Adds some harder locations into the game, e.g. Mountain Secret Area panels""" + 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" + + +the_witness_options: Dict[str, Option] = { + # "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 +} + + +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: + option = getattr(world, name, None) + + if option is None: + return 0 + + return int(option[player].value) diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt new file mode 100644 index 00000000..2eb8d735 --- /dev/null +++ b/worlds/witness/WitnessItems.txt @@ -0,0 +1,21 @@ +Progression: +0 - Dots +1 - Colored Dots +5 - Sound Dots +10 - Symmetry +20 - Triangles +30 - Eraser +40 - Shapers +41 - Rotated Shapers +50 - Negative Shapers +60 - Stars +61 - Stars + Same Colored Symbol +71 - Black/White Squares +72 - Colored Squares + +Boosts: +500 - Speed Boost + +Traps: +600 - Slowness Trap +610 - Power Surge Trap diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt new file mode 100644 index 00000000..5a9d9de5 --- /dev/null +++ b/worlds/witness/WitnessLogic.txt @@ -0,0 +1,712 @@ +First Hallway (First Hallway) - Entry - True: +0x00064 (Straight) - True - True +0x00182 (Bend) - 0x00064 - True + +Tutorial (Tutorial) - First Hallway - 0x00182: +0x00293 (Front Center) - True - True +0x00295 (Center Left) - 0x00293 - True +0x002C2 (Front Left) - 0x00295 - True +0x0A3B5 (Back Left) - True - True +0x0A3B2 (Back Right) - True - True +0x03629 (Gate Open) - 0x002C2 & 0x0A3B5 & 0x0A3B2 - True +0x03505 (Gate Close) - 0x2FAF6 - True +0x0C335 (Pillar) - True - Triangles - True +0x0C373 (Patio Floor) - 0x0C335 - True + +Outside Tutorial (Outside Tutorial) - Tutorial - 0x03629: +0x033D4 (Vault) - True - Dots & Squares & Black/White Squares +0x03481 (Vault Box) - 0x033D4 - True +0x0A171 (Optional Door 1) - 0x0A3B5 - Dots +0x17CFB (Discard) - 0x0A171 - Triangles +0x04CA4 (Optional Door 2) - 0x0A171 - Dots & Squares & Black/White Squares +0x0005D (Dots Introduction 1) - True - Dots +0x0005E (Dots Introduction 2) - 0x0005D - Dots +0x0005F (Dots Introduction 3) - 0x0005E - Dots +0x00060 (Dots Introduction 4) - 0x0005F - Dots +0x00061 (Dots Introduction 5) - 0x00060 - Dots +0x018AF (Squares Introduction 1) - True - Squares & Black/White Squares +0x0001B (Squares Introduction 2) - 0x018AF - Squares & Black/White Squares +0x012C9 (Squares Introduction 3) - 0x0001B - Squares & Black/White Squares +0x0001C (Squares Introduction 4) - 0x012C9 - Squares & Black/White Squares +0x0001D (Squares Introduction 5) - 0x0001C - Squares & Black/White Squares +0x0001E (Squares Introduction 6) - 0x0001D - Squares & Black/White Squares +0x0001F (Squares Introduction 7) - 0x0001E - Squares & Black/White Squares +0x00020 (Squares Introduction 8) - 0x0001F - Squares & Black/White Squares +0x00021 (Squares Introduction 9) - 0x00020 - Squares & Black/White Squares + +Main Island () - Outside Tutorial - True: + +Outside Glass Factory (Glass Factory) - Main Island - True: +0x01A54 (Entry Door) - True - Symmetry +0x3C12B (Discard) - True - Triangles + +Inside Glass Factory (Glass Factory) - Outside Glass Factory - 0x01A54: +0x00086 (Vertical Symmetry 1) - True - Symmetry +0x00087 (Vertical Symmetry 2) - 0x00086 - Symmetry +0x00059 (Vertical Symmetry 3) - 0x00087 - Symmetry +0x00062 (Vertical Symmetry 4) - 0x00059 - Symmetry +0x0005C (Vertical Symmetry 5) - 0x00062 - Symmetry +0x0008D (Rotational Symmetry 1) - 0x0005C - Symmetry +0x00081 (Rotational Symmetry 2) - 0x0008D - Symmetry +0x00083 (Rotational Symmetry 3) - 0x00081 - Symmetry +0x00084 (Melting 1) - 0x00083 - Symmetry +0x00082 (Melting 2) - 0x00084 - Symmetry +0x0343A (Melting 3) - 0x00082 - Symmetry +0x17CC8 (Boat Spawn) - True - Boat + +Outside Symmetry Island (Symmetry Island) - Main Island - True: +0x000B0 (Door to Symmetry Island Lower) - True - Dots + +Symmetry Island Lower (Symmetry Island) - Outside Symmetry Island - 0x000B0: +0x00022 (Black Dots 1) - True - Symmetry & Dots +0x00023 (Black Dots 2) - 0x00022 - Symmetry & Dots +0x00024 (Black Dots 3) - 0x00023 - Symmetry & Dots +0x00025 (Black Dots 4) - 0x00024 - Symmetry & Dots +0x00026 (Black Dots 5) - 0x00025 - Symmetry & Dots +0x0007C (Colored Dots 1) - 0x00026 - Symmetry & Colored Dots +0x0007E (Colored Dots 2) - 0x0007C - Symmetry & Colored Dots +0x00075 (Colored Dots 3) - 0x0007E - Symmetry & Colored Dots +0x00073 (Colored Dots 4) - 0x00075 - Symmetry & Colored Dots +0x00077 (Colored Dots 5) - 0x00073 - Symmetry & Colored Dots +0x00079 (Colored Dots 6) - 0x00077 - Symmetry & Colored Dots +0x00065 (Fading Lines 1) - 0x00079 - Symmetry & Colored Dots +0x0006D (Fading Lines 2) - 0x00065 - Symmetry & Colored Dots +0x00072 (Fading Lines 3) - 0x0006D - Symmetry & Colored Dots +0x0006F (Fading Lines 4) - 0x00072 - Symmetry & Colored Dots +0x00070 (Fading Lines 5) - 0x0006F - Symmetry & Colored Dots +0x00071 (Fading Lines 6) - 0x00070 - Symmetry & Colored Dots +0x00076 (Fading Lines 7) - 0x00071 - Symmetry & Colored Dots +0x009B8 (Scenery Outlines 1) - True - Symmetry & Environment +0x003E8 (Scenery Outlines 2) - 0x009B8 - Symmetry & Environment +0x00A15 (Scenery Outlines 3) - 0x003E8 - Symmetry & Environment +0x00B53 (Scenery Outlines 4) - 0x00A15 - Symmetry & Environment +0x00B8D (Scenery Outlines 5) - 0x00B53 - Symmetry & Environment +0x1C349 (Door to Symmetry Island Upper) - 0x00076 - Symmetry & Dots + +Symmetry Island Upper (Symmetry Island) - Symmetry Island Lower - 0x1C349: +0x00A52 (Yellow 1) - True - Symmetry & Colored Dots +0x00A57 (Yellow 2) - 0x00A52 - Symmetry & Colored Dots +0x00A5B (Yellow 3) - 0x00A57 - Symmetry & Colored Dots +0x00A61 (Blue 1) - 0x00A52 - Symmetry & Colored Dots +0x00A64 (Blue 2) - 0x00A61 & 0x00A52 - Symmetry & Colored Dots +0x00A68 (Blue 3) - 0x00A64 & 0x00A57 - Symmetry & Colored Dots +0x0360D (Laser) - 0x00A68 - True + +Orchard (Orchard) - Main Island - True: +0x00143 (Apple Tree 1) - True - Environment +0x0003B (Apple Tree 2) - 0x00143 - Environment +0x00055 (Apple Tree 3) - 0x0003B - Environment +0x032F7 (Apple Tree 4) - 0x00055 - Environment +0x032FF (Apple Tree 5) - 0x032F7 - Environment + +Desert Outside (Desert) - Main Island - True: +0x0CC7B (Vault) - True - Dots & Shapers & Rotated Shapers & Negative Shapers +0x0339E (Vault Box) - 0x0CC7B - True +0x17CE7 (Discard) - True - Triangles +0x00698 (Sun Reflection 1) - True - Reflection +0x0048F (Sun Reflection 2) - 0x00698 - Reflection +0x09F92 (Sun Reflection 3) - 0x0048F & 0x09FA0 - Reflection +0x09FA0 (Reflection 3 Control) - 0x0048F - True +0x0A036 (Sun Reflection 4) - 0x09F92 - Reflection +0x09DA6 (Sun Reflection 5) - 0x09F92 - Reflection +0x0A049 (Sun Reflection 6) - 0x09F92 - Reflection +0x0A053 (Sun Reflection 7) - 0x0A036 & 0x09DA6 & 0x0A049 - Reflection +0x09F94 (Sun Reflection 8) - 0x0A053 & 0x09F86 - Reflection +0x09F86 (Reflection 8 Control) - 0x0A053 - True +0x0C339 (Door to Desert Flood Light Room) - 0x09F94 - True + +Desert Floodlight Room (Desert) - Desert Outside - 0x0C339: +0x09FAA (Light Control) - True - True +0x00422 (Artificial Light Reflection 1) - 0x09FAA - Reflection +0x006E3 (Artificial Light Reflection 2) - 0x09FAA - Reflection +0x0A02D (Artificial Light Reflection 3) - 0x09FAA & 0x00422 & 0x006E3 - Reflection + +Desert Pond Room (Desert) - Desert Floodlight Room - 0x0A02D: +0x00C72 (Pond Reflection 1) - True - Reflection +0x0129D (Pond Reflection 2) - 0x00C72 - Reflection +0x008BB (Pond Reflection 3) - 0x0129D - Reflection +0x0078D (Pond Reflection 4) - 0x008BB - Reflection +0x18313 (Pond Reflection 5) - 0x0078D - Reflection +0x0A249 (Door to Desert Water Levels Room) - 0x18313 - Reflection + +Desert Water Levels Room (Desert) - Desert Pond Room - 0x0A249: +0x1C2DF (Reduce Water Level Far Left) - True - True +0x1831E (Reduce Water Level Far Right) - True - True +0x1C260 (Reduce Water Level Near Left) - True - True +0x1831C (Reduce Water Level Near Right) - True - True +0x1C2F3 (Raise Water Level Far Left) - True - True +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 + +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 +0x0A15F (Final Bent Reflection 3) - 0x09FFF - Reflection +0x03608 (Laser) - 0x012D7 & 0x0A15F - True + +Outside Quarry (Quarry) - Main Island - True: +0x09E57 (Door to Quarry 1) - True - Squares & Black/White Squares +0x17C09 (Door to Quarry 2) - 0x09E57 - Shapers +0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser + +Quarry (Quarry) - Outside Quarry - 0x17C09 - Quarry Mill - 0x275ED - Quarry Mill - 0x17CAC - Shadows Ledge - 0x198BF: +0x01E5A (Door to Mill Left) - True - Squares & Black/White Squares +0x01E59 (Door to Mill Right) - True - Dots +0x17CF0 (Discard) - True - Triangles +0x03612 (Laser) - 0x0A3D0 & 0x0367C - Eraser & Shapers + +Quarry Mill (Quarry Mill) - Quarry - 0x01E59 & 0x01E5A: +0x275ED (Ground Floor Shortcut Door) - True - True +0x03678 (Lower Ramp Control) - True - Dots & Eraser +0x00E0C (Eraser and Dots 1) - 0x03678 - Dots & Eraser +0x01489 (Eraser and Dots 2) - 0x00E0C - Dots & Eraser +0x0148A (Eraser and Dots 3) - 0x01489 - Dots & Eraser +0x014D9 (Eraser and Dots 4) - 0x0148A - Dots & Eraser +0x014E7 (Eraser and Dots 5) - 0x014D9 - Dots & Eraser +0x014E8 (Eraser and Dots 6) - 0x014E7 - Dots & Eraser +0x03679 (Lower Lift Control) - 0x014E8 - Dots & Eraser +0x03675 (Upper Ramp Control) - 0x03679 - Dots & Eraser +0x03676 (Upper Lift Control) - 0x03679 - Dots & Eraser +0x00557 (Eraser and Squares 1) - 0x03679 - Squares & Colored Squares & Eraser +0x005F1 (Eraser and Squares 2) - 0x00557 - Squares & Colored Squares & Eraser +0x00620 (Eraser and Squares 3) - 0x005F1 - Squares & Colored Squares & Eraser +0x009F5 (Eraser and Squares 4) - 0x00620 - Squares & Colored Squares & Eraser +0x0146C (Eraser and Squares 5) - 0x009F5 - Squares & Colored Squares & Eraser +0x3C12D (Eraser and Squares 6) - 0x0146C - Squares & Colored Squares & Eraser +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 +0x17CAC (Door to Outside Quarry Stairs) - True - True + +Quarry Boathouse (Quarry Boathouse) - Quarry - True: +0x034D4 (Intro Stars) - True - Stars +0x021D5 (Intro Shapers) - True - Shapers & Rotated Shapers +0x03852 (Ramp Height Control) - 0x034D4 & 0x021D5 - Rotated Shapers +0x021B3 (Eraser and Shapers 1) - 0x03852 - Shapers & Eraser +0x021B4 (Eraser and Shapers 2) - 0x021B3 - Shapers & Eraser +0x021B0 (Eraser and Shapers 3) - 0x021B4 - Shapers & Eraser +0x021AF (Eraser and Shapers 4) - 0x021B0 - Shapers & Eraser +0x021AE (Eraser and Shapers 5) - 0x021AF - Shapers & Eraser & Broken Shapers +0x03858 (Ramp Horizontal Control) - 0x021AE - Shapers & Eraser +0x38663 (Shortcut Door) - 0x03858 - True +0x021B5 (Stars and Colored Eraser 1) - 0x03858 - Stars & Stars + Same Colored Symbol & Eraser +0x021B6 (Stars and Colored Eraser 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser +0x021B7 (Stars and Colored Eraser 3) - 0x021B6 - Stars & Stars + Same Colored Symbol & Eraser +0x021BB (Stars and Colored Eraser 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser +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 +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 + +Shadows (Shadows) - Main Island - True - Keep Glass Plates - 0x09E49: +0x334DB (Door Timer Outside) - True - True +0x0AC74 (Lower Avoid 6) - 0x0A8DC - Shadows Avoid +0x0AC7A (Lower Avoid 7) - 0x0AC74 - Shadows Avoid +0x0A8E0 (Lower Avoid 8) - 0x0AC7A - Shadows Avoid +0x386FA (Environmental Avoid 1) - 0x0A8E0 - Shadows Avoid & Environment +0x1C33F (Environmental Avoid 2) - 0x386FA - Shadows Avoid & Environment +0x196E2 (Environmental Avoid 3) - 0x1C33F - Shadows Avoid & Environment +0x1972A (Environmental Avoid 4) - 0x196E2 - Shadows Avoid & Environment +0x19809 (Environmental Avoid 5) - 0x1972A - Shadows Avoid & Environment +0x19806 (Environmental Avoid 6) - 0x19809 - Shadows Avoid & Environment +0x196F8 (Environmental Avoid 7) - 0x19806 - Shadows Avoid & Environment +0x1972F (Environmental Avoid 8) - 0x196F8 - Shadows Avoid & Environment +0x19797 (Follow 1) - 0x0A8E0 - Shadows Follow +0x1979A (Follow 2) - 0x19797 - Shadows Follow +0x197E0 (Follow 3) - 0x1979A - Shadows Follow +0x197E8 (Follow 4) - 0x197E0 - Shadows Follow +0x197E5 (Follow 5) - 0x197E8 - Shadows Follow +0x19650 (Laser) - 0x197E5 & 0x196F8 - Shadows Avoid & Shadows Follow + +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 +0x198BF (Lower Avoid 3) - 0x198BD & 0x334DC - Shadows Avoid +0x19771 (Lower Avoid 4) - 0x198BF - Shadows Avoid +0x0A8DC (Lower Avoid 5) - 0x19771 - Shadows Avoid + +Keep (Keep) - Main Island - True: + +Keep Hedges (Keep) - Keep - True: +0x00139 (Hedge Maze 1) - True - Environment +0x019DC (Hedge Maze 2) - 0x00139 - Environment +0x019E7 (Hedge Maze 3) - 0x019DC - Environment & Sound +0x01A0F (Hedge Maze 4) - 0x019E7 - Environment + +Keep Glass Plates (Keep) - Keep - True - Keep Tower - 0x0361B: +0x0A3A8 (Reset Pressure Plates 1) - True - True +0x033EA (Pressure Plates 1) - 0x0A3A8 - Pressure Plates & Dots +0x0A3B9 (Reset Pressure Plates 2) - 0x033EA - True +0x01BE9 (Pressure Plates 2) - 0x033EA & 0x0A3B9 - Pressure Plates & Stars & Stars + Same Colored Symbol & Squares & Black/White Squares +0x0A3BB (Reset Pressure Plates 3) - 0x0A3A8 - True +0x01CD3 (Pressure Plates 3) - 0x0A3A8 & 0x0A3BB - Pressure Plates & Shapers & Squares & Black/White Squares & Colored Squares +0x0A3AD (Reset Pressure Plates 4) - 0x01CD3 - True +0x01D3F (Pressure Plates 4) - 0x01CD3 & 0x0A3AD - Pressure Plates & Shapers & Dots & Symmetry +0x17D27 (Discard) - 0x01CD3 - Triangles +0x09E49 (Shortcut to Shadows) - 0x01CD3 - True + +Shipwreck (Shipwreck) - Keep Glass Plates - 0x033EA: +0x00AFB (Vault) - True - Symmetry & Sound & Sound Dots & Colored Dots +0x03535 (Vault Box) - 0x00AFB - True +0x17D28 (Discard) - True - Triangles + +Keep Tower (Keep) - Keep Hedges - 0x01A0F - Keep Glass Plates - 0x01D3F: +0x0361B (Shortcut to Keep Glass Plates) - True - True +0x0360E (Laser Hedges) - 0x01A0F - Environment & Sound +0x03317 (Laser Pressure Plates) - 0x01D3F - Shapers & Squares & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots + +Outside Monastery (Monastery) - Main Island - True: +0x03713 (Shortcut) - True - True +0x00B10 (Door Open Left) - True - True +0x00C92 (Door Open Right) - True - True +0x00290 (Rhombic Avoid 1) - 0x09D9B - Environment +0x00038 (Rhombic Avoid 2) - 0x09D9B & 0x00290 - Environment +0x00037 (Rhombic Avoid 3) - 0x09D9B & 0x00038 - Environment +0x17CA4 (Laser) - 0x193A6 - True + +Inside Monastery (Monastery) - Outside Monastery - 0x00B10 & 0x00C92: +0x09D9B (Overhead Door Control) - True - Dots +0x193A7 (Branch Avoid 1) - 0x00037 - Environment +0x193AA (Branch Avoid 2) - 0x193A7 - Environment +0x193AB (Branch Follow 1) - 0x193AA - Environment +0x193A6 (Branch Follow 2) - 0x193AB - Environment + +Monastery Garden (Monastery) - Outside Monastery - 0x00037 - Outside Jungle River - 0x17CAA: + +Town (Town) - Main Island - True - Theater - 0x0A168 | 0x33AB2: +0x0A054 (Boat Summon) - True - Boat +0x0A0C8 (Cargo Box) - True - Squares & Black/White Squares & Shapers +0x17D01 (Cargo Box Discard) - 0x0A0C8 - Triangles +0x09F98 (Desert Laser Redirect) - True - True +0x18590 (Tree Outlines) - True - Symmetry & Environment +0x28AE3 (Vines Shadows Follow) - 0x18590 - Shadows Follow & Environment +0x28938 (Four-way Apple Tree) - 0x28AE3 - Environment +0x079DF (Triple Environmental Puzzle) - 0x28938 - Shadows Avoid & Environment & Reflection +0x28B39 (Hexagonal Reflection) - 0x079DF & 0x2896A - Reflection +0x28998 (Tinted Door to RGB House) - True - Stars & Rotated Shapers +0x28A0D (Door to Church) - 0x28998 - Stars & RGB & Environment +0x28A69 (Square Avoid) - 0x28A0D - Environment +0x28A79 (Maze Stair Control) - True - Environment +0x2896A (Maze Rooftop Bridge Control) - 0x28A79 - Shapers +0x17C71 (Rooftop Discard) - 0x2896A - Triangles +0x28AC7 (Symmetry Squares 1) - 0x2896A - Symmetry & Squares & Black/White Squares +0x28AC8 (Symmetry Squares 2) - 0x28AC7 - Symmetry & Squares & Black/White Squares +0x28ACA (Symmetry Squares 3 + Dots) - 0x28AC8 - Symmetry & Squares & Black/White Squares & Dots +0x28ACB (Symmetry Squares 4 + Dots) - 0x28ACA - Symmetry & Squares & Black/White Squares & Dots +0x28ACC (Symmetry Squares 5 + Dots) - 0x28ACB - Symmetry & Squares & Black/White Squares & Dots +0x2899C (Full Dot Grid Shapers 1) - True - Rotated Shapers & Dots +0x28A33 (Full Dot Grid Shapers 2) - 0x2899C - Shapers & Dots +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 +0x17F5F (Windmill Door) - True - Dots + +RGB House (Town) - Town - 0x28998: +0x034E4 (Sound Room Left) - True - Sound & Sound Waves +0x034E3 (Sound Room Right) - True - Sound & Sound Dots +0x334D8 (RGB Control) - 0x034E4 & 0x034E3 - Rotated Shapers & RGB & Squares & Colored Squares +0x03C0C (RGB Squares) - 0x334D8 - RGB & Squares & Colored Squares & Black/White Squares +0x03C08 (RGB Stars) - 0x334D8 - RGB & Stars + +Town Tower Top (Town) - Town - 0x28A69 & 0x28B39 & 0x28ACC & 0x28AD9: +0x032F5 (Laser) - True - True + +Windmill Interior (Windmill) - Town - 0x17F5F: +0x17D02 (Turn Control) - True - Dots +0x17F89 (Door to Front of Theater) - True - Squares & Black/White Squares + +Theater (Theater) - Windmill Interior - 0x17F89: +0x00815 (Video Input) - True - True +0x03553 (Tutorial Video) - 0x00815 & 0x03481 - True +0x03552 (Desert Video) - 0x00815 & 0x0339E - True +0x0354E (Jungle Video) - 0x00815 & 0x03702 - True +0x03549 (Challenge Video) - 0x00815 & 0x2FAF6 - True +0x0354F (Shipwreck Video) - 0x00815 & 0x03535 - True +0x03545 (Mountain Video) - 0x00815 & 0x03542 - True +0x0A168 (Door to Cargo Box Left) - True - Squares & Black/White Squares & Eraser +0x33AB2 (Door to Cargo Box Right) - True - Squares & Black/White Squares & Shapers +0x17CF7 (Discard) - True - Triangles + +Jungle (Jungle) - Main Island - True: +0x17CDF (Shore Boat Spawn) - True - Boat +0x17F9B (Discard) - True - Triangles +0x002C4 (Waves 1) - True - Sound & Sound Waves +0x00767 (Waves 2) - 0x002C4 - Sound & Sound Waves +0x002C6 (Waves 3) - 0x00767 - Sound & Sound Waves +0x0070E (Waves 4) - 0x002C6 - Sound & Sound Waves +0x0070F (Waves 5) - 0x0070E - Sound & Sound Waves +0x0087D (Waves 6) - 0x0070F - Sound & Sound Waves +0x002C7 (Waves 7) - 0x0087D - Sound & Sound Waves +0x17CAB (Popup Wall Control) - 0x002C7 - True +0x0026D (Popup Wall 1) - 0x17CAB - Sound & Sound Dots +0x0026E (Popup Wall 2) - 0x0026D - Sound & Sound Dots +0x0026F (Popup Wall 3) - 0x0026E - Sound & Sound Dots +0x00C3F (Popup Wall 4) - 0x0026F - Sound & Sound Dots +0x00C41 (Popup Wall 5) - 0x00C3F - Sound & Sound Dots +0x014B2 (Popup Wall 6) - 0x00C41 - Sound & Sound Dots +0x03616 (Laser) - 0x014B2 - True +0x337FA (Shortcut to River) - True - True + +Outside Jungle River (River) - Main Island - True - Jungle - 0x337FA: +0x17CAA (Rhombic Avoid to Monastery Garden) - True - Environment +0x15ADD (Rhombic Avoid Vault) - True - Environment +0x03702 (Vault Box) - 0x15ADD - True + +Outside Bunker (Bunker) - Main Island - True - Inside Bunker - 0x0A079: +0x17C2E (Door to Bunker) - True - Squares & Black/White Squares +0x09DE0 (Laser) - 0x0A079 - True + +Inside Bunker (Bunker) - Outside Bunker - 0x17C2E: +0x09F7D (Drawn Squares 1) - True - Squares & Colored Squares +0x09FDC (Drawn Squares 2) - 0x09F7D - Squares & Colored Squares & Black/White Squares +0x09FF7 (Drawn Squares 3) - 0x09FDC - Squares & Colored Squares & Black/White Squares +0x09F82 (Drawn Squares 4) - 0x09FF7 - Squares & Colored Squares & Black/White Squares +0x09FF8 (Drawn Squares 5) - 0x09F82 - Squares & Colored Squares & Black/White Squares +0x09D9F (Drawn Squares 6) - 0x09FF8 - Squares & Colored Squares & Black/White Squares +0x09DA1 (Drawn Squares 7) - 0x09D9F - Squares & Colored Squares +0x09DA2 (Drawn Squares 8) - 0x09DA1 - Squares & Colored Squares +0x09DAF (Drawn Squares 9) - 0x09DA2 - Squares & Colored Squares +0x0A099 (Door to Bunker Proper) - 0x09DAF - True +0x0A010 (Drawn Squares through Tinted Glass 1) - 0x0A099 - Squares & Colored Squares & RGB & Environment +0x0A01B (Drawn Squares through Tinted Glass 2) - 0x0A010 - Squares & Colored Squares & Black/White Squares & RGB & Environment +0x0A01F (Drawn Squares through Tinted Glass 3) - 0x0A01B - Squares & Colored Squares & Black/White Squares & RGB & Environment +0x34BC5 (Drop-Down Door Open) - 0x0A01F - True +0x34BC6 (Drop-Down Door Close) - 0x34BC5 - True +0x17E63 (Drop-Down Door Squares 1) - 0x0A01F & 0x34BC5 - Squares & Colored Squares & RGB & Environment +0x17E67 (Drop-Down Door Squares 2) - 0x17E63 & 0x34BC6 - Squares & Colored Squares & Black/White Squares & RGB +0x0A079 (Elevator Control) - 0x17E67 - Squares & Colored Squares & Black/White Squares & RGB + +Outside Swamp (Swamp) - Main Island - True: +0x0056E (Entry Door) - True - Shapers + +Swamp Entry Area (Swamp) - Outside Swamp - 0x0056E: +0x00469 (Seperatable Shapers 1) - True - Shapers +0x00472 (Seperatable Shapers 2) - 0x00469 - Shapers +0x00262 (Seperatable Shapers 3) - 0x00472 - Shapers +0x00474 (Seperatable Shapers 4) - 0x00262 - Shapers +0x00553 (Seperatable Shapers 5) - 0x00474 - Shapers +0x0056F (Seperatable Shapers 6) - 0x00553 - Shapers +0x00390 (Combinable Shapers 1) - 0x0056F - Shapers +0x010CA (Combinable Shapers 2) - 0x00390 - Shapers +0x00983 (Combinable Shapers 3) - 0x010CA - Shapers +0x00984 (Combinable Shapers 4) - 0x00983 - Shapers +0x00986 (Combinable Shapers 5) - 0x00984 - Shapers +0x00985 (Combinable Shapers 6) - 0x00986 - Shapers +0x00987 (Combinable Shapers 7) - 0x00985 - Shapers +0x181A9 (Combinable Shapers 8) - 0x00987 - Shapers +0x00609 (Slide Bridge) - 0x181A9 - Shapers + +Swamp Near Platform (Swamp) - Swamp Entry Area - 0x00609 | 0x18488: +0x00999 (Broken Shapers 1) - 0x00990 - Broken Shapers +0x0099D (Broken Shapers 2) - 0x00999 - Broken Shapers +0x009A0 (Broken Shapers 3) - 0x0099D - Broken Shapers +0x009A1 (Broken Shapers 4) - 0x009A0 - Broken Shapers +0x00002 (Cyan Underwater Negative Shapers 1) - 0x00006 - Shapers & Negative Shapers +0x00004 (Cyan Underwater Negative Shapers 2) - 0x00002 - Shapers & Negative Shapers +0x00005 (Cyan Underwater Negative Shapers 3) - 0x00004 - Shapers & Negative Shapers +0x013E6 (Cyan Underwater Negative Shapers 4) - 0x00005 - Shapers & Negative Shapers +0x00596 (Cyan Underwater Negative Shapers 5) - 0x013E6 - Shapers & Negative Shapers +0x18488 (Cyan Underwater Sliding Bridge Control) - 0x00006 - Shapers + +Swamp Platform (Swamp) - Swamp Near Platform - True: +0x00982 (Platform Shapers 1) - True - Shapers +0x0097F (Platform Shapers 2) - 0x00982 - Shapers +0x0098F (Platform Shapers 3) - 0x0097F - Shapers +0x00990 (Platform Shapers 4) - 0x0098F - Shapers +0x17C0D (Platform Shortcut Door Left) - True - Shapers +0x17C0E (Platform Shortcut Door Right) - True - Shapers + +Swamp Rotating Bridge Near Side (Swamp) - Swamp Near Platform - 0x009A1: +0x00007 (Rotated Shapers 1) - 0x009A1 - Rotated Shapers +0x00008 (Rotated Shapers 2) - 0x00007 - Rotated Shapers & Shapers +0x00009 (Rotated Shapers 3) - 0x00008 - Rotated Shapers +0x0000A (Rotated Shapers 4) - 0x00009 - Rotated Shapers +0x00001 (Red Underwater Negative Shapers 1) - 0x00596 - Shapers & Negative Shapers +0x014D2 (Red Underwater Negative Shapers 2) - 0x00596 - Shapers & Negative Shapers +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 +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 +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 + +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 +0x17C05 (Near Laser Shortcut Door Left) - True - Rotated Shapers +0x17C02 (Near Laser Shortcut Door Right) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers + +Treehouse Entry Area (Treehouse): +0x17C95 (Boat Spawn) - True - Boat +0x0288C (First Door) - True - Stars +0x02886 (Second Door) - 0x0288C - Stars +0x17D72 (Yellow Bridge 1) - 0x02886 - Stars +0x17D8F (Yellow Bridge 2) - 0x17D72 - Stars +0x17D74 (Yellow Bridge 3) - 0x17D8F - Stars +0x17DAC (Yellow Bridge 4) - 0x17D74 - Stars +0x17D9E (Yellow Bridge 5) - 0x17DAC - Stars +0x17DB9 (Yellow Bridge 6) - 0x17D9E - Stars +0x17D9C (Yellow Bridge 7) - 0x17DB9 - Stars +0x17DC2 (Yellow Bridge 8) - 0x17D9C - Stars +0x17DC4 (Yellow Bridge 9) - 0x17DC2 - Stars +0x0A182 (Beyond Yellow Bridge Door) - 0x17DC4 - Stars + +Treehouse Beyond Yellow Bridge (Treehouse) - Treehouse Entry Area - 0x0A182: +0x2700B (Laser House Door Timer Outside Control) - True - True +0x17DC8 (First Purple Bridge 1) - True - Stars & Dots +0x17DC7 (First Purple Bridge 2) - 0x17DC8 - Stars & Dots +0x17CE4 (First Purple Bridge 3) - 0x17DC7 - Stars & Dots +0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots +0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots +0x17D9B (Second Purple Bridge 1) - 0x17D6C - Stars & Squares & Black/White Squares +0x17D99 (Second Purple Bridge 2) - 0x17D9B - Stars & Squares & Black/White Squares +0x17DAA (Second Purple Bridge 3) - 0x17D99 - Stars & Squares & Black/White Squares +0x17D97 (Second Purple Bridge 4) - 0x17DAA - Stars & Squares & Black/White Squares & Colored Squares +0x17BDF (Second Purple Bridge 5) - 0x17D97 - Stars & Squares & Colored Squares +0x17D91 (Second Purple Bridge 6) - 0x17BDF - Stars & Squares & Colored Squares +0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Squares & Colored Squares +0x17E3C (Green Bridge 1) - True - Stars & Shapers +0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers +0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Rotated Shapers +0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Rotated Shapers & Environment +0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Colored Shapers & Stars + Same Colored Symbol +0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Colored Shapers & Negative Shapers & Colored Negative Shapers & Stars + Same Colored Symbol +0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Rotated Shapers +0x17FA9 (Green Bridge Discard) - 0x17E61 - Triangles +0x17DB3 (Left Orange Bridge 1) - 0x17DC6 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Squares & Black/White Squares & Colored Squares & Stars + Same Colored Symbol +0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Squares & Black/White Squares & Colored Squares & Stars + Same Colored Symbol +0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Squares & Black/White Squares & Colored Squares & Stars + Same Colored Symbol +0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Squares & Colored Squares & Stars + Same Colored Symbol +0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Squares & Colored Squares & Stars + Same Colored Symbol & Environment +0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Squares & Colored Squares & Stars + Same Colored Symbol +0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Squares & Colored Squares & Stars + Same Colored Symbol +0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DAE (Left Orange Bridge 13) - 0x17DEC - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x17FA0 (Burned House Discard) - 0x17DDB - Triangles +0x17D88 (Right Orange Bridge 1) - True - Stars +0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars +0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars +0x17CE3 (Right Orange Bridge 4 & Directional) - 0x17D8C - Stars & Environment +0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars +0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars +0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars +0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars +0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars +0x17DB7 (Right Orange Bridge 10 & Directional) - 0x17D8E - Stars +0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars +0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars + +Treehouse Laser Room (Treehouse) - Treehouse Beyond Yellow Bridge - 0x2700B & 0x17DA2 & 0x17DDB: +0x03613 (Laser) - True - True +0x17CBC (Laser House Door Timer Inside Control) - True - True + +Treehouse Bridge Platform (Treehouse) - Treehouse Beyond Yellow Bridge - 0x17DA2 - Main Island - 0x037FF: +0x037FF (Bridge Control) - True - Stars + +Mountaintop (Mountaintop) - Main Island - True: +0x0042D (River Shape) - True - True +0x09F7F (Box Open) - 7 Lasers - 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 +0x03542 (Vault Box) - 0x002A6 - True + +Inside Mountain Top Layer (Inside Mountain) - Mountaintop - 0x17C34: +0x09E39 (Light Bridge Controller) - True - Squares & Black/White Squares & Colored Squares & Eraser & Colored Eraser + +Inside Mountain Top Layer Bridge (Inside Mountain) - Inside Mountain Top Layer - 0x09E39: +0x09E7A (Obscured Vision 1) - True - Obscured & Squares & Black/White Squares & Dots +0x09E71 (Obscured Vision 2) - 0x09E7A - Obscured & Squares & Black/White Squares & Dots +0x09E72 (Obscured Vision 3) - 0x09E71 - Obscured & Squares & Black/White Squares & Rotated Shapers & Dots +0x09E69 (Obscured Vision 4) - 0x09E72 - Obscured & Squares & Black/White Squares & Dots +0x09E7B (Obscured Vision 5) - 0x09E69 - Obscured & Squares & Black/White Squares & Dots +0x09E73 (Moving Background 1) - True - Moving & Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x09E75 (Moving Background 2) - 0x09E73 - Moving & Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x09E78 (Moving Background 3) - 0x09E75 - Moving & Shapers +0x09E79 (Moving Background 4) - 0x09E78 - Moving & Shapers & Rotated Shapers +0x09E6C (Moving Background 5) - 0x09E79 - Moving & Stars & Squares & Black/White Squares & Stars + Same Colored Symbol +0x09E6F (Moving Background 6) - 0x09E6C - Moving & Stars & Rotated Shapers & Shapers +0x09E6B (Moving Background 7) - 0x09E6F - Moving & Stars & Dots +0x33AF5 (Physically Obstructed 1) - True - Squares & Black/White Squares & Environment & Symmetry +0x33AF7 (Physically Obstructed 2) - 0x33AF5 - Squares & Black/White Squares & Stars & Environment +0x09F6E (Physically Obstructed 3) - 0x33AF7 - Symmetry & Dots & Environment +0x09EAD (Angled Inside Trash 1) - True - Squares & Black/White Squares & Shapers & Angled +0x09EAF (Angled Inside Trash 2) - 0x09EAD - Squares & Black/White Squares & Shapers & Angled + +Inside Mountain Second Layer (Inside Mountain) - Inside Mountain Top Layer Bridge - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B: +0x09FD3 (Color Cycle 1) - True - Color Cycle & RGB & Stars & Squares & Colored Squares & Stars + Same Colored Symbol +0x09FD4 (Color Cycle 2) - 0x09FD3 - Color Cycle & RGB & Stars & Squares & Colored Squares & Stars + Same Colored Symbol +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 + +Inside Mountain Second Layer Beyond Bridge (Inside Mountain) - Inside Mountain Second Layer - 0x09E86: +0x09FCC (Same Solution 1) - True - Dots & Same Solution +0x09FCE (Same Solution 2) - 0x09FCC - Squares & Black/White Squares & Same Solution +0x09FCF (Same Solution 3) - 0x09FCE - Stars & Same Solution +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 + +Inside Mountain Second Layer Elevator (Inside Mountain) - Inside Mountain Second Layer - 0x09ED8 & 0x09E86: +0x09EEB (Elevator Control Panel) - True - Dots +0x17F93 (Elevator Discard) - True - Triangles + +Inside Mountain Third Layer (Inside Mountain) - Inside Mountain Second Layer Elevator - 0x09EEB: +0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser +0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser +0x09F01 (Giant Puzzle Top Right) - True - Rotated Shapers +0x09EFF (Giant Puzzle Top Left) - True - Shapers & Eraser +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 +0x01983 (Door to Final Room Left) - True - Shapers & Stars +0x01987 (Door to Final Room Right) - True - Squares & Colored Squares + + +Inside Mountain Path to Secret Area (Inside Mountain) - Inside Mountain Bottom Layer - 0x17FA2: +0x00FF8 (Door to Secret Area) - True - Triangles & Black/White Squares & Squares +0x334E1 (Rock Control) - True - True + +Inside Mountain Secret Area (Inside Mountain Secret Area) - Inside Mountain Path to Secret Area - 0x00FF8 - Main Island - 0x021D7 - Main Island - 0x17CF2: +0x021D7 (Shortcut to Mountain) - True - Triangles & Stars & Stars + Same Colored Symbol & Colored Triangles +0x17CF2 (Shortcut to Swamp) - True - Triangles +0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares +0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares +0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots +0x00190 (Dot Grid Triangles 1) - True - Dots & Triangles +0x00558 (Dot Grid Triangles 2) - 0x00190 - Dots & Triangles +0x00567 (Dot Grid Triangles 3) - 0x00558 - Dots & Triangles +0x006FE (Dot Grid Triangles 4) - 0x00567 - Dots & Triangles +0x01A0D (Symmetry Triangles) - True - Symmetry & Triangles +0x008B8 (Squares and Triangles) - True - Squares & Black/White Squares & Triangles +0x00973 (Stars and Triangles) - 0x008B8 - Stars & Triangles +0x0097B (Stars and Triangles of same color) - 0x00973 - Stars & Triangles & Stars and Triangles of same color & Stars + Same Colored Symbol +0x0097D (Stars & Squares and Triangles) - 0x0097B - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol & Triangles +0x0097E (Stars & Squares and Triangles 2) - 0x0097D - Stars & Squares & Black/White Squares & Stars + Same Colored Symbol & Stars and Triangles of same color +0x00994 (Rotated Shapers and Triangles 1) - True - Rotated Shapers & Triangles +0x334D5 (Rotated Shapers and Triangles 2) - 0x00994 - Rotated Shapers & Triangles +0x00995 (Rotated Shapers and Triangles 3) - 0x334D5 - Rotated Shapers & Triangles +0x00996 (Shapers and Triangles 1) - 0x00995 - Shapers & Triangles +0x00998 (Shapers and Triangles 2) - 0x00996 - Shapers & Triangles +0x009A4 (Broken Shapers) - True - Shapers & Broken Shapers +0x018A0 (Symmetry Shapers) - True - Shapers & Symmetry +0x00A72 (Broken and Negative Shapers) - True - Shapers & Broken Shapers & Negative Shapers +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 +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 +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 +0x0A2CE (Upstairs Dot Grid Stars) - 0x0A16B - Stars & Dots +0x0A2D7 (Upstairs Dot Grid Triangles) - 0x0A2CE - Triangles & Dots +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 +0x0006B (Upstairs Invisible Dots 2) - 0x0008F - Dots & Invisible Dots +0x0008B (Upstairs Invisible Dots 3) - 0x0006B - Dots & Invisible Dots +0x0008C (Upstairs Invisible Dots 4) - 0x0008B - Dots & Invisible Dots +0x0008A (Upstairs Invisible Dots 5) - 0x0008C - Dots & Invisible Dots +0x00089 (Upstairs Invisible Dots 6) - 0x0008A - Dots & Invisible Dots +0x0006A (Upstairs Invisible Dots 7) - 0x00089 - Dots & Invisible Dots +0x0006C (Upstairs Invisible Dots 8) - 0x0006A - Dots & Invisible Dots +0x00027 (Upstairs Invisible Dot Symmetry 1) - True - Dots & Invisible Dots & Symmetry +0x00028 (Upstairs Invisible Dot Symmetry 2) - 0x00027 - Dots & Invisible Dots & Symmetry +0x00029 (Upstairs Invisible Dot Symmetry 3) - 0x00028 - Dots & Invisible Dots & Symmetry + +Challenge (Challenge) - Inside Mountain Secret Area - 0x0A16E: +0x0A332 (Start Timer) - True - True +0x0088E (Small Basic) - 0x0A332 - True +0x00BAF (Big Basic) - 0x0088E - True +0x00BF3 (Square) - 0x00BAF - Squares & Black/White Squares +0x00C09 (Maze Map) - 0x00BF3 - Dots +0x00CDB (Stars and Dots) - 0x00C09 - Stars & Dots +0x0051F (Symmetry) - 0x00CDB - Symmetry & Colored Dots & Dots +0x00524 (Stars and Shapers) - 0x0051F - Stars & Shapers +0x00CD4 (Big Basic 2) - 0x00524 - True +0x00CB9 (Choice Squares Right) - 0x00CD4 - Squares & Black/White Squares +0x00CA1 (Choice Squares Middle) - 0x00CD4 - Squares & Black/White Squares +0x00C80 (Choice Squares Left) - 0x00CD4 - Squares & Black/White Squares +0x00C68 (Choice Squares 2 Right) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares +0x00C59 (Choice Squares 2 Middle) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares +0x00C22 (Choice Squares 2 Left) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares +0x034F4 (Maze Hidden 1) - 0x00C68 | 0x00C59 | 0x00C22 - Triangles +0x034EC (Maze Hidden 2) - 0x00C68 | 0x00C59 | 0x00C22 - Triangles +0x1C31A (Dots Pillar) - 0x034F4 & 0x034EC - Dots & Symmetry & Pillar +0x1C319 (Squares Pillar) - 0x034F4 & 0x034EC - Squares & Black/White Squares & Symmetry & Pillar +0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True +0x039B4 (Door to Theater Walkway) - True - Triangles + +Theater Walkway (Theater Walkway) - Challenge - 0x039B4 - Theater - 0x27732 - Desert Elevator Room - 0x2773D & 0x03608 - Town - 0x09E85: +0x2FAF6 (Vault Box) - True - True +0x27732 (Door to Back of Theater) - True - True +0x2773D (Door to Desert Elevator Room) - True - True +0x09E85 (Door to Town) - True - Triangles + +Final Room (Inside Mountain Final Room) - Inside Mountain Bottom Layer - 0x01983 & 0x01987: +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 +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 + +Elevator (Inside Mountain Final Room) - Final Room - 0x339BB & 0x33961: +0x3D9A6 (Elevator Door Closer Left) - True - True +0x3D9A7 (Elevator Door Close Right) - True - True +0x3C113 (Elevator Door Open Left) - 0x3D9A6 | 0x3D9A7 - True +0x3C114 (Elevator Door Open Right) - 0x3D9A6 | 0x3D9A7 - True +0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +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: diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py new file mode 100644 index 00000000..1c10c6bc --- /dev/null +++ b/worlds/witness/__init__.py @@ -0,0 +1,165 @@ +""" +Archipelago init file for The Witness +""" + +import typing + +from BaseClasses import Region, RegionType, Location, MultiWorld, Item, Entrance +from ..AutoWorld import World, WebWorld +from .player_logic import StaticWitnessLogic, WitnessPlayerLogic +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 + + +class WitnessWebWorld(WebWorld): + theme = "jungle" + + +class WitnessWorld(World): + """ + The Witness is an open-world puzzle game with dozens of locations + to explore and over 500 puzzles. Play the popular puzzle randomizer + by sigma144, with an added layer of progression randomization! + """ + game = "The Witness" + topology_present = False + static_logic = StaticWitnessLogic() + static_locat = StaticWitnessLocations() + static_items = StaticWitnessItems() + web = WitnessWebWorld() + options = the_witness_options + + item_name_to_id = { + name: data.code for name, data in static_items.ALL_ITEM_TABLE.items() + } + location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID + + def _get_slot_data(self): + 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 + } + + def generate_early(self): + self.player_logic = WitnessPlayerLogic(self.world, self.player) + self.locat = WitnessPlayerLocations(self.world, self.player, self.player_logic) + self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic) + self.regio = WitnessRegions(self.locat) + + def generate_basic(self): + # Generate item pool + pool = [] + 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: + 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]) + + # Put in junk items to fill the rest + junk_pool = self.items.JUNK_WEIGHTS.copy() + junk_pool = self.world.random.choices( + list(junk_pool.keys()), weights=list(junk_pool.values()), + k=len(self.locat.CHECK_LOCATION_TABLE) - len(pool) - len(self.locat.EVENT_LOCATION_TABLE) - 1 + ) + + pool += [self.create_item(junk) for junk in junk_pool] + + # Tie Event Items to Event Locations (e.g. Laser Activations) + for event_location in self.locat.EVENT_LOCATION_TABLE: + item_obj = self.create_item( + self.player_logic.EVENT_ITEM_PAIRS[event_location] + ) + location_obj = self.world.get_location(event_location, self.player) + location_obj.place_locked_item(item_obj) + + self.world.itempool += pool + + def create_regions(self): + self.regio.create_regions(self.world, self.player, self.player_logic) + + def set_rules(self): + set_rules(self.world, self.player, self.player_logic, self.locat) + + def fill_slot_data(self) -> dict: + slot_data = self._get_slot_data() + + slot_data["hard_mode"] = False + + for option_name in the_witness_options: + slot_data[option_name] = is_option_enabled( + self.world, self.player, option_name + ) + + return slot_data + + def create_item(self, name: str) -> Item: + # this conditional is purely for unit tests, which need to be able to create an item before generate_early + if hasattr(self, 'items'): + item = self.items.ITEM_TABLE[name] + else: + item = StaticWitnessItems.ALL_ITEM_TABLE[name] + + new_item = WitnessItem( + name, item.progression, item.code, player=self.player + ) + new_item.trap = item.trap + return new_item + + def get_filler_item_name(self) -> str: # Used ny itemlinks + junk_pool = self.items.JUNK_WEIGHTS.copy() + + return self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()))[0] + + +class WitnessLocation(Location): + """ + Archipelago Location for The Witness + """ + game: str = "The Witness" + check_hex: int = -1 + + def __init__(self, player: int, name: str, address: typing.Optional[int], parent, ch_hex: int = -1): + super().__init__(player, name, address, parent) + self.check_hex = ch_hex + + +def create_region(world: MultiWorld, player: int, name: str, + locat: WitnessPlayerLocations, region_locations=None, exits=None): + """ + Create an Archipelago Region for The Witness + """ + + ret = Region(name, RegionType.Generic, name, player) + ret.world = world + if region_locations: + for location in region_locations: + loc_id = locat.CHECK_LOCATION_TABLE[location] + + check_hex = -1 + if location in StaticWitnessLogic.CHECKS_BY_NAME: + check_hex = int( + StaticWitnessLogic.CHECKS_BY_NAME[location]["checkHex"], 0 + ) + location = WitnessLocation( + player, location, loc_id, ret, check_hex + ) + + ret.locations.append(location) + if exits: + for single_exit in exits: + ret.exits.append(Entrance(player, single_exit, ret)) + + return ret diff --git a/worlds/witness/items.py b/worlds/witness/items.py new file mode 100644 index 00000000..4e19bfa7 --- /dev/null +++ b/worlds/witness/items.py @@ -0,0 +1,98 @@ +""" +Defines progression, junk and event items for The Witness +""" +import copy +from typing import Dict, NamedTuple, Optional + +from BaseClasses import Item, MultiWorld +from . import StaticWitnessLogic, WitnessPlayerLocations, WitnessPlayerLogic +from .Options import is_option_enabled + + +class ItemData(NamedTuple): + """ + ItemData for an item in The Witness + """ + code: Optional[int] + progression: bool + event: bool = False + trap: bool = False + + +class WitnessItem(Item): + """ + Item from the game The Witness + """ + game: str = "The Witness" + + +class StaticWitnessItems: + """ + Class that handles Witness items independent of world settings + """ + + ALL_ITEM_TABLE: Dict[str, ItemData] = {} + + JUNK_WEIGHTS = { + "Speed Boost": 1, + "Slowness": 0.8, + "Power Surge": 0.2, + } + + def __init__(self): + item_tab = dict() + + for item in StaticWitnessLogic.ALL_ITEMS: + if item[0] == "11 Lasers" or item == "7 Lasers": + continue + + item_tab[item[0]] = ItemData(158000 + item[1], True, False) + + for item in StaticWitnessLogic.ALL_TRAPS: + item_tab[item[0]] = ItemData( + 158000 + item[1], False, False, True + ) + + for item in StaticWitnessLogic.ALL_BOOSTS: + item_tab[item[0]] = ItemData(158000 + item[1], False, False) + + item_tab = dict(sorted( + item_tab.items(), + key=lambda single_item: single_item[1].code + if isinstance(single_item[1].code, int) else 0) + ) + + for key, item in item_tab.items(): + self.ALL_ITEM_TABLE[key] = item + + +class WitnessPlayerItems: + """ + Class that defines Items for a single world + """ + + def __init__(self, locat: WitnessPlayerLocations, world: MultiWorld, player: int, player_logic: WitnessPlayerLogic): + """Adds event items after logic changes due to options""" + self.EVENT_ITEM_TABLE = dict() + self.ITEM_TABLE = copy.copy(StaticWitnessItems.ALL_ITEM_TABLE) + + self.GOOD_ITEMS = [ + "Dots", "Black/White Squares", "Stars", + "Shapers", "Symmetry" + ] + + if is_option_enabled(world, player, "shuffle_discarded_panels"): + self.GOOD_ITEMS.append("Triangles") + if not is_option_enabled(world, player, "disable_non_randomized_puzzles"): + self.GOOD_ITEMS.append("Colored Squares") + + for event_location in locat.EVENT_LOCATION_TABLE: + location = player_logic.EVENT_ITEM_PAIRS[event_location] + self.EVENT_ITEM_TABLE[location] = ItemData(None, True, True) + self.ITEM_TABLE[location] = ItemData(None, True, True) + + self.JUNK_WEIGHTS = { + key: value for (key, value) + in StaticWitnessItems.JUNK_WEIGHTS.items() + if key in self.ITEM_TABLE.keys() + } diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py new file mode 100644 index 00000000..548dcafd --- /dev/null +++ b/worlds/witness/locations.py @@ -0,0 +1,281 @@ +""" +Defines constants for different types of locations in the game +""" + +from .Options import is_option_enabled +from .player_logic import StaticWitnessLogic, WitnessPlayerLogic + + +class StaticWitnessLocations: + """ + Witness Location Constants that stay consistent across worlds + """ + ID_START = 158000 + + TYPE_OFFSETS = { + "General": 0, + "Discard": 600, + "Vault": 650, + "Laser": 700, + } + + GENERAL_LOCATIONS = { + "Tutorial Gate Open", + + "Outside Tutorial Vault Box", + "Outside Tutorial Discard", + "Outside Tutorial Dots Introduction 5", + "Outside Tutorial Squares Introduction 9", + + "Glass Factory Discard", + "Glass Factory Vertical Symmetry 5", + "Glass Factory Rotational Symmetry 3", + "Glass Factory Melting 3", + + "Symmetry Island Black Dots 5", + "Symmetry Island Colored Dots 6", + "Symmetry Island Fading Lines 7", + "Symmetry Island Scenery Outlines 5", + "Symmetry Island Laser", + + "Orchard Apple Tree 5", + + "Desert Vault Box", + "Desert Discard", + "Desert Sun Reflection 8", + "Desert Artificial Light Reflection 3", + "Desert Pond Reflection 5", + "Desert Flood Reflection 6", + "Desert Laser", + + "Quarry Mill Eraser and Dots 6", + "Quarry Mill Eraser and Squares 8", + "Quarry Mill Small Squares & Dots & and Eraser", + "Quarry Mill Big Squares & Dots & and Eraser", + "Quarry Boathouse Intro Shapers", + "Quarry Boathouse Eraser and Shapers 5", + "Quarry Boathouse Stars & Eraser & and Shapers 2", + "Quarry Boathouse Stars & Eraser & and Shapers 5", + "Quarry Discard", + "Quarry Laser", + + "Shadows Lower Avoid 8", + "Shadows Environmental Avoid 8", + "Shadows Follow 5", + "Shadows Laser", + + "Keep Hedge Maze 4", + "Keep Pressure Plates 4", + "Keep Discard", + "Keep Laser Hedges", + "Keep Laser Pressure Plates", + + "Shipwreck Vault Box", + "Shipwreck Discard", + + "Monastery Rhombic Avoid 3", + "Monastery Branch Follow 2", + "Monastery Laser", + + "Town Cargo Box Discard", + "Town Hexagonal Reflection", + "Town Square Avoid", + "Town Rooftop Discard", + "Town Symmetry Squares 5 + Dots", + "Town Full Dot Grid Shapers 5", + "Town Shapers & Dots & and Eraser", + "Town Laser", + + "Theater Discard", + + "Jungle Discard", + "Jungle Waves 3", + "Jungle Waves 7", + "Jungle Popup Wall 6", + "Jungle Laser", + + "River Vault Box", + + "Bunker Drawn Squares 5", + "Bunker Drawn Squares 9", + "Bunker Drawn Squares through Tinted Glass 3", + "Bunker Drop-Down Door Squares 2", + "Bunker Laser", + + "Swamp Seperatable Shapers 6", + "Swamp Combinable Shapers 8", + "Swamp Broken Shapers 4", + "Swamp Cyan Underwater Negative Shapers 5", + "Swamp Platform Shapers 4", + "Swamp Rotated Shapers 4", + "Swamp Red Underwater Negative Shapers 4", + "Swamp More Rotated Shapers 4", + "Swamp Blue Underwater Negative Shapers 5", + "Swamp Laser", + + "Treehouse Yellow Bridge 9", + "Treehouse First Purple Bridge 5", + "Treehouse Second Purple Bridge 7", + "Treehouse Green Bridge 7", + "Treehouse Green Bridge Discard", + "Treehouse Left Orange Bridge 15", + "Treehouse Burned House Discard", + "Treehouse Right Orange Bridge 12", + "Treehouse Laser", + + "Mountaintop Trap Door Triple Exit", + "Mountaintop Discard", + "Mountaintop Vault Box", + + "Inside Mountain Obscured Vision 5", + "Inside Mountain Moving Background 7", + "Inside Mountain Physically Obstructed 3", + "Inside Mountain Angled Inside Trash 2", + "Inside Mountain Color Cycle 5", + "Inside Mountain Same Solution 6", + "Inside Mountain Elevator Discard", + "Inside Mountain Giant Puzzle", + } + + UNCOMMON_LOCATIONS = { + "Mountaintop River Shape", + "Tutorial Patio Floor", + "Theater Tutorial Video", + "Theater Desert Video", + "Theater Jungle Video", + "Theater Shipwreck Video", + "Theater Mountain Video", + "Town RGB Squares", + "Town RGB Stars", + "Swamp Underwater Back Optional", + } + + HARD_LOCATIONS = { + "Tutorial Gate Close", + + "Inside Mountain Secret Area Dot Grid Triangles 4", + "Inside Mountain Secret Area Symmetry Triangles", + "Inside Mountain Secret Area Stars & Squares and Triangles 2", + "Inside Mountain Secret Area Shapers and Triangles 2", + "Inside Mountain Secret Area Symmetry Shapers", + "Inside Mountain Secret Area Broken and Negative Shapers", + "Inside Mountain Secret Area Broken Shapers", + + "Inside Mountain Secret Area Rainbow Squares", + "Inside Mountain Secret Area Squares & Stars and Colored Eraser", + "Inside Mountain Secret Area Rotated Broken Shapers", + "Inside Mountain Secret Area Stars and Squares", + "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 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 Rotated Shapers", + + "Challenge Vault Box", + "Theater Walkway Vault Box", + "Inside Mountain Bottom Layer Discard", + "Theater Challenge Video", + } + + ALL_LOCATIONS_TO_ID = dict() + + @staticmethod + def get_id(chex): + """ + Calculates the location ID for any given location + """ + + panel_offset = StaticWitnessLogic.CHECKS_BY_HEX[chex]["idOffset"] + type_offset = StaticWitnessLocations.TYPE_OFFSETS[ + StaticWitnessLogic.CHECKS_BY_HEX[chex]["panelType"] + ] + + return StaticWitnessLocations.ID_START + panel_offset + type_offset + + @staticmethod + def get_event_name(panel_hex): + """ + Returns the event name of any given panel. + Currently this is always "Panelname Solved" + """ + + return StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]["checkName"] + " Solved" + + def __init__(self): + all_loc_to_id = { + panel_obj["checkName"]: self.get_id(chex) + for chex, panel_obj in StaticWitnessLogic.CHECKS_BY_HEX.items() + } + + all_loc_to_id = dict( + sorted(all_loc_to_id.items(), key=lambda loc: loc[1]) + ) + + for key, item in all_loc_to_id.items(): + self.ALL_LOCATIONS_TO_ID[key] = item + + +class WitnessPlayerLocations: + """ + Class that defines locations for a single player + """ + + def __init__(self, world, player, player_logic: WitnessPlayerLogic): + self.PANEL_TYPES_TO_SHUFFLE = {"General", "Laser"} + self.CHECK_LOCATIONS = ( + StaticWitnessLocations.GENERAL_LOCATIONS + ) + + """Defines locations AFTER logic changes due to options""" + + if is_option_enabled(world, player, "shuffle_discarded_panels"): + self.PANEL_TYPES_TO_SHUFFLE.add("Discard") + + if is_option_enabled(world, player, "shuffle_vault_boxes"): + self.PANEL_TYPES_TO_SHUFFLE.add("Vault") + + if is_option_enabled(world, player, "shuffle_uncommon"): + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.UNCOMMON_LOCATIONS + + if is_option_enabled(world, player, "shuffle_hard"): + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | StaticWitnessLocations.HARD_LOCATIONS + + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS + + self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - { + StaticWitnessLogic.CHECKS_BY_HEX[check_hex]["checkName"] + for check_hex in player_logic.COMPLETELY_DISABLED_CHECKS + } + + self.CHECK_PANELHEX_TO_ID = { + StaticWitnessLogic.CHECKS_BY_NAME[ch]["checkHex"]: StaticWitnessLocations.ALL_LOCATIONS_TO_ID[ch] + for ch in self.CHECK_LOCATIONS + } + + self.CHECK_PANELHEX_TO_ID = dict( + sorted(self.CHECK_PANELHEX_TO_ID.items(), key=lambda item: item[1]) + ) + + 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 = { + StaticWitnessLocations.get_event_name(panel_hex): None + for panel_hex in event_locations + } + + check_dict = { + location: StaticWitnessLocations.get_id(StaticWitnessLogic.CHECKS_BY_NAME[location]["checkHex"]) + for location in self.CHECK_LOCATIONS + if StaticWitnessLogic.CHECKS_BY_NAME[location]["panelType"] in self.PANEL_TYPES_TO_SHUFFLE + } + + self.CHECK_LOCATION_TABLE = {**self.EVENT_LOCATION_TABLE, **check_dict} diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py new file mode 100644 index 00000000..82a6d07b --- /dev/null +++ b/worlds/witness/player_logic.py @@ -0,0 +1,287 @@ +""" +Parses the WitnessLogic.txt logic file into useful data structures. +This is the heart of the randomization. + +In WitnessLogic.txt we have regions defined with their connections: + +Region Name (Short name) - Connected Region 1 - Connection Requirement 1 - Connected Region 2... + +And then panels in that region with the hex code used in the game +previous panels that are required to turn them on, as well as the symbols they require: + +0x##### (Panel Name) - Required Panels - Required Items + +On __init__, the base logic is read and all panels are given Location IDs. +When the world has parsed its options, a second function is called to finalize the logic. +""" + +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 + + +class WitnessPlayerLogic: + """WITNESS LOGIC CLASS""" + + def reduce_req_within_region(self, panel_hex): + """ + Panels in this game often only turn on when other panels are solved. + Those other panels may have different item requirements. + It would be slow to recursively check solvability each time. + This is why we reduce the item dependencies within the region. + 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"] + + 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: + dependent_items_for_option = frozenset({frozenset()}) + + for option_panel in option: + new_items = set() + dep_obj = StaticWitnessLogic.CHECKS_BY_HEX.get(option_panel) + if option_panel in {"7 Lasers", "11 Lasers"}: + new_items = frozenset({frozenset([option_panel])}) + # If a panel turns on when a panel in a different region turns on, + # the latter panel will be an "event panel", unless it ends up being + # a location itself. This prevents generation failures. + elif dep_obj["region"]["name"] != check_obj["region"]["name"]: + new_items = frozenset({frozenset([option_panel])}) + self.EVENT_PANELS_FROM_PANELS.add(option_panel) + else: + new_items = self.reduce_req_within_region(option_panel) + + updated_items = set() + + for items_option in dependent_items_for_option: + for items_option2 in new_items: + updated_items.add(items_option.union(items_option2)) + + dependent_items_for_option = updated_items + + for items_option in these_items: + for dependentItem in dependent_items_for_option: + all_options.add(items_option.union(dependentItem)) + + return frozenset(all_options) + + def make_single_adjustment(self, adj_type, line): + """Makes a single logic adjustment based on additional logic file""" + + if adj_type == "Event Items": + line_split = line.split(" - ") + hex_set = line_split[1].split(",") + + for hex_code in hex_set: + self.ALWAYS_EVENT_NAMES_BY_HEX[hex_code] = line_split[0] + + """ + Should probably do this differently... + Events right now depend on a panel. + That seems bad. + """ + + to_remove = set() + + for hex_code, event_name in self.ALWAYS_EVENT_NAMES_BY_HEX.items(): + if hex_code not in hex_set and event_name == line_split[0]: + to_remove.add(hex_code) + + for remove in to_remove: + del self.ALWAYS_EVENT_NAMES_BY_HEX[remove] + + return + + if adj_type == "Requirement Changes": + line_split = line.split(" - ") + + required_items = parse_lambda(line_split[2]) + items_actually_in_the_game = {item[0] for item in StaticWitnessLogic.ALL_ITEMS} + required_items = frozenset( + subset.intersection(items_actually_in_the_game) + for subset in required_items + ) + + requirement = { + "panels": parse_lambda(line_split[1]), + "items": required_items + } + + self.DEPENDENT_REQUIREMENTS_BY_HEX[line_split[0]] = requirement + + return + + if adj_type == "Disabled Locations": + self.COMPLETELY_DISABLED_CHECKS.add(line[:7]) + + return + + if adj_type == "Region Changes": + new_region_and_options = define_new_region(line + ":") + + self.CONNECTIONS_BY_REGION_NAME[new_region_and_options[0]["name"]] = new_region_and_options[1] + + return + + if adj_type == "Added Locations": + self.ADDED_CHECKS.add(line) + + def make_options_adjustments(self, world, player): + """Makes logic adjustments based on options""" + adjustment_linesets_in_order = [] + + if is_option_enabled(world, player, "challenge_victory"): + self.VICTORY_LOCATION = "0x0356B" + else: + self.VICTORY_LOCATION = "0x3D9A9" + + self.COMPLETELY_DISABLED_CHECKS.add( + self.VICTORY_LOCATION + ) + + if is_option_enabled(world, player, "disable_non_randomized_puzzles"): + adjustment_linesets_in_order.append(get_disable_unrandomized_list()) + + for adjustment_lineset in adjustment_linesets_in_order: + current_adjustment_type = None + + for line in adjustment_lineset: + if len(line) == 0: + continue + + if line[-1] == ":": + current_adjustment_type = line[:-1] + continue + + self.make_single_adjustment(current_adjustment_type, line) + + def make_dependency_reduced_checklist(self): + """ + Turns dependent check set into semi-independent check set + """ + + for check_hex in self.DEPENDENT_REQUIREMENTS_BY_HEX.keys(): + indep_requirement = self.reduce_req_within_region(check_hex) + + self.REQUIREMENTS_BY_HEX[check_hex] = indep_requirement + + def make_event_item_pair(self, panel): + """ + Makes a pair of an event panel and its event item + """ + name = StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" + pair = (name, self.EVENT_ITEM_NAMES[panel]) + return pair + + def make_event_panel_lists(self): + """ + Special event panel data structures + """ + + for region_conn in self.CONNECTIONS_BY_REGION_NAME.values(): + for region_and_option in region_conn: + for panelset in region_and_option[1]: + for panel in panelset: + self.EVENT_PANELS_FROM_REGIONS.add(panel) + + self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory" + + 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(): + for connection in self.CONNECTIONS_BY_REGION_NAME[region_name]: + connected_r = connection[0] + if connected_r not in StaticWitnessLogic.ALL_REGIONS_BY_NAME: + continue + if region_name == "Boat" or connected_r == "Boat": + continue + connected_r = StaticWitnessLogic.ALL_REGIONS_BY_NAME[connected_r] + if not any([panel in option for option in connection[1]]): + continue + if panel not in region["panels"] | connected_r["panels"]: + self.NECESSARY_EVENT_PANELS.add(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) + self.EVENT_ITEM_NAMES[always_hex] = always_item + + for panel in self.NECESSARY_EVENT_PANELS: + pair = self.make_event_item_pair(panel) + self.EVENT_ITEM_PAIRS[pair[0]] = pair[1] + + def __init__(self, world: MultiWorld, player: int): + self.EVENT_PANELS_FROM_PANELS = set() + self.EVENT_PANELS_FROM_REGIONS = 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() + + # Determining which panels need to be events is a difficult process. + # At the end, we will have EVENT_ITEM_PAIRS for all the necessary ones. + self.ORIGINAL_EVENT_PANELS = set() + self.NECESSARY_EVENT_PANELS = set() + self.EVENT_ITEM_PAIRS = dict() + self.ALWAYS_EVENT_HEX_CODES = set() + self.COMPLETELY_DISABLED_CHECKS = set() + self.ADDED_CHECKS = set() + self.VICTORY_LOCATION = "0x0356B" + self.EVENT_ITEM_NAMES = { + "0x01A0F": "Keep Laser Panel (Hedge Mazes) Activates", + "0x09D9B": "Monastery Overhead Doors Open", + "0x193A6": "Monastery Laser Panel Activates", + "0x00037": "Monastery Branch Panels Activate", + "0x0A079": "Access to Bunker Laser", + "0x0A3B5": "Door to Tutorial Discard Opens", + "0x01D3F": "Keep Laser Panel (Pressure Plates) Activates", + "0x09F7F": "Mountain Access", + "0x0367C": "Quarry Laser Mill Requirement Met", + "0x009A1": "Swamp Rotating Bridge Near Side", + "0x00006": "Swamp Cyan Water Drains", + "0x00990": "Swamp Broken Shapers 1 Activates", + "0x0A8DC": "Lower Avoid 6 Activates", + "0x0000A": "Swamp More Rotated Shapers 1 Access", + "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" + } + + self.ALWAYS_EVENT_NAMES_BY_HEX = { + "0x0360D": "Symmetry Laser Activation", + "0x03608": "Desert Laser Activation", + "0x09F98": "Desert Laser Redirection", + "0x03612": "Quarry Laser Activation", + "0x19650": "Shadows Laser Activation", + "0x0360E": "Keep Laser Hedges Activation", + "0x03317": "Keep Laser Pressure Plates Activation", + "0x17CA4": "Monastery Laser Activation", + "0x032F5": "Town Laser Activation", + "0x03616": "Jungle Laser Activation", + "0x09DE0": "Bunker Laser Activation", + "0x03615": "Swamp Laser Activation", + "0x03613": "Treehouse Laser Activation", + "0x03535": "Shipwreck Video Pattern Knowledge", + "0x03542": "Mountain Video Pattern Knowledge", + "0x0339E": "Desert Video Pattern Knowledge", + "0x03481": "Tutorial Video Pattern Knowledge", + "0x03702": "Jungle Video Pattern Knowledge", + "0x2FAF6": "Theater Walkway Video Pattern Knowledge", + } + + self.make_options_adjustments(world, player) + self.make_dependency_reduced_checklist() + self.make_event_panel_lists() diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py new file mode 100644 index 00000000..a7d549e7 --- /dev/null +++ b/worlds/witness/regions.py @@ -0,0 +1,89 @@ +""" +Defines Region for The Witness, assigns locations to them, +and connects them with the proper requirements +""" + +from BaseClasses import MultiWorld, Entrance +from . import StaticWitnessLogic +from .locations import WitnessPlayerLocations +from .player_logic import WitnessPlayerLogic + + +class WitnessRegions: + """Class that defines Witness Regions""" + + locat = None + logic = None + + def make_lambda(self, panel_hex_to_solve_set, world, player, player_logic): + """ + Lambdas are made in a for loop, so the values have to be captured + This function is for that purpose + """ + + return lambda state: state._witness_can_solve_panels( + panel_hex_to_solve_set, world, player, player_logic, self.locat + ) + + def connect(self, world: MultiWorld, player: int, source: str, target: str, player_logic: WitnessPlayerLogic, + panel_hex_to_solve_set=None): + """ + connect two regions and set the corresponding requirement + """ + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + + connection = Entrance( + player, + source + " to " + target + " via " + str(panel_hex_to_solve_set), + source_region + ) + + connection.access_rule = self.make_lambda(panel_hex_to_solve_set, world, player, player_logic) + + source_region.exits.append(connection) + connection.connect(target_region) + + def create_regions(self, world, player: int, player_logic: WitnessPlayerLogic): + """ + Creates all the regions for The Witness + """ + from . import create_region + + world.regions += [ + create_region(world, player, 'Menu', self.locat, None, ["The Splashscreen?"]), + ] + + all_locations = set() + + for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items(): + locations_for_this_region = [ + StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] for panel in region["panels"] + if StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE + ] + locations_for_this_region += [ + StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" for panel in region["panels"] + if StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" in self.locat.EVENT_LOCATION_TABLE + ] + + all_locations = all_locations | set(locations_for_this_region) + + world.regions += [ + create_region(world, player, region_name, self.locat,locations_for_this_region) + ] + + for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items(): + for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]: + if connection[0] == "Entry": + continue + self.connect(world, player, region_name, + connection[0], player_logic, connection[1]) + self.connect(world, player, connection[0], + region_name, player_logic, connection[1]) + + world.get_entrance("The Splashscreen?", player).connect( + world.get_region('First Hallway', player) + ) + + def __init__(self, locat: WitnessPlayerLocations): + self.locat = locat diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py new file mode 100644 index 00000000..e7ef30aa --- /dev/null +++ b/worlds/witness/rules.py @@ -0,0 +1,171 @@ +""" +Defines the rules by which locations can be accessed, +depending on the items received +""" + +# pylint: disable=E1101 + +from BaseClasses import MultiWorld +from .player_logic import WitnessPlayerLogic +from .Options import is_option_enabled +from .locations import WitnessPlayerLocations +from . import StaticWitnessLogic +from ..AutoWorld import LogicMixin +from ..generic.Rules import set_rule + + +class WitnessLogic(LogicMixin): + """ + Logic macros that get reused + """ + + def _witness_has_lasers(self, world, player: int, amount: int) -> bool: + lasers = 0 + + lasers += int(self.has("Symmetry Laser Activation", player)) + lasers += int(self.has("Desert Laser Activation", player) + 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("Quarry Laser Activation", player)) + lasers += int(self.has("Treehouse Laser Activation", player)) + lasers += int(self.has("Jungle Laser Activation", player)) + lasers += int(self.has("Bunker Laser Activation", player)) + lasers += int(self.has("Swamp Laser Activation", player)) + lasers += int(self.has("Shadows Laser Activation", player)) + + return lasers >= amount + + def _witness_can_solve_panel(self, panel, world, player, player_logic: WitnessPlayerLogic, locat): + """ + Determines whether a panel can be solved + """ + + panel_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel] + check_name = panel_obj["checkName"] + + if (check_name + " Solved" in locat.EVENT_LOCATION_TABLE + and not self.has(player_logic.EVENT_ITEM_PAIRS[check_name + " Solved"], player)): + return False + if panel not in player_logic.ORIGINAL_EVENT_PANELS and not self.can_reach(check_name, "Location", player): + return False + if (panel in player_logic.ORIGINAL_EVENT_PANELS + 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): + """ + Checks whether item and panel requirements are met for + a panel + """ + + panel_req = player_logic.REQUIREMENTS_BY_HEX[panel] + + for option in panel_req: + if len(option) == 0: + return True + + valid_option = True + + for item in option: + if item == "7 Lasers": + if not self._witness_has_lasers(world, player, 7): + valid_option = False + break + elif item == "11 Lasers": + if not self._witness_has_lasers(world, player, 11): + 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 not self.has(item, player): + valid_option = False + break + + if valid_option: + return True + + return False + + def _witness_safe_manual_panel_check(self, panel, world, player, player_logic: WitnessPlayerLogic, locat): + """ + nested can_reach can cause problems, but only if the region being + checked is neither of the two original regions from the first + can_reach. + A nested can_reach is okay here because the only panels this + function is called on are panels that exist on either side of all + connections they are required for. + The spoiler log looks so much nicer this way, + it gets rid of a bunch of event items, only leaving a couple. :) + """ + region = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"] + + return ( + self._witness_meets_item_requirements(panel, world, player, player_logic, locat) + and self.can_reach(region, "Region", player) + ) + + def _witness_can_solve_panels(self, panel_hex_to_solve_set, world, player, player_logic: WitnessPlayerLogic, locat): + """ + Checks whether a set of panels can be solved. + """ + + for option in panel_hex_to_solve_set: + if len(option) == 0: + return True + + valid_option = True + + for panel in option: + if not self._witness_can_solve_panel(panel, world, player, player_logic, locat): + valid_option = False + break + + if valid_option: + return True + return False + + +def make_lambda(check_hex, world, player, player_logic, locat): + """ + Lambdas are created in a for loop so values need to be captured + """ + return lambda state: state._witness_meets_item_requirements( + check_hex, world, player, player_logic, locat + ) + + +def set_rules(world: MultiWorld, player: int, player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations): + """ + Sets all rules for all locations + """ + + for location in locat.CHECK_LOCATION_TABLE: + real_location = location + + if location in locat.EVENT_LOCATION_TABLE: + real_location = location[:-7] + + panel = StaticWitnessLogic.CHECKS_BY_NAME[real_location] + check_hex = panel["checkHex"] + + rule = make_lambda(check_hex, world, player, player_logic, locat) + + set_rule(world.get_location(location, player), rule) + + world.completion_condition[player] = \ + lambda state: state.has('Victory', player) diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py new file mode 100644 index 00000000..680ca680 --- /dev/null +++ b/worlds/witness/static_logic.py @@ -0,0 +1,138 @@ +import os + +from .utils import define_new_region, parse_lambda + + +class StaticWitnessLogic: + ALL_ITEMS = set() + ALL_TRAPS = set() + ALL_BOOSTS = set() + + EVENT_PANELS_FROM_REGIONS = set() + + # All regions with a list of panels in them and the connections to other regions, before logic adjustments + ALL_REGIONS_BY_NAME = dict() + STATIC_CONNECTIONS_BY_REGION_NAME = dict() + + CHECKS_BY_HEX = dict() + CHECKS_BY_NAME = dict() + STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict() + + def parse_items(self): + """ + Parses currently defined items from WitnessItems.txt + """ + + path = os.path.join(os.path.dirname(__file__), "WitnessItems.txt") + with open(path, "r", encoding="utf-8") as file: + current_set = self.ALL_ITEMS + + for line in file.readlines(): + line = line.strip() + + if line == "Progression:": + current_set = self.ALL_ITEMS + continue + if line == "Boosts:": + current_set = self.ALL_BOOSTS + continue + if line == "Traps:": + current_set = self.ALL_TRAPS + continue + if line == "": + continue + + line_split = line.split(" - ") + + current_set.add((line_split[1], int(line_split[0]))) + + def read_logic_file(self): + """ + Reads the logic file and does the initial population of data structures + """ + path = os.path.join(os.path.dirname(__file__), "WitnessLogic.txt") + with open(path, "r", encoding="utf-8") as file: + current_region = "" + + discard_ids = 0 + normal_panel_ids = 0 + vault_ids = 0 + laser_ids = 0 + + for line in file.readlines(): + line = line.strip() + + if line == "": + continue + + if line[0] != "0": + new_region_and_connections = define_new_region(line) + current_region = new_region_and_connections[0] + region_name = current_region["name"] + self.ALL_REGIONS_BY_NAME[region_name] = current_region + self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1] + continue + + line_split = line.split(" - ") + + check_name_full = line_split.pop(0) + + check_hex = check_name_full[0:7] + check_name = check_name_full[9:-1] + + required_panel_lambda = line_split.pop(0) + required_item_lambda = line_split.pop(0) + + laser_names = { + "Laser", + "Laser Hedges", + "Laser Pressure Plates", + "Desert Laser Redirect" + } + is_vault_or_video = "Vault" in check_name or "Video" in check_name + + if "Discard" in check_name: + location_type = "Discard" + location_id = discard_ids + discard_ids += 1 + elif is_vault_or_video or check_name == "Tutorial Gate Close": + location_type = "Vault" + location_id = vault_ids + vault_ids += 1 + elif check_name in laser_names: + location_type = "Laser" + location_id = laser_ids + laser_ids += 1 + else: + location_type = "General" + 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( + subset.intersection(items_actually_in_the_game) + for subset in required_items + ) + + requirement = { + "panels": parse_lambda(required_panel_lambda), + "items": required_items + } + + self.CHECKS_BY_HEX[check_hex] = { + "checkName": current_region["shortName"] + " " + check_name, + "checkHex": check_hex, + "region": current_region, + "idOffset": location_id, + "panelType": location_type + } + + self.CHECKS_BY_NAME[self.CHECKS_BY_HEX[check_hex]["checkName"]] = self.CHECKS_BY_HEX[check_hex] + self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[check_hex] = requirement + + current_region["panels"].add(check_hex) + + def __init__(self): + self.parse_items() + self.read_logic_file() diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py new file mode 100644 index 00000000..85f43ab2 --- /dev/null +++ b/worlds/witness/utils.py @@ -0,0 +1,58 @@ +import os +from Utils import cache_argsless + + +def define_new_region(region_string): + """ + Returns a region object by parsing a line in the logic file + """ + + region_string = region_string[:-1] + line_split = region_string.split(" - ") + + region_name_full = line_split.pop(0) + + region_name_split = region_name_full.split(" (") + + region_name = region_name_split[0] + region_name_simple = region_name_split[1][:-1] + + options = set() + + for _ in range(len(line_split) // 2): + connected_region = line_split.pop(0) + corresponding_lambda = line_split.pop(0) + + options.add( + (connected_region, parse_lambda(corresponding_lambda)) + ) + + region_obj = { + "name": region_name, + "shortName": region_name_simple, + "panels": set() + } + return region_obj, options + + +def parse_lambda(lambda_string): + """ + Turns a lambda String literal like this: a | b & c + into a set of sets like this: {{a}, {b, c}} + The lambda has to be in DNF. + """ + if lambda_string == "True": + return frozenset([frozenset()]) + split_ands = set(lambda_string.split(" | ")) + lambda_set = frozenset({frozenset(a.split(" & ")) for a in split_ands}) + + return lambda_set + + +@cache_argsless +def get_disable_unrandomized_list(): + adjustment_file = "Disable_Unrandomized.txt" + path = os.path.join(os.path.dirname(__file__), adjustment_file) + + with open(path) as f: + return [line.strip() for line in f.readlines()] \ No newline at end of file