Archipelago/worlds/witness/rules.py

172 lines
6.4 KiB
Python

"""
Defines the rules by which locations can be accessed,
depending on the items received
"""
# pylint: disable=E1101
from BaseClasses import MultiWorld
from .player_logic import WitnessPlayerLogic
from .Options import is_option_enabled
from .locations import WitnessPlayerLocations
from . import StaticWitnessLogic
from ..AutoWorld import LogicMixin
from ..generic.Rules import set_rule
class WitnessLogic(LogicMixin):
"""
Logic macros that get reused
"""
def _witness_has_lasers(self, world, player: int, amount: int) -> bool:
lasers = 0
lasers += int(self.has("Symmetry Laser Activation", player))
lasers += int(self.has("Desert Laser Activation", player)
and self.has("Desert Laser Redirection", player))
lasers += int(self.has("Town Laser Activation", player))
lasers += int(self.has("Monastery Laser Activation", player))
lasers += int(self.has("Keep Laser Pressure Plates Activation", player) and (
is_option_enabled(world, player, "disable_non_randomized_puzzles")
or self.has("Keep Laser Hedges Activation", player)
))
lasers += int(self.has("Quarry Laser Activation", player))
lasers += int(self.has("Treehouse Laser Activation", player))
lasers += int(self.has("Jungle Laser Activation", player))
lasers += int(self.has("Bunker Laser Activation", player))
lasers += int(self.has("Swamp Laser Activation", player))
lasers += int(self.has("Shadows Laser Activation", player))
return lasers >= amount
def _witness_can_solve_panel(self, panel, world, player, player_logic: WitnessPlayerLogic, locat):
"""
Determines whether a panel can be solved
"""
panel_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel]
check_name = panel_obj["checkName"]
if (check_name + " Solved" in locat.EVENT_LOCATION_TABLE
and not self.has(player_logic.EVENT_ITEM_PAIRS[check_name + " Solved"], player)):
return False
if panel not in player_logic.ORIGINAL_EVENT_PANELS and not self.can_reach(check_name, "Location", player):
return False
if (panel in player_logic.ORIGINAL_EVENT_PANELS
and check_name + " Solved" not in locat.EVENT_LOCATION_TABLE
and not self._witness_safe_manual_panel_check(panel, world, player, player_logic, locat)):
return False
return True
def _witness_meets_item_requirements(self, panel, world, player, player_logic: WitnessPlayerLogic, locat):
"""
Checks whether item and panel requirements are met for
a panel
"""
panel_req = player_logic.REQUIREMENTS_BY_HEX[panel]
for option in panel_req:
if len(option) == 0:
return True
valid_option = True
for item in option:
if item == "7 Lasers":
if not self._witness_has_lasers(world, player, 7):
valid_option = False
break
elif item == "11 Lasers":
if not self._witness_has_lasers(world, player, 11):
valid_option = False
break
elif item in player_logic.NECESSARY_EVENT_PANELS:
if StaticWitnessLogic.CHECKS_BY_HEX[item]["checkName"] + " Solved" in locat.EVENT_LOCATION_TABLE:
valid_option = self.has(player_logic.EVENT_ITEM_NAMES[item], player)
else:
valid_option = self.can_reach(
StaticWitnessLogic.CHECKS_BY_HEX[item]["checkName"], "Location", player
)
if not valid_option:
break
elif not self.has(item, player):
valid_option = False
break
if valid_option:
return True
return False
def _witness_safe_manual_panel_check(self, panel, world, player, player_logic: WitnessPlayerLogic, locat):
"""
nested can_reach can cause problems, but only if the region being
checked is neither of the two original regions from the first
can_reach.
A nested can_reach is okay here because the only panels this
function is called on are panels that exist on either side of all
connections they are required for.
The spoiler log looks so much nicer this way,
it gets rid of a bunch of event items, only leaving a couple. :)
"""
region = StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"]
return (
self._witness_meets_item_requirements(panel, world, player, player_logic, locat)
and self.can_reach(region, "Region", player)
)
def _witness_can_solve_panels(self, panel_hex_to_solve_set, world, player, player_logic: WitnessPlayerLogic, locat):
"""
Checks whether a set of panels can be solved.
"""
for option in panel_hex_to_solve_set:
if len(option) == 0:
return True
valid_option = True
for panel in option:
if not self._witness_can_solve_panel(panel, world, player, player_logic, locat):
valid_option = False
break
if valid_option:
return True
return False
def make_lambda(check_hex, world, player, player_logic, locat):
"""
Lambdas are created in a for loop so values need to be captured
"""
return lambda state: state._witness_meets_item_requirements(
check_hex, world, player, player_logic, locat
)
def set_rules(world: MultiWorld, player: int, player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations):
"""
Sets all rules for all locations
"""
for location in locat.CHECK_LOCATION_TABLE:
real_location = location
if location in locat.EVENT_LOCATION_TABLE:
real_location = location[:-7]
panel = StaticWitnessLogic.CHECKS_BY_NAME[real_location]
check_hex = panel["checkHex"]
rule = make_lambda(check_hex, world, player, player_logic, locat)
set_rule(world.get_location(location, player), rule)
world.completion_condition[player] = \
lambda state: state.has('Victory', player)