mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Witness: Big™ new™ content update™ (#2114)
Co-authored-by: blastron <blastron@mac.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
@@ -3,223 +3,218 @@ Defines the rules by which locations can be accessed,
|
||||
depending on the items received
|
||||
"""
|
||||
|
||||
# pylint: disable=E1101
|
||||
from typing import TYPE_CHECKING, Callable, FrozenSet
|
||||
|
||||
from BaseClasses import MultiWorld
|
||||
from BaseClasses import CollectionState
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
from .Options import is_option_enabled, get_option_value
|
||||
from .locations import WitnessPlayerLocations
|
||||
from . import StaticWitnessLogic
|
||||
from worlds.AutoWorld import LogicMixin
|
||||
from . import StaticWitnessLogic, WitnessRegions
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import WitnessWorld
|
||||
|
||||
class WitnessLogic(LogicMixin):
|
||||
laser_hexes = [
|
||||
"0x028A4",
|
||||
"0x00274",
|
||||
"0x032F9",
|
||||
"0x01539",
|
||||
"0x181B3",
|
||||
"0x0C2B2",
|
||||
"0x00509",
|
||||
"0x00BF6",
|
||||
"0x014BB",
|
||||
"0x012FB",
|
||||
"0x17C65",
|
||||
]
|
||||
|
||||
|
||||
def _has_laser(laser_hex: str, world: "WitnessWorld", player: int) -> Callable[[CollectionState], bool]:
|
||||
if laser_hex == "0x012FB":
|
||||
return lambda state: (
|
||||
_can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)(state)
|
||||
and state.has("Desert Laser Redirection", player)
|
||||
)
|
||||
else:
|
||||
return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)
|
||||
|
||||
|
||||
def _has_lasers(amount: int, world: "WitnessWorld") -> Callable[[CollectionState], bool]:
|
||||
laser_lambdas = []
|
||||
|
||||
for laser_hex in laser_hexes:
|
||||
has_laser_lambda = _has_laser(laser_hex, world, world.player)
|
||||
|
||||
laser_lambdas.append(has_laser_lambda)
|
||||
|
||||
return lambda state: sum(laser_lambda(state) for laser_lambda in laser_lambdas) >= amount
|
||||
|
||||
|
||||
def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logic: WitnessPlayerLogic,
|
||||
locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]:
|
||||
"""
|
||||
Logic macros that get reused
|
||||
Determines whether a panel can be solved
|
||||
"""
|
||||
|
||||
def _witness_has_lasers(self, world, player: int, amount: int) -> bool:
|
||||
regular_lasers = not is_option_enabled(world, player, "shuffle_lasers")
|
||||
panel_obj = player_logic.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]
|
||||
entity_name = panel_obj["checkName"]
|
||||
|
||||
lasers = 0
|
||||
|
||||
place_names = [
|
||||
"Symmetry", "Desert", "Town", "Monastery", "Keep",
|
||||
"Quarry", "Treehouse", "Jungle", "Bunker", "Swamp", "Shadows"
|
||||
]
|
||||
|
||||
for place in place_names:
|
||||
has_laser = self.has(place + " Laser", player)
|
||||
|
||||
has_laser = has_laser or (regular_lasers and self.has(place + " Laser Activation", player))
|
||||
|
||||
if place == "Desert":
|
||||
has_laser = has_laser and self.has("Desert Laser Redirection", player)
|
||||
|
||||
lasers += int(has_laser)
|
||||
|
||||
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 (check_name + " Solved" not in locat.EVENT_LOCATION_TABLE
|
||||
and not self._witness_meets_item_requirements(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":
|
||||
laser_req = get_option_value(world, player, "mountain_lasers")
|
||||
|
||||
if not self._witness_has_lasers(world, player, laser_req):
|
||||
valid_option = False
|
||||
break
|
||||
elif item == "11 Lasers":
|
||||
laser_req = get_option_value(world, player, "challenge_lasers")
|
||||
|
||||
if not self._witness_has_lasers(world, player, laser_req):
|
||||
valid_option = False
|
||||
break
|
||||
elif item == "PP2 Weirdness":
|
||||
hedge_2_access = (
|
||||
self.can_reach("Keep 2nd Maze to Keep", "Entrance", player)
|
||||
or self.can_reach("Keep to Keep 2nd Maze", "Entrance", player)
|
||||
)
|
||||
|
||||
hedge_3_access = (
|
||||
self.can_reach("Keep 3rd Maze to Keep", "Entrance", player)
|
||||
or self.can_reach("Keep 2nd Maze to Keep 3rd Maze", "Entrance", player)
|
||||
and hedge_2_access
|
||||
)
|
||||
|
||||
hedge_4_access = (
|
||||
self.can_reach("Keep 4th Maze to Keep", "Entrance", player)
|
||||
or self.can_reach("Keep 3rd Maze to Keep 4th Maze", "Entrance", player)
|
||||
and hedge_3_access
|
||||
)
|
||||
|
||||
hedge_access = (
|
||||
self.can_reach("Keep 4th Maze to Keep Tower", "Entrance", player)
|
||||
and self.can_reach("Keep", "Region", player)
|
||||
and hedge_4_access
|
||||
)
|
||||
|
||||
backwards_to_fourth = (
|
||||
self.can_reach("Keep", "Region", player)
|
||||
and self.can_reach("Keep 4th Pressure Plate to Keep Tower", "Entrance", player)
|
||||
and (
|
||||
self.can_reach("Keep Tower to Keep", "Entrance", player)
|
||||
or hedge_access
|
||||
)
|
||||
)
|
||||
|
||||
shadows_shortcut = (
|
||||
self.can_reach("Main Island", "Region", player)
|
||||
and self.can_reach("Keep 4th Pressure Plate to Shadows", "Entrance", player)
|
||||
)
|
||||
|
||||
backwards_access = (
|
||||
self.can_reach("Keep 3rd Pressure Plate to Keep 4th Pressure Plate", "Entrance", player)
|
||||
and (backwards_to_fourth or shadows_shortcut)
|
||||
)
|
||||
|
||||
front_access = (
|
||||
self.can_reach("Keep to Keep 2nd Pressure Plate", 'Entrance', player)
|
||||
and self.can_reach("Keep", "Region", player)
|
||||
)
|
||||
|
||||
if not (front_access and backwards_access):
|
||||
valid_option = False
|
||||
break
|
||||
elif item == "Theater to Tunnels":
|
||||
direct_access = (
|
||||
self.can_reach("Tunnels to Windmill Interior", "Entrance", player)
|
||||
and self.can_reach("Windmill Interior to Theater", "Entrance", player)
|
||||
)
|
||||
|
||||
theater_from_town = (
|
||||
self.can_reach("Town to Windmill Interior", "Entrance", player)
|
||||
and self.can_reach("Windmill Interior to Theater", "Entrance", player)
|
||||
or self.can_reach("Theater to Town", "Entrance", player)
|
||||
)
|
||||
|
||||
tunnels_from_town = (
|
||||
self.can_reach("Tunnels to Windmill Interior", "Entrance", player)
|
||||
and self.can_reach("Town to Windmill Interior", "Entrance", player)
|
||||
or self.can_reach("Tunnels to Town", "Entrance", player)
|
||||
)
|
||||
|
||||
if not (direct_access or theater_from_town and tunnels_from_town):
|
||||
valid_option = False
|
||||
break
|
||||
elif item in player_logic.EVENT_PANELS:
|
||||
if not self._witness_can_solve_panel(item, world, player, player_logic, locat):
|
||||
valid_option = False
|
||||
break
|
||||
elif not self.has(item, player):
|
||||
# The player doesn't have the item. Check to see if it's part of a progressive item and, if so, the
|
||||
# player has enough of that.
|
||||
prog_item = StaticWitnessLogic.get_parent_progressive_item(item)
|
||||
if prog_item is item or not self.has(prog_item, player, player_logic.MULTI_AMOUNTS[item]):
|
||||
valid_option = False
|
||||
break
|
||||
|
||||
if valid_option:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
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
|
||||
if entity_name + " Solved" in locat.EVENT_LOCATION_TABLE:
|
||||
return lambda state: state.has(player_logic.EVENT_ITEM_PAIRS[entity_name + " Solved"], player)
|
||||
else:
|
||||
return make_lambda(panel, world)
|
||||
|
||||
|
||||
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 _can_move_either_direction(state: CollectionState, source: str, target: str, regio: WitnessRegions) -> bool:
|
||||
entrance_forward = regio.created_entrances[(source, target)]
|
||||
entrance_backward = regio.created_entrances[(source, target)]
|
||||
|
||||
return (
|
||||
any(entrance.can_reach(state) for entrance in entrance_forward)
|
||||
or
|
||||
any(entrance.can_reach(state) for entrance in entrance_backward)
|
||||
)
|
||||
|
||||
|
||||
def set_rules(world: MultiWorld, player: int, player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations):
|
||||
def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool:
|
||||
player = world.player
|
||||
|
||||
hedge_2_access = (
|
||||
_can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.regio)
|
||||
)
|
||||
|
||||
hedge_3_access = (
|
||||
_can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.regio)
|
||||
or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.regio)
|
||||
and hedge_2_access
|
||||
)
|
||||
|
||||
hedge_4_access = (
|
||||
_can_move_either_direction(state, "Keep 4th Maze", "Keep", world.regio)
|
||||
or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.regio)
|
||||
and hedge_3_access
|
||||
)
|
||||
|
||||
hedge_access = (
|
||||
_can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.regio)
|
||||
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.regio)
|
||||
and (
|
||||
_can_move_either_direction(state, "Keep", "Keep Tower", world.regio)
|
||||
or hedge_access
|
||||
)
|
||||
)
|
||||
|
||||
shadows_shortcut = (
|
||||
state.can_reach("Main Island", "Region", player)
|
||||
and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.regio)
|
||||
)
|
||||
|
||||
backwards_access = (
|
||||
_can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.regio)
|
||||
and (backwards_to_fourth or shadows_shortcut)
|
||||
)
|
||||
|
||||
front_access = (
|
||||
_can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.regio)
|
||||
and state.can_reach("Keep", "Region", player)
|
||||
)
|
||||
|
||||
return front_access and backwards_access
|
||||
|
||||
|
||||
def _can_do_theater_to_tunnels(state: CollectionState, world: "WitnessWorld") -> bool:
|
||||
direct_access = (
|
||||
_can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio)
|
||||
and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio)
|
||||
)
|
||||
|
||||
theater_from_town = (
|
||||
_can_move_either_direction(state, "Town", "Windmill Interior", world.regio)
|
||||
and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio)
|
||||
or _can_move_either_direction(state, "Town", "Theater", world.regio)
|
||||
)
|
||||
|
||||
tunnels_from_town = (
|
||||
_can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio)
|
||||
and _can_move_either_direction(state, "Town", "Windmill Interior", world.regio)
|
||||
or _can_move_either_direction(state, "Tunnels", "Town", world.regio)
|
||||
)
|
||||
|
||||
return direct_access or theater_from_town and tunnels_from_town
|
||||
|
||||
|
||||
def _has_item(item: str, world: "WitnessWorld", player: int,
|
||||
player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]:
|
||||
if item in player_logic.REFERENCE_LOGIC.ALL_REGIONS_BY_NAME:
|
||||
return lambda state: state.can_reach(item, "Region", player)
|
||||
if item == "7 Lasers":
|
||||
laser_req = world.options.mountain_lasers.value
|
||||
return _has_lasers(laser_req, world)
|
||||
if item == "11 Lasers":
|
||||
laser_req = world.options.challenge_lasers.value
|
||||
return _has_lasers(laser_req, world)
|
||||
elif item == "PP2 Weirdness":
|
||||
return lambda state: _can_do_expert_pp2(state, world)
|
||||
elif item == "Theater to Tunnels":
|
||||
return lambda state: _can_do_theater_to_tunnels(state, world)
|
||||
if item in player_logic.EVENT_PANELS:
|
||||
return _can_solve_panel(item, world, player, player_logic, locat)
|
||||
|
||||
prog_item = StaticWitnessLogic.get_parent_progressive_item(item)
|
||||
return lambda state: state.has(prog_item, player, player_logic.MULTI_AMOUNTS[item])
|
||||
|
||||
|
||||
def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]],
|
||||
world: "WitnessWorld") -> Callable[[CollectionState], bool]:
|
||||
"""
|
||||
Checks whether item and panel requirements are met for
|
||||
a panel
|
||||
"""
|
||||
|
||||
lambda_conversion = [
|
||||
[_has_item(item, world, world.player, world.player_logic, world.locat) for item in subset]
|
||||
for subset in requirements
|
||||
]
|
||||
|
||||
return lambda state: any(
|
||||
all(condition(state) for condition in sub_requirement)
|
||||
for sub_requirement in lambda_conversion
|
||||
)
|
||||
|
||||
|
||||
def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionState], bool]:
|
||||
"""
|
||||
Lambdas are created in a for loop so values need to be captured
|
||||
"""
|
||||
entity_req = world.player_logic.REQUIREMENTS_BY_HEX[entity_hex]
|
||||
|
||||
return _meets_item_requirements(entity_req, world)
|
||||
|
||||
|
||||
def set_rules(world: "WitnessWorld"):
|
||||
"""
|
||||
Sets all rules for all locations
|
||||
"""
|
||||
|
||||
for location in locat.CHECK_LOCATION_TABLE:
|
||||
for location in world.locat.CHECK_LOCATION_TABLE:
|
||||
real_location = location
|
||||
|
||||
if location in locat.EVENT_LOCATION_TABLE:
|
||||
if location in world.locat.EVENT_LOCATION_TABLE:
|
||||
real_location = location[:-7]
|
||||
|
||||
panel = StaticWitnessLogic.CHECKS_BY_NAME[real_location]
|
||||
check_hex = panel["checkHex"]
|
||||
associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location]
|
||||
entity_hex = associated_entity["entity_hex"]
|
||||
|
||||
rule = make_lambda(check_hex, world, player, player_logic, locat)
|
||||
rule = make_lambda(entity_hex, world)
|
||||
|
||||
set_rule(world.get_location(location, player), rule)
|
||||
location = world.multiworld.get_location(location, world.player)
|
||||
|
||||
world.completion_condition[player] = \
|
||||
lambda state: state.has('Victory', player)
|
||||
set_rule(location, rule)
|
||||
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has('Victory', world.player)
|
||||
|
||||
Reference in New Issue
Block a user