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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user