mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Witness: Automatic Postgame & Disabled Panels Calculation (#2698)
* Refactor postgame code to be more readable * Change all references to options to strings * oops * Fix some outdated code related to yaml-disabled EPs * Small fixes to short/longbox stuff (thanks Medic) * comment * fix duplicate * Removed triplicate lmfao * Better comment * added another 'unfun' postgame consideration * comment * more option strings * oops * Remove an unnecessary comparison * another string missed * New classification changes (Credit: Exempt-Medic) * Don't need to pass world * Comments * Replace it with another magic system because why not at this point :DDDDDD * oops * Oops * Another was missed * Make events conditions. Disable_Non_Randomized will no longer just 'have all events' * What the fuck? Has this just always been broken? * Don't have boolean function with 'not' in the name * Another useful classification * slight code refactor * Funny haha booleans * This would create a really bad merge error * I can't believe this actually kind of works * And here's the punchline. + some bugfixes * Comment dat code * Comments galore * LMAO OOPS * so nice I did it twice * debug x2 * Careful * Add more comments * That comment is a bit unnecessary now * Fix overriding region connections * Correct a comment * Correct again * Rename variable * Idk I guess this is in this branch now * More tweaking of postgame & comments * This is commit just exists to fix that grammar error * I think I can just fucking delete this now??? * Forgot to reset something here * Delete dead codepath * Obelisk Keys were getting yote erroneously * More comments * Fix duplicate connections * Oopsington III * performance improvements & cleanup * More rules cleanup and performance improvements * Oh cool I can do this huh * Okay but this is even more swag tho * Lazy eval * remove some implicit checks * Is this too magical yet * more guard magic * Maaaaaaaagiccccccccc * Laaaaaaaaaaaaaaaazzzzzzyyyyyyyyyyy * Make it docstring * Newline bc I like that better * this is a little spooky lol * lol * Wait * spoO * Better variable name and comment * Improved comment again * better API * oops I deleted a deepcopy * lol help * Help??? * player_regionsns lmao * Add some comments * Make doors disabled properly again. I hope this works * Don't disable lasers * Omega oops * Make Floor 2 Exit not exist * Make a fix that's warps compatible * I think this was an oversight, I tested a seed and it seems to have the same result * This is definitely less Violet than before * Does this feel more violet lol * Exception if a laser gets disabled, cleanup * Ruff * >:( * consistent utils import * Make autopostgame more reviewable (hopefully) * more reviewability * WitnessRule * replace another instance of it * lint * style * comment * found the bug * Move comment * Get rid of cache and ugly allow_victory * comments and lint
This commit is contained in:
@@ -2,15 +2,14 @@
|
||||
Defines the rules by which locations can be accessed,
|
||||
depending on the items received
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, FrozenSet
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
from worlds.generic.Rules import CollectionRule, set_rule
|
||||
|
||||
from . import WitnessPlayerRegions
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.utils import WitnessRule
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -32,8 +31,7 @@ laser_hexes = [
|
||||
]
|
||||
|
||||
|
||||
def _has_laser(laser_hex: str, world: "WitnessWorld", player: int,
|
||||
redirect_required: bool) -> CollectionRule:
|
||||
def _has_laser(laser_hex: str, world: "WitnessWorld", player: int, redirect_required: bool) -> CollectionRule:
|
||||
if laser_hex == "0x012FB" and redirect_required:
|
||||
return lambda state: (
|
||||
_can_solve_panel(laser_hex, world, world.player, world.player_logic, world.player_locations)(state)
|
||||
@@ -69,95 +67,164 @@ def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logi
|
||||
return make_lambda(panel, world)
|
||||
|
||||
|
||||
def _can_move_either_direction(state: CollectionState, source: str, target: str,
|
||||
player_regions: WitnessPlayerRegions) -> bool:
|
||||
entrance_forward = player_regions.created_entrances[source, target]
|
||||
entrance_backward = player_regions.created_entrances[target, source]
|
||||
|
||||
return (
|
||||
any(entrance.can_reach(state) for entrance in entrance_forward)
|
||||
or
|
||||
any(entrance.can_reach(state) for entrance in entrance_backward)
|
||||
)
|
||||
|
||||
|
||||
def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool:
|
||||
"""
|
||||
For Expert PP2, you need a way to access PP2 from the front, and a separate way from the back.
|
||||
This condition is quite complicated. We'll attempt to evaluate it as lazily as possible.
|
||||
"""
|
||||
|
||||
player = world.player
|
||||
|
||||
hedge_2_access = (
|
||||
_can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.player_regions)
|
||||
)
|
||||
|
||||
hedge_3_access = (
|
||||
_can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.player_regions)
|
||||
or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.player_regions)
|
||||
and hedge_2_access
|
||||
)
|
||||
|
||||
hedge_4_access = (
|
||||
_can_move_either_direction(state, "Keep 4th Maze", "Keep", world.player_regions)
|
||||
or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.player_regions)
|
||||
and hedge_3_access
|
||||
)
|
||||
|
||||
hedge_access = (
|
||||
_can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.player_regions)
|
||||
and state.can_reach("Keep", "Region", player)
|
||||
and hedge_4_access
|
||||
)
|
||||
|
||||
backwards_to_fourth = (
|
||||
state.can_reach("Keep", "Region", player)
|
||||
and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Keep Tower", world.player_regions)
|
||||
and (
|
||||
_can_move_either_direction(state, "Keep", "Keep Tower", world.player_regions)
|
||||
or hedge_access
|
||||
)
|
||||
)
|
||||
|
||||
shadows_shortcut = (
|
||||
state.can_reach("Main Island", "Region", player)
|
||||
and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.player_regions)
|
||||
)
|
||||
|
||||
backwards_access = (
|
||||
_can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.player_regions)
|
||||
and (backwards_to_fourth or shadows_shortcut)
|
||||
)
|
||||
player_regions = world.player_regions
|
||||
|
||||
front_access = (
|
||||
_can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.player_regions)
|
||||
and state.can_reach("Keep", "Region", player)
|
||||
any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 2nd Pressure Plate", "Keep"])
|
||||
and state.can_reach_region("Keep", player)
|
||||
)
|
||||
|
||||
return front_access and backwards_access
|
||||
# If we don't have front access, we can't do PP2.
|
||||
if not front_access:
|
||||
return False
|
||||
|
||||
# Front access works. Now, we need to check for the many ways to access PP2 from the back.
|
||||
# All of those ways lead through the PP3 exit door from PP4. So we check this first.
|
||||
|
||||
fourth_to_third = any(e.can_reach(state) for e in player_regions.two_way_entrance_register[
|
||||
"Keep 3rd Pressure Plate", "Keep 4th Pressure Plate"
|
||||
])
|
||||
|
||||
# If we can't get from PP4 to PP3, we can't do PP2.
|
||||
if not fourth_to_third:
|
||||
return False
|
||||
|
||||
# We can go from PP4 to PP3. We now need to find a way to PP4.
|
||||
# The shadows shortcut is the simplest way.
|
||||
|
||||
shadows_shortcut = (
|
||||
any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Pressure Plate", "Shadows"])
|
||||
)
|
||||
|
||||
if shadows_shortcut:
|
||||
return True
|
||||
|
||||
# We don't have the Shadows shortcut. This means we need to come in through the PP4 exit door instead.
|
||||
|
||||
tower_to_pp4 = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Pressure Plate", "Keep Tower"]
|
||||
)
|
||||
|
||||
# If we don't have the PP4 exit door, we've run out of options.
|
||||
if not tower_to_pp4:
|
||||
return False
|
||||
|
||||
# We have the PP4 exit door. If we can get to Keep Tower from behind, we can do PP2.
|
||||
# The simplest way would be the Tower Shortcut.
|
||||
|
||||
tower_shortcut = any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep", "Keep Tower"])
|
||||
|
||||
if tower_shortcut:
|
||||
return True
|
||||
|
||||
# We don't have the Tower shortcut. At this point, there is one possibility remaining:
|
||||
# Getting to Keep Tower through the hedge mazes. This can be done in a multitude of ways.
|
||||
# No matter what, though, we would need Hedge Maze 4 Exit to Keep Tower.
|
||||
|
||||
tower_access_from_hedges = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep Tower"]
|
||||
)
|
||||
|
||||
if not tower_access_from_hedges:
|
||||
return False
|
||||
|
||||
# We can reach Keep Tower from Hedge Maze 4. If we now have the Hedge 4 Shortcut, we are immediately good.
|
||||
|
||||
hedge_4_shortcut = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep"]
|
||||
)
|
||||
|
||||
# If we have the hedge 4 shortcut, that works.
|
||||
if hedge_4_shortcut:
|
||||
return True
|
||||
|
||||
# We don't have the hedge 4 shortcut. This means we would now need to come through Hedge Maze 3.
|
||||
|
||||
hedge_3_to_4 = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 4th Maze", "Keep 3rd Maze"]
|
||||
)
|
||||
|
||||
if not hedge_3_to_4:
|
||||
return False
|
||||
|
||||
# We can get to Hedge 4 from Hedge 3. If we have the Hedge 3 Shortcut, we're good.
|
||||
|
||||
hedge_3_shortcut = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 3rd Maze", "Keep"]
|
||||
)
|
||||
|
||||
if hedge_3_shortcut:
|
||||
return True
|
||||
|
||||
# We don't have Hedge 3 Shortcut. This means we would now need to come through Hedge Maze 2.
|
||||
|
||||
hedge_2_to_3 = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 3rd Maze", "Keep 2nd Maze"]
|
||||
)
|
||||
|
||||
if not hedge_2_to_3:
|
||||
return False
|
||||
|
||||
# We can get to Hedge 3 from Hedge 2. If we can get from Keep to Hedge 2, we're good.
|
||||
# This covers both Hedge 1 Exit and Hedge 2 Shortcut, because Hedge 1 is just part of the Keep region.
|
||||
|
||||
hedge_2_from_keep = any(
|
||||
e.can_reach(state) for e in player_regions.two_way_entrance_register["Keep 2nd Maze", "Keep"]
|
||||
)
|
||||
|
||||
return hedge_2_from_keep
|
||||
|
||||
|
||||
def _can_do_theater_to_tunnels(state: CollectionState, world: "WitnessWorld") -> bool:
|
||||
"""
|
||||
To do Tunnels Theater Flowers EP, you need to quickly move from Theater to Tunnels.
|
||||
This condition is a little tricky. We'll attempt to evaluate it as lazily as possible.
|
||||
"""
|
||||
|
||||
# Checking for access to Theater is not necessary, as solvability of Tutorial Video is checked in the other half
|
||||
# of the Theater Flowers EP condition.
|
||||
|
||||
player_regions = world.player_regions
|
||||
|
||||
direct_access = (
|
||||
_can_move_either_direction(state, "Tunnels", "Windmill Interior", world.player_regions)
|
||||
and _can_move_either_direction(state, "Theater", "Windmill Interior", world.player_regions)
|
||||
any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Windmill Interior"])
|
||||
and any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Theater", "Windmill Interior"])
|
||||
)
|
||||
|
||||
theater_from_town = (
|
||||
_can_move_either_direction(state, "Town", "Windmill Interior", world.player_regions)
|
||||
and _can_move_either_direction(state, "Theater", "Windmill Interior", world.player_regions)
|
||||
or _can_move_either_direction(state, "Town", "Theater", world.player_regions)
|
||||
)
|
||||
if direct_access:
|
||||
return True
|
||||
|
||||
# We don't have direct access through the shortest path.
|
||||
# This means we somehow need to exit Theater to the Main Island, and then enter Tunnels from the Main Island.
|
||||
# Getting to Tunnels through Mountain -> Caves -> Tunnels is way too slow, so we only expect paths through Town.
|
||||
|
||||
# We need a way from Theater to Town. This is actually guaranteed, otherwise we wouldn't be in Theater.
|
||||
# The only ways to Theater are through Town and Tunnels. We just checked the Tunnels way.
|
||||
# This might need to be changed when warps are implemented.
|
||||
|
||||
# We also need a way from Town to Tunnels.
|
||||
|
||||
tunnels_from_town = (
|
||||
_can_move_either_direction(state, "Tunnels", "Windmill Interior", world.player_regions)
|
||||
and _can_move_either_direction(state, "Town", "Windmill Interior", world.player_regions)
|
||||
or _can_move_either_direction(state, "Tunnels", "Town", world.player_regions)
|
||||
any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Windmill Interior"])
|
||||
and any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Town", "Windmill Interior"])
|
||||
or any(e.can_reach(state) for e in player_regions.two_way_entrance_register["Tunnels", "Town"])
|
||||
)
|
||||
|
||||
return direct_access or theater_from_town and tunnels_from_town
|
||||
return tunnels_from_town
|
||||
|
||||
|
||||
def _has_item(item: str, world: "WitnessWorld", player: int,
|
||||
player_logic: WitnessPlayerLogic, player_locations: WitnessPlayerLocations) -> CollectionRule:
|
||||
if item in player_logic.REFERENCE_LOGIC.ALL_REGIONS_BY_NAME:
|
||||
return lambda state: state.can_reach(item, "Region", player)
|
||||
region = world.get_region(item)
|
||||
return region.can_reach
|
||||
if item == "7 Lasers":
|
||||
laser_req = world.options.mountain_lasers.value
|
||||
return _has_lasers(laser_req, world, False)
|
||||
@@ -181,8 +248,7 @@ def _has_item(item: str, world: "WitnessWorld", player: int,
|
||||
return lambda state: state.has(prog_item, player, player_logic.MULTI_AMOUNTS[item])
|
||||
|
||||
|
||||
def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]],
|
||||
world: "WitnessWorld") -> CollectionRule:
|
||||
def _meets_item_requirements(requirements: WitnessRule, world: "WitnessWorld") -> CollectionRule:
|
||||
"""
|
||||
Checks whether item and panel requirements are met for
|
||||
a panel
|
||||
|
||||
Reference in New Issue
Block a user