The Witness: The Secret Feature (#4370)

* Secret Feature

* Fixes

* Fixes and unit tests

* renaming some variables

* Fix the thing

* unit test for elevator egg

* Docstring

* reword

* Fix duplicate locations I think?

* Remove debug thing

* Add the tests back lol

* Make it so that you can exclude an egg to disable it

* Improve hint text for easter eggs

* Update worlds/witness/options.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/witness/player_logic.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/witness/options.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/witness/player_logic.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/witness/rules.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update test_easter_egg_shuffle.py

* This was actually not necessary, since this is the Egg requirements, nothing to do with location names

* Move one of them

* Improve logic

* Lol

* Moar

* Adjust unit tests

* option docstring adjustment

* Recommend door shuffle

* Don't overlap IDs

* Option description idk

* Change the way the difficulties work to reward playing higher modes

* Fix merge

* add some stuff to generate_data_file (this file is not imported during gen, don't review it :D)

* oop

* space

* This can be earlier than I thought, apparently.

* buffer

* Comment

* Make sure the option is VERY visible

* Some mypy stuff

* apparently ruff wants this

* .

* durinig

* Update options.py

* Explain the additional effects of each difficulty

* Fix logic of flood room secret

* Add Southern Peninsula Area

* oop

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
This commit is contained in:
NewSoupVi
2025-03-08 01:44:06 +01:00
committed by GitHub
parent bc61221ec6
commit 08b3b3ecf5
18 changed files with 651 additions and 73 deletions

View File

@@ -5,7 +5,7 @@ import dataclasses
from logging import error, warning
from typing import Any, Dict, List, Optional, cast
from BaseClasses import CollectionState, Entrance, Location, Region, Tutorial
from BaseClasses import CollectionState, Entrance, Location, LocationProgressType, Region, Tutorial
from Options import OptionError, PerGameCommonOptions, Toggle
from worlds.AutoWorld import WebWorld, World
@@ -380,6 +380,10 @@ class WitnessWorld(World):
if isinstance(item_name, dict):
item_name = next(iter(item_name))
# Easter Egg events with arbitrary sizes
if item_name.startswith("+") and "Easter Egg" in item_name:
return WitnessItem.make_egg_event(item_name, self.player)
# this conditional is purely for unit tests, which need to be able to create an item before generate_early
item_data: ItemData
if hasattr(self, "player_items") and self.player_items and item_name in self.player_items.item_data:
@@ -389,6 +393,18 @@ class WitnessWorld(World):
return WitnessItem(item_name, item_data.classification, item_data.ap_code, player=self.player)
def collect(self, state: "CollectionState", item: WitnessItem) -> bool:
changed = super().collect(state, item)
if changed and item.eggs:
state.prog_items[self.player]["Egg"] += item.eggs
return changed
def remove(self, state: "CollectionState", item: WitnessItem) -> bool:
changed = super().remove(state, item)
if changed and item.eggs:
state.prog_items[self.player]["Egg"] -= item.eggs
return changed
def get_filler_item_name(self) -> str:
return "Speed Boost"
@@ -398,11 +414,9 @@ class WitnessLocation(Location):
Archipelago Location for The Witness
"""
game: str = "The Witness"
entity_hex: int = -1
def __init__(self, player: int, name: str, address: Optional[int], parent: Region, ch_hex: int = -1) -> None:
def __init__(self, player: int, name: str, address: Optional[int], parent: Region) -> None:
super().__init__(player, name, address, parent)
self.entity_hex = ch_hex
def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlayerLocations,
@@ -416,14 +430,13 @@ def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlaye
for location in region_locations:
loc_id = player_locations.CHECK_LOCATION_TABLE[location]
entity_hex = -1
location_obj = WitnessLocation(world.player, location, loc_id, ret)
if location in static_witness_logic.ENTITIES_BY_NAME:
entity_hex = int(
static_witness_logic.ENTITIES_BY_NAME[location]["entity_hex"], 0
)
location_obj = WitnessLocation(
world.player, location, loc_id, ret, entity_hex
)
entity_hex = static_witness_logic.ENTITIES_BY_NAME[location]["entity_hex"]
if entity_hex in world.player_logic.EXCLUDED_ENTITIES:
location_obj.progress_type = LocationProgressType.EXCLUDED
ret.locations.append(location_obj)
if exits:

View File

@@ -206,7 +206,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316 - Desert Flood Room Underwater - 0x1C260:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -224,6 +224,8 @@ Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Flood Room Underwater (Desert):
Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
@@ -501,7 +503,7 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159121 - 0x03BE3 (Garden Right EP) - True - True
159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
Inside Monastery (Monastery) - Monastery North Shutters - 0x09D9B:
158213 - 0x09D9B (Shutters Control) - True - Dots
158214 - 0x193A7 (Inside 1) - 0x00037 - True
158215 - 0x193AA (Inside 2) - 0x193A7 - True
@@ -513,6 +515,8 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
Monastery North Shutters (Monastery):
==Town==
Town Obelisk (Town) - Entry - True:
@@ -637,9 +641,13 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
==Southern Peninsula==
Southern Peninsula (Southern Peninsula) - Main Island - True:
==Jungle==
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF - Jungle Under Popup Wall - 0x1475B:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Triangles
158252 - 0x002C4 (First Row 1) - True - True
@@ -670,6 +678,8 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
Jungle Under Popup Wall (Jungle):
Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
@@ -712,9 +722,11 @@ Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D:
158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Colored Squares & Black/White Squares
Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay - Bunker Under Elevator - 0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
Bunker Under Elevator (Bunker):
Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
@@ -1005,7 +1017,7 @@ Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Colored Squares & Eraser
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Dots
@@ -1018,11 +1030,15 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW
158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Black/White Squares & Stars + Same Colored Symbol
158419 - 0x09E6F (Left Row 6) - 0x09E6C - Stars & Rotated Shapers & Shapers
158420 - 0x09E6B (Left Row 7) - 0x09E6F - Stars & Dots
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shaper
Mountain Floor 1 Trash Pillar (Mountain Floor 1):
Mountain Floor 1 Back Section (Mountain Floor 1):
158421 - 0x33AF5 (Back Row 1) - True - Black/White Squares & Symmetry
158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Black/White Squares & Stars
158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Dots
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers
Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
@@ -1098,14 +1114,16 @@ Elevator (Mountain Bottom Floor):
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D - Caves Entry Door - TrueOneWay:
158447 - 0x00FF8 (Caves Entry Panel) - True - Triangles & Black/White Squares
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
==Caves==
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
Caves Entry Door (Caves):
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5 - Caves Entry Door - TrueOneWay:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
@@ -1219,3 +1237,7 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
==Easter Eggs==
Easter Eggs (Easter Eggs) - Entry - True:

View File

@@ -206,7 +206,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316 - Desert Flood Room Underwater - 0x1C260:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -224,6 +224,8 @@ Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Flood Room Underwater (Desert):
Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
@@ -501,7 +503,7 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159121 - 0x03BE3 (Garden Right EP) - True - True
159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
Inside Monastery (Monastery) - Monastery North Shutters - 0x09D9B:
158213 - 0x09D9B (Shutters Control) - True - Dots
158214 - 0x193A7 (Inside 1) - 0x00037 - True
158215 - 0x193AA (Inside 2) - 0x193A7 - True
@@ -513,6 +515,8 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
Monastery North Shutters (Monastery):
==Town==
Town Obelisk (Town) - Entry - True:
@@ -637,9 +641,13 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
==Southern Peninsula==
Southern Peninsula (Southern Peninsula) - Main Island - True:
==Jungle==
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF - Jungle Under Popup Wall - 0x1475B:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Arrows
158252 - 0x002C4 (First Row 1) - True - True
@@ -670,6 +678,8 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
Jungle Under Popup Wall (Jungle):
Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
@@ -712,9 +722,11 @@ Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D:
158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Squares & Colored Squares & Black/White Squares
Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay - Bunker Under Elevator - 0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
Bunker Under Elevator (Bunker):
Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
@@ -1005,7 +1017,7 @@ Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol
@@ -1018,11 +1030,15 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW
158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
158419 - 0x09E6F (Left Row 6) - 0x09E6C - Symmetry & Stars & Colored Squares & Black/White Squares & Stars + Same Colored Symbol & Symmetry & Eraser
158420 - 0x09E6B (Left Row 7) - 0x09E6F - Symmetry & Dots & Full Dots & Triangles
158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles
Mountain Floor 1 Trash Pillar (Mountain Floor 1):
Mountain Floor 1 Back Section (Mountain Floor 1):
158421 - 0x33AF5 (Back Row 1) - True - Symmetry & Black/White Squares & Triangles
158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Symmetry & Stars & Triangles & Stars + Same Colored Symbol
158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Stars & Shapers & Stars + Same Colored Symbol
158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles
Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
@@ -1098,14 +1114,16 @@ Elevator (Mountain Bottom Floor):
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D - Caves Entry Door - TrueOneWay:
158447 - 0x00FF8 (Caves Entry Panel) - True - Arrows & Black/White Squares
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
==Caves==
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
Caves Entry Door (Caves):
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5 - Caves Entry Door - TrueOneWay:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots
@@ -1219,3 +1237,7 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
==Easter Eggs==
Easter Eggs (Easter Eggs) - Entry - True:

View File

@@ -206,7 +206,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316 - Desert Flood Room Underwater - 0x1C260:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -224,6 +224,8 @@ Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Flood Room Underwater (Desert):
Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
@@ -501,7 +503,7 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159121 - 0x03BE3 (Garden Right EP) - True - True
159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
Inside Monastery (Monastery) - Monastery North Shutters - 0x09D9B:
158213 - 0x09D9B (Shutters Control) - True - Dots
158214 - 0x193A7 (Inside 1) - 0x00037 - True
158215 - 0x193AA (Inside 2) - 0x193A7 - True
@@ -513,6 +515,8 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
Monastery North Shutters (Monastery):
==Town==
Town Obelisk (Town) - Entry - True:
@@ -637,9 +641,13 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
==Southern Peninsula==
Southern Peninsula (Southern Peninsula) - Main Island - True:
==Jungle==
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF - Jungle Under Popup Wall - 0x1475B:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Triangles
158252 - 0x002C4 (First Row 1) - True - True
@@ -670,6 +678,8 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
Jungle Under Popup Wall (Jungle):
Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
@@ -712,9 +722,11 @@ Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D:
158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Colored Squares & Black/White Squares
Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay - Bunker Under Elevator - 0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
Bunker Under Elevator (Bunker):
Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
@@ -1005,7 +1017,7 @@ Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Rotated Shapers
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers
@@ -1018,11 +1030,15 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW
158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Black/White Squares
158419 - 0x09E6F (Left Row 6) - 0x09E6C - Shapers & Dots
158420 - 0x09E6B (Left Row 7) - 0x09E6F - Dots
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers
Mountain Floor 1 Trash Pillar (Mountain Floor 1):
Mountain Floor 1 Back Section (Mountain Floor 1):
158421 - 0x33AF5 (Back Row 1) - True - Black/White Squares & Symmetry
158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Black/White Squares
158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Dots
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers
Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
@@ -1098,14 +1114,16 @@ Elevator (Mountain Bottom Floor):
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D - Caves Entry Door - TrueOneWay:
158447 - 0x00FF8 (Caves Entry Panel) - True - Black/White Squares
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
==Caves==
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
Caves Entry Door (Caves):
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5 - Caves Entry Door - TrueOneWay:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
@@ -1219,3 +1237,7 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
==Easter Eggs==
Easter Eggs (Easter Eggs) - Entry - True:

View File

@@ -206,7 +206,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316 - Desert Flood Room Underwater - 0x1C260:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -224,6 +224,8 @@ Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
Desert Flood Room Underwater (Desert):
Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
@@ -501,7 +503,7 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159121 - 0x03BE3 (Garden Right EP) - True - True
159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
Inside Monastery (Monastery) - Monastery North Shutters - 0x09D9B:
158213 - 0x09D9B (Shutters Control) - True - Dots
158214 - 0x193A7 (Inside 1) - 0x00037 - True
158215 - 0x193AA (Inside 2) - 0x193A7 - True
@@ -513,6 +515,8 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
Monastery North Shutters (Monastery):
==Town==
Town Obelisk (Town) - Entry - True:
@@ -637,9 +641,13 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
==Southern Peninsula==
Southern Peninsula (Southern Peninsula) - Main Island - True:
==Jungle==
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF - Jungle Under Popup Wall - 0x1475B:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Arrows & Triangles
158252 - 0x002C4 (First Row 1) - True - True
@@ -670,6 +678,8 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
Jungle Under Popup Wall (Jungle):
Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
@@ -712,9 +722,11 @@ Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D:
158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Colored Squares & Black/White Squares
Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay - Bunker Under Elevator - 0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
Bunker Under Elevator (Bunker):
Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
@@ -1005,7 +1017,7 @@ Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Colored Squares & Eraser
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol
@@ -1018,11 +1030,15 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW
158418 - 0x09E6C (Left Row 5) - 0x09E79 - Arrows & Black/White Squares & Stars & Stars + Same Colored Symbol
158419 - 0x09E6F (Left Row 6) - 0x09E6C - Arrows & Dots & Full Dots
158420 - 0x09E6B (Left Row 7) - 0x09E6F - Arrows & Dots & Full Dots
158424 - 0x09EAD (Trash Pillar 1) - True - Triangles & Arrows
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Triangles & Arrows
Mountain Floor 1 Trash Pillar (Mountain Floor 1):
Mountain Floor 1 Back Section (Mountain Floor 1):
158421 - 0x33AF5 (Back Row 1) - True - Symmetry & Triangles
158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Triangles
158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Triangles
158424 - 0x09EAD (Trash Pillar 1) - True - Triangles & Arrows
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Triangles & Arrows
Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
@@ -1098,14 +1114,16 @@ Elevator (Mountain Bottom Floor):
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D - Caves Entry Door - TrueOneWay:
158447 - 0x00FF8 (Caves Entry Panel) - True - Black/White Squares & Arrows & Triangles
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
==Caves==
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
Caves Entry Door (Caves):
Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5 - Caves Entry Door - TrueOneWay:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
@@ -1219,3 +1237,7 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
==Easter Eggs==
Easter Eggs (Easter Eggs) - Entry - True:

View File

@@ -0,0 +1,75 @@
MAXIMUM_EASTER_EGG_CHECKS = 50
EASTER_EGGS = {
"Tutorial": 1,
"Outside Tutorial": 4,
"Outside Tutorial Path To Outpost": 1,
"Outside Tutorial Outpost": 1,
"Orchard Beyond First Gate": 1,
"Orchard End": 1,
"Inside Glass Factory": 2,
"Symmetry Island Lower": 2,
"Symmetry Island Upper": 1,
"Desert Outside": 6,
"Desert Vault": 1,
"Desert Pond Room": 2,
"Desert Flood Room Underwater": 1,
"Desert Elevator Room": 1,
"Outside Quarry": 2,
"Quarry": 5,
"Quarry Stoneworks Upper Floor": 2,
"Quarry Boathouse": 2,
"Shadows": 2,
"Shadows Ledge": 1,
"Shadows Laser Room": 1,
"Keep": 1,
"Keep 3rd Maze": 3,
"Keep 2nd Pressure Plate": 2,
"Keep 3rd Pressure Plate": 1,
"Keep 4th Pressure Plate": 1,
"Keep Tower": 2,
"Shipwreck": 5,
"Inside Monastery": 1,
"Monastery North Shutters": 1,
"Monastery Garden": 1,
"Town": 5,
"Town Wooden Rooftop": 1,
"Town RGB House": 2,
"Town Tower Top": 1,
"Windmill Interior": 2,
"Theater": 1,
"Southern Peninsula": 5,
"Jungle": 2,
"Jungle Under Popup Wall": 1,
"Jungle Vault": 1,
"Outside Bunker": 3,
"Bunker Glass Room": 1,
"Bunker Under Elevator": 1,
"Bunker Green Room": 1,
"Outside Swamp": 2,
"Swamp Entry Area": 1,
"Swamp Platform": 1,
"Swamp Cyan Underwater": 1,
"Swamp Near Boat": 1,
"Swamp Laser Area": 1,
"Treehouse Beach": 1,
"Treehouse Yellow Bridge": 1,
"Treehouse Junction": 2,
"Treehouse Second Purple Bridge": 1,
"Treehouse Green Bridge Left House": 1,
"Treehouse Laser Room Back Platform": 1,
"Treehouse Burned House": 1,
"Treehouse Drawbridge Platform": 1,
"Mountainside": 4,
"Mountaintop": 1,
"Mountain Floor 1 Trash Pillar": 1,
"Mountain Floor 1 Back Section": 1,
"Mountain Floor 2": 1,
"Mountain Bottom Floor Pillars Room": 1,
"Caves Entry Door": 1,
"Caves": 2,
"Caves Path to Challenge": 1,
"Challenge": 2,
"Tunnels": 2,
"The Ocean": 2,
}

View File

@@ -1,4 +1,4 @@
from collections import defaultdict
from collections import Counter, defaultdict
from typing import Any, Dict, List, Optional, Set, Tuple
from Utils import cache_argsless
@@ -11,6 +11,7 @@ from .item_definition_classes import (
ProgressiveItemDefinition,
WeightedItemDefinition,
)
from .settings.easter_eggs import EASTER_EGGS
from .utils import (
WitnessRule,
define_new_region,
@@ -49,6 +50,70 @@ class StaticWitnessLogicObj:
self.reverse_connections()
self.combine_connections()
def add_easter_eggs(self) -> None:
egg_counter = 0
area_counts: Dict[str, int] = Counter()
for region_name, entity_amount in EASTER_EGGS.items():
region_object = self.ALL_REGIONS_BY_NAME[region_name]
correct_area = region_object["area"]
for _ in range(entity_amount):
location_id = 160200 + egg_counter
entity_hex = hex(0xEE000 + egg_counter)
egg_counter += 1
area_counts[correct_area["name"]] += 1
full_entity_name = f"{correct_area['name']} Easter Egg {area_counts[correct_area['name']]}"
self.ENTITIES_BY_HEX[entity_hex] = {
"checkName": full_entity_name,
"entity_hex": entity_hex,
"region": region_object,
"id": int(location_id),
"entityType": "Easter Egg",
"locationType": "Easter Egg",
"area": correct_area,
"order": len(self.ENTITIES_BY_HEX),
}
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
"entities": frozenset({frozenset({})})
}
region_object["entities"].append(entity_hex)
region_object["physical_entities"].append(entity_hex)
easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"]
easter_egg_area = easter_egg_region["area"]
for i in range(sum(EASTER_EGGS.values())):
location_id = 160000 + i
entity_hex = hex(0xEE200 + i)
if i == 0:
continue
full_entity_name = f"{i + 1} Easter Eggs Collected"
self.ENTITIES_BY_HEX[entity_hex] = {
"checkName": full_entity_name,
"entity_hex": entity_hex,
"region": easter_egg_region,
"id": int(location_id),
"entityType": "Easter Egg Total",
"locationType": "Easter Egg Total",
"area": easter_egg_area,
"order": len(self.ENTITIES_BY_HEX),
}
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
"entities": frozenset({frozenset({})})
}
easter_egg_region["entities"].append(entity_hex)
easter_egg_region["physical_entities"].append(entity_hex)
def read_logic_file(self, lines: List[str]) -> None:
"""
Reads the logic file and does the initial population of data structures
@@ -66,7 +131,7 @@ class StaticWitnessLogicObj:
continue
if line[-1] == ":":
new_region_and_connections = define_new_region(line)
new_region_and_connections = define_new_region(line, current_area)
current_region = new_region_and_connections[0]
region_name = current_region["name"]
self.ALL_REGIONS_BY_NAME[region_name] = current_region
@@ -198,6 +263,8 @@ class StaticWitnessLogicObj:
current_region["entities"].append(entity_hex)
current_region["physical_entities"].append(entity_hex)
self.add_easter_eggs()
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
target = connection[0]
traversal_options = connection[1]

View File

@@ -1,3 +1,4 @@
from datetime import date
from math import floor
from pkgutil import get_data
from random import Random
@@ -61,7 +62,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
return rounded_output
def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
"""
Returns a region object by parsing a line in the logic file
"""
@@ -91,6 +92,7 @@ def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str
"shortName": region_name_simple,
"entities": [],
"physical_entities": [],
"area": area,
}
return region_obj, options
@@ -264,3 +266,15 @@ def logical_and_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRu
def logical_or_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRule:
return optimize_witness_rule(frozenset.union(*witness_rules))
def is_easter_time() -> bool:
# dateutils would have been nice here, because it has an easter() function.
# But adding it as a requirement seems heavier than necessary.
# Thus, we just take a range from the earliest to latest possible easter dates.
today = date.today()
earliest_easter_day = date(today.year, 3, 20) # Earliest possible is 3/22 + 2 day buffer for Good Friday
last_easter_day = date(today.year, 4, 26) # Latest possible is 4/25 + 1 day buffer for Easter Monday
return earliest_easter_day <= today <= last_easter_day

View File

@@ -43,3 +43,12 @@ if __name__ == "__main__":
)
)
datafile.write("\n};\n\n")
datafile.write("inline std::map<int, std::string> entityToName = {")
datafile.write(
"\n".join(
"\t{ " + entity_hex + ', "' + entity_object["checkName"] + '" },'
for entity_hex, entity_object in static_witness_logic.ENTITIES_BY_HEX.items()
)
)
datafile.write("\n};\n\n")

View File

@@ -241,7 +241,10 @@ def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint) -> Witnes
area = chosen_group
# local locations should only ever return a location group, as Witness defines groups for every location.
hint_text = f"{item_name} can be found in the {area} area."
if area == "Easter Eggs":
hint_text = f"{item_name} can be found by collecting Easter Eggs."
else:
hint_text = f"{item_name} can be found in the {area} area."
else:
player_name = world.multiworld.get_player_name(hint.location.player)
@@ -505,10 +508,13 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, area_items: List[Ite
area_progression_word = "Both" if total_progression == 2 else "All"
hint_string = f"In the {hinted_area} area, you will find "
if hinted_area == "Easter Eggs":
hint_string = "Through collecting Easter Eggs, you will find "
else:
hint_string = f"In the {hinted_area} area, you will find "
hunt_panels = None
if world.options.victory_condition == "panel_hunt":
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
hunt_panels = sum(
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"]["name"] == hinted_area
for hunt_entity in world.player_logic.HUNT_ENTITIES

View File

@@ -19,7 +19,7 @@ class WitnessPlayerLocations:
def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None:
"""Defines locations AFTER logic changes due to options"""
self.PANEL_TYPES_TO_SHUFFLE = {"General", "Good Boi"}
self.PANEL_TYPES_TO_SHUFFLE = {"General", "Good Boi", "Easter Egg Total"}
self.CHECK_LOCATIONS = static_witness_locations.GENERAL_LOCATIONS.copy()
if world.options.shuffle_discarded_panels:

View File

@@ -1,4 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Tuple
from schema import And, Schema
@@ -18,6 +20,7 @@ from Options import (
from .data import static_logic as static_witness_logic
from .data.item_definition_classes import ItemCategory, WeightedItemDefinition
from .data.utils import is_easter_time
from .entity_hunt import ALL_HUNTABLE_PANELS
@@ -142,6 +145,53 @@ class ShuffleEnvironmentalPuzzles(Choice):
option_obelisk_sides = 2
class EasterEggHunt(Choice):
"""
Adds up to 120 Easter Eggs to the game, placed by NewSoupVi, Exempt-Medic, hatkirby, Scipio, and Rever.
These can be collected by simply clicking on them.
The difficulty options differ by how many Eggs you need to collect for each check and how many are logically required for each check.
- "Easy": 3 / 8
- "Normal": 3 / 6
- "Hard": 4 / 6
- "Very Hard": 4 / 5
- "Extreme": 4 / 4 (You are expected to collect every Easter Egg)
Checks that require more Eggs than logically available still exist, but are excluded.
For example, on "Easy", the "63 Eggs Collected" check can physically be obtained, but would logically require 125 Easter Eggs, which is impossible. Thus, it is excluded.
On "Easy", "Normal", and "Hard", you will start with an "Egg Radar" that you can activate using the Puzzle Skip key.
On every difficulty except "Extreme", there will be a message when you've collected all Easter Eggs in an area.
On "Easy", there will be an additional message after every Easter Egg telling you how many Easter Eggs are remaining in the area.
It is recommended that you play this mode together with Door Shuffle. Without it, more than half of the Easter Eggs will be in sphere 1.
"""
visibility = Visibility.all if is_easter_time() else Visibility.none
display_name = "Easter Egg Hunt"
option_off = 0
# Number represents the amount of eggs needed per check
option_easy = 1
option_normal = 2
option_hard = 3
option_very_hard = 4
option_extreme = 5
default = 2 if is_easter_time() else 0
def get_step_and_logical_step(self) -> Tuple[int, int]:
if self == "easy":
return 3, 8
if self == "normal":
return 3, 6
if self == "hard":
return 4, 6
if self == "very_hard":
return 4, 5
return 4, 4
class ShuffleDog(Choice):
"""
Adds petting the dog statue in Town into the location pool.
@@ -504,6 +554,7 @@ class TheWitnessOptions(PerGameCommonOptions):
death_link_amnesty: DeathLinkAmnesty
puzzle_randomization_seed: PuzzleRandomizationSeed
shuffle_dog: ShuffleDog
easter_egg_hunt: EasterEggHunt
witness_option_groups = [
@@ -561,3 +612,13 @@ witness_option_groups = [
ShuffleDog,
])
]
# Make sure that Easter Egg Hunt is VERY visible during easter time (when it's enabled by default)
if is_easter_time():
easter_special_option_group = OptionGroup("EASTER SPECIAL", [
EasterEggHunt,
])
witness_option_groups = [easter_special_option_group, *witness_option_groups]
else:
silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options")
silly_options_group.options.append(EasterEggHunt)

View File

@@ -31,6 +31,13 @@ class WitnessItem(Item):
Item from the game The Witness
"""
game: str = "The Witness"
eggs: int = 0
@classmethod
def make_egg_event(cls, item_name: str, player: int):
ret = cls(item_name, ItemClassification.progression, None, player)
ret.eggs = int(item_name[1:].split(" ", 1)[0])
return ret
class WitnessPlayerItems:

View File

@@ -24,7 +24,6 @@ from .data.item_definition_classes import DoorItemDefinition, ItemCategory, Prog
from .data.static_logic import StaticWitnessLogicObj
from .data.utils import (
WitnessRule,
define_new_region,
get_boat,
get_caves_except_path_to_challenge_exclusion_list,
get_complex_additional_panels,
@@ -119,6 +118,8 @@ class WitnessPlayerLogic:
self.PRE_PICKED_HUNT_ENTITIES: Set[str] = set()
self.HUNT_ENTITIES: Set[str] = set()
self.AVAILABLE_EASTER_EGGS: Set[str] = set()
self.AVAILABLE_EASTER_EGGS_PER_REGION: Dict[str, int] = {}
self.ALWAYS_EVENT_NAMES_BY_HEX = {
"0x00509": "+1 Laser",
"0x012FB": "+1 Laser (Unredirected)",
@@ -154,6 +155,9 @@ class WitnessPlayerLogic:
picker = EntityHuntPicker(self, world, self.PRE_PICKED_HUNT_ENTITIES)
self.HUNT_ENTITIES = picker.pick_panel_hunt_panels(world.options.panel_hunt_total.value)
if world.options.easter_egg_hunt:
self.finalize_easter_eggs(world)
# Finalize which items actually exist in the MultiWorld and which get grouped into progressive items.
self.finalize_items()
@@ -241,6 +245,8 @@ class WitnessPlayerLogic:
if option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect",
"PP2 Weirdness", "Theater to Tunnels", "Entity Hunt"}:
new_items = frozenset({frozenset([option_entity])})
elif "Eggs" in option_entity:
new_items = frozenset({frozenset([option_entity])})
elif option_entity in self.DISABLE_EVERYTHING_BEHIND:
new_items = frozenset()
else:
@@ -387,13 +393,6 @@ class WitnessPlayerLogic:
return
if adj_type == "Region Changes":
new_region_and_options = define_new_region(line + ":")
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[new_region_and_options[0]["name"]] = new_region_and_options[1]
return
if adj_type == "New Connections":
line_split = line.split(" - ")
source_region = line_split[0]
@@ -533,6 +532,55 @@ class WitnessPlayerLogic:
return postgame_adjustments
def set_easter_egg_requirements(self, world: "WitnessWorld") -> None:
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
for entity_hex, entity_obj in static_witness_logic.ENTITIES_BY_HEX.items():
if entity_obj["entityType"] != "Easter Egg Total":
continue
direct_egg_count = int(entity_obj["checkName"].split(" ")[0])
if direct_egg_count % eggs_per_check:
self.COMPLETELY_DISABLED_ENTITIES.add(entity_hex)
requirement = direct_egg_count // eggs_per_check * logically_required_eggs_per_check
self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
"entities": frozenset({frozenset({f"{requirement} Eggs"})})
}
def finalize_easter_eggs(self, world: "WitnessWorld") -> None:
self.AVAILABLE_EASTER_EGGS = {
entity_hex for entity_hex, entity_obj in static_witness_logic.ENTITIES_BY_HEX.items()
if entity_obj["entityType"] == "Easter Egg" and self.solvability_guaranteed(entity_hex)
}
max_eggs = len(self.AVAILABLE_EASTER_EGGS)
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
for entity_hex in self.AVAILABLE_EASTER_EGGS:
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"]["name"]
self.AVAILABLE_EASTER_EGGS_PER_REGION[region_name] += 1
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
for entity_hex, entity_obj in static_witness_logic.ENTITIES_BY_HEX.items():
if entity_obj["entityType"] != "Easter Egg Total":
continue
if entity_hex in self.COMPLETELY_DISABLED_ENTITIES:
continue
direct_egg_count = int(entity_obj["checkName"].split(" ", 1)[0])
logically_required_egg_count = direct_egg_count // eggs_per_check * logically_required_eggs_per_check
if direct_egg_count > max_eggs:
self.COMPLETELY_DISABLED_ENTITIES.add(entity_hex)
continue
self.ADDED_CHECKS.add(entity_obj["checkName"])
if logically_required_egg_count > max_eggs:
# Exclude and set logic to require every egg
self.EXCLUDED_ENTITIES.add(entity_hex)
self.REQUIREMENTS_BY_HEX[entity_hex] = frozenset({frozenset({f"{max_eggs} Eggs"})})
def make_options_adjustments(self, world: "WitnessWorld") -> None:
"""Makes logic adjustments based on options"""
adjustment_linesets_in_order = []
@@ -641,6 +689,8 @@ class WitnessPlayerLogic:
adjustment_linesets_in_order.append([
"New Connections:",
"Outside Bunker - Bunker Elevator - TrueOneWay",
"Bunker Elevator Section - Bunker Under Elevator - "
"0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform | Outside Bunker",
])
if "Swamp Long Bridge" in world.options.elevators_come_to_you:
adjustment_linesets_in_order.append([
@@ -655,6 +705,14 @@ class WitnessPlayerLogic:
# "New Connections:"
# "Town Red Rooftop - Town Maze Rooftop - TrueOneWay"
if world.options.easter_egg_hunt:
self.set_easter_egg_requirements(world)
else:
self.COMPLETELY_DISABLED_ENTITIES.update({
entity_hex for entity_hex, entity_obj in static_witness_logic.ENTITIES_BY_HEX.items()
if "Easter Egg" in entity_obj["entityType"]
})
if world.options.victory_condition == "panel_hunt":
adjustment_linesets_in_order.append(get_entity_hunt())
@@ -691,6 +749,9 @@ class WitnessPlayerLogic:
if loc_obj["entityType"] == "EP":
self.COMPLETELY_DISABLED_ENTITIES.add(loc_obj["entity_hex"])
if loc_obj["entityType"] == "Easter Egg":
self.COMPLETELY_DISABLED_ENTITIES.add(loc_obj["entity_hex"])
elif loc_obj["entityType"] == "Panel":
self.EXCLUDED_ENTITIES.add(loc_obj["entity_hex"])
@@ -937,6 +998,7 @@ class WitnessPlayerLogic:
doors = world.options.shuffle_doors
shortbox_req = world.options.mountain_lasers
longbox_req = world.options.challenge_lasers
eggs_exist = world.options.easter_egg_hunt
swamp_bridge_comes_to_you = "Swamp Long Bridge" in world.options.elevators_come_to_you
quarry_elevator_comes_to_you = "Quarry Elevator" in world.options.elevators_come_to_you
@@ -953,17 +1015,17 @@ class WitnessPlayerLogic:
# It is easier to think about when these items *are* required, so we make that dict first
# If the entity is disabled anyway, we don't need to consider that case
is_item_required_dict = {
"0x03750": eps_shuffled, # Monastery Garden Entry Door
"0x03750": eps_shuffled or eggs_exist, # Monastery Garden Entry Door
"0x275FA": eps_shuffled, # Boathouse Hook Control
"0x17D02": eps_shuffled, # Windmill Turn Control
"0x0368A": symbols_shuffled or door_panels, # Quarry Stoneworks Stairs Door
"0x3865F": symbols_shuffled or door_panels or eps_shuffled, # Quarry Boathouse 2nd Barrier
"0x17CC4": quarry_elevator_comes_to_you or eps_shuffled, # Quarry Elevator Panel
"0x17E2B": swamp_bridge_comes_to_you and boat_shuffled or eps_shuffled, # Swamp Long Bridge
"0x0CF2A": False, # Jungle Monastery Garden Shortcut
"0x0CF2A": eggs_exist, # Jungle Monastery Garden Shortcut
"0x0364E": False, # Monastery Laser Shortcut Door
"0x03713": remote_doors, # Monastery Laser Shortcut Panel
"0x03313": False, # Orchard Second Gate
"0x03313": eggs_exist, # Orchard Second Gate
"0x337FA": remote_doors, # Jungle Bamboo Laser Shortcut Panel
"0x3873B": False, # Jungle Bamboo Laser Shortcut Door
"0x335AB": False, # Caves Elevator Controls
@@ -1026,4 +1088,9 @@ class WitnessPlayerLogic:
entity_name = entity_obj["checkName"]
self.EVENT_ITEM_PAIRS[entity_name + " (Panel Hunt)"] = ("+1 Panel Hunt", entity_hex)
for region_name, easter_egg_count in self.AVAILABLE_EASTER_EGGS_PER_REGION.items():
plural = "s" if easter_egg_count != 1 else ""
event_name = f"+{easter_egg_count} Easter Egg{plural}"
self.EVENT_ITEM_PAIRS[f"{region_name} Easter Egg{plural}"] = (event_name, region_name)
return

View File

@@ -117,12 +117,17 @@ class WitnessPlayerRegions:
event_locations_per_region = defaultdict(dict)
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
region = static_witness_logic.ENTITIES_BY_HEX[event_item_and_entity[1]]["region"]
if region is None:
region_name = "Entry"
entity_or_region = event_item_and_entity[1]
if entity_or_region in static_witness_logic.ALL_REGIONS_BY_NAME:
region_name = entity_or_region
order = -1
else:
region_name = region["name"]
order = self.reference_logic.ENTITIES_BY_HEX[event_item_and_entity[1]]["order"]
region = static_witness_logic.ENTITIES_BY_HEX[event_item_and_entity[1]]["region"]
if region is None:
region_name = "Entry"
else:
region_name = region["name"]
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
event_locations_per_region[region_name][event_location] = order
for region_name, region in regions_to_create.items():

View File

@@ -2,7 +2,7 @@ line-length = 120
[lint]
select = ["C", "E", "F", "R", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"]
ignore = ["C9", "RUF012", "RUF100"]
ignore = ["C9", "RUF012", "RUF021", "RUF100", "UP006", "UP035"]
[lint.per-file-ignores]
# The way options definitions work right now, I am forced to break line length requirements.

View File

@@ -196,6 +196,8 @@ def _has_item(item: str, world: "WitnessWorld",
if item == "Entity Hunt":
# Right now, panel hunt is the only type of entity hunt. This may need to be changed later
return _can_do_panel_hunt(world)
if "Eggs" in item:
return SimpleItemRepresentation("Egg", int(item.split(" ")[0]))
if item == "PP2 Weirdness":
return lambda state: _can_do_expert_pp2(state, world)
if item == "Theater to Tunnels":
@@ -303,6 +305,11 @@ def make_lambda(entity_hex: str, world: "WitnessWorld") -> Optional[CollectionRu
return _meets_item_requirements(entity_req, world)
def make_region_lambda(region_name: str, world: "WitnessWorld") -> CollectionRule:
region = world.get_region(region_name)
return lambda state: region.can_reach(state)
def set_rules(world: "WitnessWorld") -> None:
"""
Sets all rules for all locations
@@ -312,8 +319,12 @@ def set_rules(world: "WitnessWorld") -> None:
real_location = location
if location in world.player_locations.EVENT_LOCATION_TABLE:
entity_hex = world.player_logic.EVENT_ITEM_PAIRS[location][1]
real_location = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"]
entity_hex_or_region_name = world.player_logic.EVENT_ITEM_PAIRS[location][1]
if entity_hex_or_region_name in static_witness_logic.ALL_REGIONS_BY_NAME:
set_rule(world.get_location(location), make_region_lambda(entity_hex_or_region_name, world))
continue
real_location = static_witness_logic.ENTITIES_BY_HEX[entity_hex_or_region_name]["checkName"]
associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location]
entity_hex = associated_entity["entity_hex"]

View File

@@ -0,0 +1,155 @@
from typing import cast
from BaseClasses import LocationProgressType
from .. import WitnessWorld
from ..test import WitnessMultiworldTestBase
class TestEasterEggShuffle(WitnessMultiworldTestBase):
options_per_world = [
{
"easter_egg_hunt": "off",
},
{
"easter_egg_hunt": "easy",
},
{
"easter_egg_hunt": "normal",
},
{
"easter_egg_hunt": "hard",
},
{
"easter_egg_hunt": "very_hard",
},
{
"easter_egg_hunt": "extreme",
},
]
def test_easter_egg_hunt(self) -> None:
with self.subTest("Test that player without Easter Egg Hunt has no easter egg related locations"):
egg_locations = {location for location in self.multiworld.get_locations(1) if "Egg" in location.name}
self.assertFalse(egg_locations)
for player, eggs_per_check, logical_eggs_per_check in zip([2, 3, 4, 5, 6], [3, 3, 4, 4, 4], [8, 6, 6, 5, 4]):
world = cast(WitnessWorld, self.multiworld.worlds[player])
option_name = world.options.easter_egg_hunt
with self.subTest(f"Test that {option_name} Egg Hunt player starts with 0 eggs"):
self.assertEqual(self.multiworld.state.count("Egg", player), 0)
with self.subTest(f"Test that the correct Egg Collection locations exist for {option_name} player"):
first_egg_location = f"{eggs_per_check} Easter Eggs Collected"
one_less_location = f"{eggs_per_check - 1} Easter Eggs Collected"
one_more_location = f"{eggs_per_check + 1} Easter Eggs Collected"
self.assert_location_exists(first_egg_location, player)
self.assert_location_does_not_exist(one_less_location, player, strict_check=False)
self.assert_location_does_not_exist(one_more_location, player, strict_check=False)
one_too_few = logical_eggs_per_check - 1
with self.subTest(f'Test that "+{one_too_few} Easter Eggs" item adds 4 easter eggs'):
item = world.create_item(f"+{one_too_few} Easter Eggs")
self.multiworld.state.collect(item, prevent_sweep=True)
self.assertEqual(self.multiworld.state.count("Egg", player), one_too_few)
with self.subTest(
f"Test that {one_too_few} Easter Eggs are not enough for {option_name} player's first location"
):
self.assertFalse(self.multiworld.state.can_reach_location(first_egg_location, player))
with self.subTest(
f"Test that {logical_eggs_per_check} Easter Eggs are enough for {option_name} player's first location"
):
item = world.create_item("+1 Easter Egg")
self.multiworld.state.collect(item, prevent_sweep=True)
self.assertTrue(self.multiworld.state.can_reach_location(first_egg_location, player))
class TestEggRestrictions(WitnessMultiworldTestBase):
options_per_world = [
{
"shuffle_postgame": False,
},
{
"shuffle_postgame": True,
},
{
"shuffle_postgame": True,
"exclude_locations": frozenset({"Bunker Easter Egg 3"}),
}
]
common_options = {
"victory_condition": "mountain_box_short",
"shuffle_doors": "off",
"easter_egg_hunt": "very_hard",
"shuffle_vault_boxes": True,
}
def test_egg_restrictions(self) -> None:
with self.subTest("Test that locations beyond 108 Easter Eggs don't exist for a seed without Mountain"):
self.assert_location_exists("108 Easter Eggs Collected", 1)
self.assert_location_does_not_exist("112 Easter Eggs Collected", 1)
with self.subTest(
"Test that locations beyond 86 Easter Eggs, which would logically require more than 108 Eggs, are excluded"
):
egg_84_location = self.multiworld.get_location("84 Easter Eggs Collected", 1)
egg_88_location = self.multiworld.get_location("88 Easter Eggs Collected", 1)
self.assertNotEqual(egg_84_location.progress_type, LocationProgressType.EXCLUDED)
self.assertEqual(egg_88_location.progress_type, LocationProgressType.EXCLUDED)
with self.subTest("Test that in a seed with the whole game included, the 120 egg location exists"):
self.assert_location_exists("120 Easter Eggs Collected", 2)
with self.subTest(
"Test that locations beyond 96 Easter Eggs, which would logically require more than 120 Eggs, are excluded"
):
egg_96_location = self.multiworld.get_location("96 Easter Eggs Collected", 2)
egg_100_location = self.multiworld.get_location("100 Easter Eggs Collected", 2)
self.assertNotEqual(egg_96_location.progress_type, LocationProgressType.EXCLUDED)
self.assertEqual(egg_100_location.progress_type, LocationProgressType.EXCLUDED)
with self.subTest("Test that you can exclude and egg to disable it"):
self.assert_location_exists("116 Easter Eggs Collected", 3)
self.assert_location_does_not_exist("120 Easter Eggs Collected", 3)
class TestBunkerElevatorEgg(WitnessMultiworldTestBase):
options_per_world = [
{
"elevators_come_to_you": frozenset()
},
{
"elevators_come_to_you": frozenset({"Bunker Elevator"})
},
]
common_options = {
"easter_egg_hunt": "normal",
"shuffle_doors": "panels",
"shuffle_symbols": False,
}
def test_bunker_elevator_egg(self) -> None:
items_to_reach_bunker_elevator = [
"Bunker Entry (Panel)",
"Bunker Tinted Glass Door (Panel)",
"Bunker Drop-Down Door Controls (Panel)"
]
with self.subTest("Test that normally, the egg behind the elevator needs Elevator Control"):
self.assertFalse(self.multiworld.state.can_reach_location("Bunker Under Elevator Easter Egg", 1))
self.collect_by_name(items_to_reach_bunker_elevator, 1)
self.assertFalse(self.multiworld.state.can_reach_location("Bunker Under Elevator Easter Egg", 1))
self.collect_by_name(["Bunker Elevator Control (Panel)"], 1)
self.assertTrue(self.multiworld.state.can_reach_location("Bunker Under Elevator Easter Egg", 1))
with self.subTest("Test that with auto-elevators, the egg behind the elevator doesn't need Elevator Control"):
self.assertFalse(self.multiworld.state.can_reach_location("Bunker Under Elevator Easter Egg", 2))
self.collect_by_name(items_to_reach_bunker_elevator, 2)
self.assertTrue(self.multiworld.state.can_reach_location("Bunker Under Elevator Easter Egg", 2))