from typing import Dict, List, Tuple, Any, Callable, TYPE_CHECKING from BaseClasses import CollectionState if TYPE_CHECKING: from . import BlasphemousWorld else: BlasphemousWorld = object class BlasRules: player: int world: BlasphemousWorld string_rules: Dict[str, Callable[[CollectionState], bool]] def __init__(self, world: "BlasphemousWorld") -> None: self.player = world.player self.world = world self.multiworld = world.multiworld self.indirect_conditions: List[Tuple[str, str]] = [] # BrandenEK/Blasphemous.Randomizer/ItemRando/BlasphemousInventory.cs self.string_rules = { # Visibility flags "DoubleJump": lambda state: bool(self.world.options.purified_hand), "NormalLogic": lambda state: self.world.options.difficulty >= 1, "NormalLogicAndDoubleJump": lambda state: self.world.options.difficulty >= 1 \ and bool(self.world.options.purified_hand), "HardLogic": lambda state: self.world.options.difficulty >= 2, "HardLogicAndDoubleJump": lambda state: self.world.options.difficulty >= 2 \ and bool(self.world.options.purified_hand), "EnemySkips": self.enemy_skips_allowed, "EnemySkipsAndDoubleJump": lambda state: self.enemy_skips_allowed(state) \ and bool(self.world.options.purified_hand), # Relics "blood": self.blood, # skip "root" "linen": self.linen, "nail": self.nail, "shroud": self.shroud, # skip "lung" # Keys "bronzeKey": self.bronze_key, "silverKey": self.silver_key, "goldKey": self.gold_key, "peaksKey": self.peaks_key, "elderKey": self.elder_key, "woodKey": self.wood_key, # Collections "cherubs20": lambda state: self.cherubs(state) >= 20, "cherubs38": lambda state: self.cherubs(state) >= 38, "bones4": lambda state: self.bones(state) >= 4, "bones8": lambda state: self.bones(state) >= 8, "bones12": lambda state: self.bones(state) >= 12, "bones16": lambda state: self.bones(state) >= 16, "bones20": lambda state: self.bones(state) >= 20, "bones24": lambda state: self.bones(state) >= 24, "bones28": lambda state: self.bones(state) >= 28, "bones30": lambda state: self.bones(state) >= 30, "bones32": lambda state: self.bones(state) >= 32, "bones36": lambda state: self.bones(state) >= 36, "bones40": lambda state: self.bones(state) >= 40, "bones44": lambda state: self.bones(state) >= 44, "tears0": lambda state: True, # Special items "dash": self.dash, "wallClimb": self.wall_climb, # skip "airImpulse" "boots": self.boots, "doubleJump": self.double_jump, # Speed boosts "wheel": self.wheel, # skip "dawnHeart" # Health boosts # skip "flasks" # skip "quicksilver" # Puzzles "redWax1": lambda state: self.red_wax(state) >= 1, "redWax3": lambda state: self.red_wax(state) >= 3, "blueWax1": lambda state: self.blue_wax(state) >= 1, "blueWax3": lambda state: self.blue_wax(state) >= 3, "chalice": self.chalice, # Cherubs "debla": self.debla, "lorquiana": self.lorquiana, "zarabanda": self.zarabanda, "taranto": self.taranto, "verdiales": self.verdiales, "cante": self.cante, "cantina": self.cantina, "aubade": self.aubade, "tirana": self.tirana, "ruby": self.ruby, "tiento": self.tiento, # skip "anyPrayer" "pillar": self.pillar, # Stats # skip "healthLevel" # skip "fervourLevel" # skip "swordLevel" # Skills # skip "combo" # skip "charged" # skip "ranged" # skip "dive" # skip "lunge" "chargeBeam": self.charge_beam, "rangedAttack": lambda state: self.ranged(state) > 0, # Main quest "holyWounds3": lambda state: self.holy_wounds(state) >= 3, "masks1": lambda state: self.masks(state) >= 1, "masks2": lambda state: self.masks(state) >= 2, "masks3": lambda state: self.masks(state) >= 3, "guiltBead": self.guilt_bead, # LOTL quest "cloth": self.cloth, "hand": self.hand, "hatchedEgg": self.hatched_egg, # Tirso quest "herbs1": lambda state: self.herbs(state) >= 1, "herbs2": lambda state: self.herbs(state) >= 2, "herbs3": lambda state: self.herbs(state) >= 3, "herbs4": lambda state: self.herbs(state) >= 4, "herbs5": lambda state: self.herbs(state) >= 5, "herbs6": lambda state: self.herbs(state) >= 6, # Tentudia quest "tentudiaRemains1": lambda state: self.tentudia_remains(state) >= 1, "tentudiaRemains2": lambda state: self.tentudia_remains(state) >= 2, "tentudiaRemains3": lambda state: self.tentudia_remains(state) >= 3, # Gemino quest "emptyThimble": self.empty_thimble, "fullThimble": self.full_thimble, "driedFlowers": self.dried_flowers, # Altasgracias quest "ceremonyItems3": lambda state: self.ceremony_items(state) >= 3, "egg": self.egg, # Redento quest # skip "limestones", not actually used # skip "knots", not actually used # Cleofas quest "marksOfRefuge3": lambda state: self.marks_of_refuge(state) >= 3, "cord": self.cord, # Crisanta quest "scapular": self.scapular, "trueHeart": self.true_heart, "traitorEyes2": lambda state: self.traitor_eyes(state) >= 2, # Jibrael quest "bell": self.bell, "verses4": lambda state: self.verses(state) >= 4, # Movement tech "canAirStall": self.can_air_stall, "canDawnJump": self.can_dawn_jump, "canWaterJump": self.can_water_jump, # Breakable tech "canBreakHoles": self.can_break_holes, "canDiveLaser": self.can_dive_laser, # Root tech "canWalkOnRoot": self.can_walk_on_root, "canClimbOnRoot": self.can_climb_on_root, # Lung tech "canSurvivePoison1": self.can_survive_poison_1, "canSurvivePoison2": self.can_survive_poison_2, "canSurvivePoison3": self.can_survive_poison_3, # Enemy tech "canEnemyBounce": self.can_enemy_bounce, "canEnemyUpslash": self.can_enemy_upslash, # Reaching rooms "guiltRooms1": lambda state: self.guilt_rooms(state) >= 1, "guiltRooms2": lambda state: self.guilt_rooms(state) >= 2, "guiltRooms3": lambda state: self.guilt_rooms(state) >= 3, "guiltRooms4": lambda state: self.guilt_rooms(state) >= 4, "guiltRooms5": lambda state: self.guilt_rooms(state) >= 5, "guiltRooms6": lambda state: self.guilt_rooms(state) >= 6, "guiltRooms7": lambda state: self.guilt_rooms(state) >= 7, "swordRooms1": lambda state: self.sword_rooms(state) >= 1, "swordRooms2": lambda state: self.sword_rooms(state) >= 2, "swordRooms3": lambda state: self.sword_rooms(state) >= 3, "swordRooms4": lambda state: self.sword_rooms(state) >= 4, "swordRooms5": lambda state: self.sword_rooms(state) >= 5, "swordRooms6": lambda state: self.sword_rooms(state) >= 6, "swordRooms7": lambda state: self.sword_rooms(state) >= 7, "redentoRooms2": lambda state: self.redento_rooms(state) >= 2, "redentoRooms3": lambda state: self.redento_rooms(state) >= 3, "redentoRooms4": lambda state: self.redento_rooms(state) >= 4, "redentoRooms5": lambda state: self.redento_rooms(state) >= 5, "miriamRooms5": lambda state: self.miriam_rooms(state) >= 5, "amanecidaRooms1": lambda state: self.amanecida_rooms(state) >= 1, "amanecidaRooms2": lambda state: self.amanecida_rooms(state) >= 2, "amanecidaRooms3": lambda state: self.amanecida_rooms(state) >= 3, "amanecidaRooms4": lambda state: self.amanecida_rooms(state) >= 4, "chaliceRooms3": lambda state: self.chalice_rooms(state) >= 3, # Crossing gaps "canCrossGap1": self.can_cross_gap_1, "canCrossGap2": self.can_cross_gap_2, "canCrossGap3": self.can_cross_gap_3, "canCrossGap4": self.can_cross_gap_4, "canCrossGap5": self.can_cross_gap_5, "canCrossGap6": self.can_cross_gap_6, "canCrossGap7": self.can_cross_gap_7, "canCrossGap8": self.can_cross_gap_8, "canCrossGap9": self.can_cross_gap_9, "canCrossGap10": self.can_cross_gap_10, "canCrossGap11": self.can_cross_gap_11, # Events in different scenes "openedDCGateW": self.opened_dc_gate_w, "openedDCGateE": self.opened_dc_gate_e, "openedDCLadder": self.opened_dc_ladder, "openedWOTWCave": self.opened_wotw_cave, "rodeGotPElevator": self.rode_gotp_elevator, "openedConventLadder": self.opened_convent_ladder, "brokeJondoBellW": self.broke_jondo_bell_w, "brokeJondoBellE": self.broke_jondo_bell_e, "openedMoMLadder": self.opened_mom_ladder, "openedTSCGate": self.opened_tsc_gate, "openedARLadder": self.opened_ar_ladder, "brokeBotTCStatue": self.broke_bottc_statue, "openedWotHPGate": self.opened_wothp_gate, "openedBotSSLadder": self.opened_botss_ladder, # Special skips "upwarpSkipsAllowed": self.upwarp_skips_allowed, "mourningSkipAllowed": self.mourning_skip_allowed, "enemySkipsAllowed": self.enemy_skips_allowed, "obscureSkipsAllowed": self.obscure_skips_allowed, "preciseSkipsAllowed": self.precise_skips_allowed, # Bosses "canBeatBrotherhoodBoss": self.can_beat_brotherhood_boss, "canBeatMercyBoss": self.can_beat_mercy_boss, "canBeatConventBoss": self.can_beat_convent_boss, "canBeatGrievanceBoss": self.can_beat_grievance_boss, "canBeatBridgeBoss": self.can_beat_bridge_boss, "canBeatMothersBoss": self.can_beat_mothers_boss, "canBeatCanvasesBoss": self.can_beat_canvases_boss, "canBeatPrisonBoss": self.can_beat_prison_boss, "canBeatRooftopsBoss": self.can_beat_rooftops_boss, "canBeatOssuaryBoss": self.can_beat_ossuary_boss, "canBeatMourningBoss": self.can_beat_mourning_boss, "canBeatGraveyardBoss": self.can_beat_graveyard_boss, "canBeatJondoBoss": self.can_beat_jondo_boss, "canBeatPatioBoss": self.can_beat_patio_boss, "canBeatWallBoss": self.can_beat_wall_boss, "canBeatHallBoss": self.can_beat_hall_boss, "canBeatPerpetua": self.can_beat_perpetua, "canBeatLegionary": self.can_beat_legionary } boss_strength_indirect_regions: List[str] = [ # flasks "D01Z05S05[SW]", "D02Z02S04[W]", "D03Z02S08[W]", "D03Z03S04[SW]", "D04Z02S13[W]", "D05Z01S08[NW]", "D20Z01S07[NE]", # quicksilver "D01Z05S01[W]" ] guilt_indirect_regions: List[str] = [ "D01Z04S01[NE]", "D02Z02S11[W]", "D03Z03S02[NE]", "D04Z02S02[SE]", "D05Z01S05[NE]", "D09Z01S05[W]", "D17Z01S04[W]" ] sword_indirect_regions: List[str] = [ "D01Z02S07[E]", "D01Z02S02[SW]", "D20Z01S04[E]", "D01Z05S23[W]", "D02Z03S02[NE]", "D04Z02S21[NE]", "D05Z01S21[NW]", "D06Z01S15[NE]", "D17Z01S07[SW]" ] redento_indirect_regions: List[str] = [ "D03Z01S04[E]", "D03Z02S10[N]", "D17Z01S05[S]", "D17BZ02S01[FrontR]", "D01Z03S04[E]", "D08Z01S01[W]", "D04Z01S03[E]", "D04Z02S01[W]", "D06Z01S18[-Cherubs]", "D04Z02S08[E]", "D04BZ02S01[Redento]", "D17Z01S07[NW]" ] miriam_indirect_regions: List[str] = [ "D02Z03S07[NWW]", "D03Z03S07[NW]", "D04Z04S01[E]", "D05Z01S06[W]", "D06Z01S17[E]" ] chalice_indirect_regions: List[str] = [ "D03Z01S02[E]", "D01Z05S02[W]", "D20Z01S03[N]", "D05Z01S11[SE]", "D05Z02S02[NW]", "D09Z01S09[E]", "D09Z01S10[W]", "D09Z01S08[SE]", "D09Z01S02[SW]" ] self.indirect_regions: Dict[str, List[str]] = { "openedDCGateW": ["D20Z01S04[E]", "D01Z05S23[W]"], "openedDCGateE": ["D01Z05S10[SE]", "D01Z04S09[W]"], "openedDCLadder": ["D01Z05S25[NE]", "D01Z05S02[S]"], "openedWOTWCave": ["D02Z01S01[SW]", "D02Z01S08[E]", "D02Z01S02[]"], "rodeGotPElevator": ["D02Z03S14[E]", "D02Z02S13[W]", "D02Z02S06[E]", "D02Z02S12[W]", "D02Z02S08[W]"], "openedConventLadder": ["D02Z03S02[N]", "D02Z03S15[E]", "D02Z03S19[E]", "D02Z03S10[W]", "D02Z03S22[W]"], "brokeJondoBellW": ["D03Z02S08[N]", "D03Z02S12[E]", "D03Z02S10[S]", "D03Z02S10[-Cherubs]"], "brokeJondoBellE": ["D03Z02S04[NE]", "D03Z02S11[W]", "D03Z02S03[E]"], "openedMoMLadder": ["D04Z02S11[E]", "D04Z02S09[W]", "D06Z01S23[S]", "D04Z02S04[N]"], "openedTSCGate": ["D05Z02S06[SE]", "D05Z01S21[-Cherubs]"], "openedARLadder": ["D06Z01S22[Sword]", "D06Z01S20[W]", "D04Z02S06[N]", "D06Z01S01[-Cherubs]"], "brokeBotTCStatue": ["D08Z03S03[W]", "D08Z02S03[W]"], "openedWotHPGate": ["D09Z01S13[E]", "D09Z01S03[W]", "D09Z01S08[W]"], "openedBotSSLadder": ["D17Z01S05[S]", "D17BZ02S01[FrontR]"], "canBeatBrotherhoodBoss": [*boss_strength_indirect_regions, "D17Z01S05[E]", "D17Z01S03[W]"], "canBeatMercyBoss": [*boss_strength_indirect_regions, "D01Z04S19[E]", "D01Z04S12[W]"], "canBeatConventBoss": [*boss_strength_indirect_regions, "D02Z03S09[E]", "D02Z03S21[W]"], "canBeatGrievanceBoss": [*boss_strength_indirect_regions, "D03Z03S11[E]", "D03Z03S16[W]"], "canBeatBridgeBoss": [*boss_strength_indirect_regions, "D01Z03S06[E]", "D08Z02S01[W]"], "canBeatMothersBoss": [*boss_strength_indirect_regions, "D04Z02S15[E]", "D04Z02S21[W]"], "canBeatCanvasesBoss": [*boss_strength_indirect_regions, "D05Z02S06[NE]", "D05Z01S21[SW]"], "canBeatPrisonBoss": [*boss_strength_indirect_regions, "D09Z01S05[SE]", "D09Z01S08[S]"], "canBeatRooftopsBoss": [*boss_strength_indirect_regions, "D06Z01S19[E]", "D07Z01S01[W]"], "canBeatOssuaryBoss": [*boss_strength_indirect_regions, "D01BZ06S01[E]"], "canBeatMourningBoss": [*boss_strength_indirect_regions, "D20Z02S07[W]"], "canBeatGraveyardBoss": [*boss_strength_indirect_regions, "D01Z06S01[Santos]", "D02Z03S18[NW]", "D02Z02S03[NE]"], "canBeatJondoBoss": [*boss_strength_indirect_regions, "D01Z06S01[Santos]", "D20Z01S06[NE]", "D20Z01S04[W]", "D03Z01S04[E]", "D03Z02S10[N]"], "canBeatPatioBoss": [*boss_strength_indirect_regions, "D01Z06S01[Santos]", "D06Z01S02[W]", "D04Z01S03[E]", "D04Z01S01[W]", "D06Z01S18[-Cherubs]"], "canBeatWallBoss": [*boss_strength_indirect_regions, "D01Z06S01[Santos]", "D09Z01S09[Cell24]", "D09Z01S11[E]", "D06Z01S13[W]"], "canBeatHallBoss": [*boss_strength_indirect_regions, "D08Z01S02[NE]", "D08Z03S02[NW]"], "canBeatPerpetua": boss_strength_indirect_regions, "canBeatLegionary": boss_strength_indirect_regions, "guiltRooms1": guilt_indirect_regions, "guiltRooms2": guilt_indirect_regions, "guiltRooms3": guilt_indirect_regions, "guiltRooms4": guilt_indirect_regions, "guiltRooms5": guilt_indirect_regions, "guiltRooms6": guilt_indirect_regions, "guiltRooms7": guilt_indirect_regions, "swordRooms1": sword_indirect_regions, "swordRooms2": sword_indirect_regions, "swordRooms3": sword_indirect_regions, "swordRooms4": sword_indirect_regions, "swordRooms5": sword_indirect_regions, "swordRooms6": sword_indirect_regions, "swordRooms7": sword_indirect_regions, "redentoRooms2": redento_indirect_regions, "redentoRooms3": redento_indirect_regions, "redentoRooms4": redento_indirect_regions, "redentoRooms5": redento_indirect_regions, "miriamRooms5": miriam_indirect_regions, "chaliceRooms3": chalice_indirect_regions } self.indirect_regions["amanecidaRooms1"] = [*self.indirect_regions["canBeatGraveyardBoss"], *self.indirect_regions["canBeatJondoBoss"], *self.indirect_regions["canBeatPatioBoss"], *self.indirect_regions["canBeatWallBoss"]] self.indirect_regions["amanecidaRooms2"] = [*self.indirect_regions["canBeatGraveyardBoss"], *self.indirect_regions["canBeatJondoBoss"], *self.indirect_regions["canBeatPatioBoss"], *self.indirect_regions["canBeatWallBoss"]] self.indirect_regions["amanecidaRooms3"] = [*self.indirect_regions["canBeatGraveyardBoss"], *self.indirect_regions["canBeatJondoBoss"], *self.indirect_regions["canBeatPatioBoss"], *self.indirect_regions["canBeatWallBoss"]] self.indirect_regions["amanecidaRooms4"] = [*self.indirect_regions["canBeatGraveyardBoss"], *self.indirect_regions["canBeatJondoBoss"], *self.indirect_regions["canBeatPatioBoss"], *self.indirect_regions["canBeatWallBoss"]] def req_is_region(self, string: str) -> bool: return (string[0] == "D" and string[3] == "Z" and string[6] == "S")\ or (string[0] == "D" and string[3] == "B" and string[4] == "Z" and string[7] == "S") def load_rule(self, obj_is_region: bool, name: str, obj: Dict[str, Any]) -> Callable[[CollectionState], bool]: clauses = [] for clause in obj["logic"]: reqs = [] for req in clause["item_requirements"]: if self.req_is_region(req): if obj_is_region: # add to indirect conditions if object and requirement are doors self.indirect_conditions.append((req, f"{name} -> {obj['target']}")) reqs.append(lambda state, req=req: state.can_reach_region(req, self.player)) else: if obj_is_region and req in self.indirect_regions: # add to indirect conditions if object is door and requirement has list of regions for region in self.indirect_regions[req]: self.indirect_conditions.append((region, f"{name} -> {obj['target']}")) reqs.append(self.string_rules[req]) if len(reqs) == 1: clauses.append(reqs[0]) else: clauses.append(lambda state, reqs=reqs: all(req(state) for req in reqs)) if not clauses: return lambda state: True elif len(clauses) == 1: return clauses[0] else: return lambda state: any(clause(state) for clause in clauses) # Relics def blood(self, state: CollectionState) -> bool: return state.has("Blood Perpetuated in Sand", self.player) def root(self, state: CollectionState) -> bool: return state.has("Three Gnarled Tongues", self.player) def linen(self, state: CollectionState) -> bool: return state.has("Linen of Golden Thread", self.player) def nail(self, state: CollectionState) -> bool: return state.has("Nail Uprooted from Dirt", self.player) def shroud(self, state: CollectionState) -> bool: return state.has("Shroud of Dreamt Sins", self.player) def lung(self, state: CollectionState) -> bool: return state.has("Silvered Lung of Dolphos", self.player) # Keys def bronze_key(self, state: CollectionState) -> bool: return state.has("Key of the Secular", self.player) def silver_key(self, state: CollectionState) -> bool: return state.has("Key of the Scribe", self.player) def gold_key(self, state: CollectionState) -> bool: return state.has("Key of the Inquisitor", self.player) def peaks_key(self, state: CollectionState) -> bool: return state.has("Key of the High Peaks", self.player) def elder_key(self, state: CollectionState) -> bool: return state.has("Key to the Chamber of the Eldest Brother", self.player) def wood_key(self, state: CollectionState) -> bool: return state.has("Key Grown from Twisted Wood", self.player) # Collections def cherubs(self, state: CollectionState) -> int: return state.count("Child of Moonlight", self.player) def bones(self, state: CollectionState) -> int: return state.count_group_unique("bones", self.player) # def tears(): # Special items def dash(self, state: CollectionState) -> bool: return state.has("Dash Ability", self.player) def wall_climb(self, state: CollectionState) -> bool: return state.has("Wall Climb Ability", self.player) #def air_impulse(): def boots(self, state: CollectionState) -> bool: return state.has("Boots of Pleading", self.player) def double_jump(self, state: CollectionState) -> bool: return state.has("Purified Hand of the Nun", self.player) # Speed boosts def wheel(self, state: CollectionState) -> bool: return state.has("The Young Mason's Wheel", self.player) def dawn_heart(self, state: CollectionState) -> bool: return state.has("Brilliant Heart of Dawn", self.player) # Health boosts def flasks(self, state: CollectionState) -> int: doors = { "D01Z05S05[SW]", "D02Z02S04[W]", "D03Z02S08[W]", "D03Z03S04[SW]", "D04Z02S13[W]", "D05Z01S08[NW]", "D20Z01S07[NE]" } return state.count("Empty Bile Vessel", self.player) \ if sum(state.can_reach_region(door, self.player) for door in doors) >= 1 else 0 def quicksilver(self, state: CollectionState) -> int: return state.count("Quicksilver", self.player) if state.can_reach_region("D01Z05S01[W]", self.player) else 0 # Puzzles def red_wax(self, state: CollectionState) -> int: return state.count("Bead of Red Wax", self.player) def blue_wax(self, state: CollectionState) -> int: return state.count("Bead of Blue Wax", self.player) def chalice(self, state: CollectionState) -> bool: return state.has("Chalice of Inverted Verses", self.player) # Cherubs def debla(self, state: CollectionState) -> bool: return state.has("Debla of the Lights", self.player) def lorquiana(self, state: CollectionState) -> bool: return state.has("Lorquiana", self.player) def zarabanda(self, state: CollectionState) -> bool: return state.has("Zarabanda of the Safe Haven", self.player) def taranto(self, state: CollectionState) -> bool: return state.has("Taranto to my Sister", self.player) def verdiales(self, state: CollectionState) -> bool: return state.has("Verdiales of the Forsaken Hamlet", self.player) def cante(self, state: CollectionState) -> bool: return state.has("Cante Jondo of the Three Sisters", self.player) def cantina(self, state: CollectionState) -> bool: return state.has("Cantina of the Blue Rose", self.player) def aubade(self, state: CollectionState) -> bool: return ( state.has("Aubade of the Nameless Guardian", self.player) and self.total_fervour(state) >= 90 ) def tirana(self, state: CollectionState) -> bool: return ( state.has("Tirana of the Celestial Bastion", self.player) and self.total_fervour(state) >= 90 ) def ruby(self, state: CollectionState) -> bool: return state.has("Cloistered Ruby", self.player) def tiento(self, state: CollectionState) -> bool: return state.has("Tiento to my Sister", self.player) def any_small_prayer(self, state: CollectionState) -> bool: return ( self.debla(state) or self.lorquiana(state) or self.zarabanda(state) or self.taranto(state) or self.verdiales(state) or self.cante(state) or self.cantina(state) or self.tiento(state) or state.has_any({ "Campanillero to the Sons of the Aurora", "Mirabras of the Return to Port", "Romance to the Crimson Mist", "Saeta Dolorosa", "Seguiriya to your Eyes like Stars", "Verdiales of the Forsaken Hamlet", "Zambra to the Resplendent Crown" }, self.player) ) def pillar(self, state: CollectionState) -> bool: return ( self.debla(state) or self.taranto(state) or self.ruby(state) ) def can_use_any_prayer(self, state: CollectionState) -> bool: return ( self.any_small_prayer(state) or self.tirana(state) or self.aubade(state) ) # Stats def total_fervour(self, state: CollectionState) -> int: return ( 60 + (20 * min(6, state.count("Fervour Upgrade", self.player))) + (10 * min(3, state.count("Bead of Blue Wax", self.player))) ) # Skills def combo(self, state: CollectionState) -> int: return state.count("Combo Skill", self.player) def charged(self, state: CollectionState) -> int: return state.count("Charged Skill", self.player) def ranged(self, state: CollectionState) -> int: return state.count("Ranged Skill", self.player) def dive(self, state: CollectionState) -> int: return state.count("Dive Skill", self.player) def lunge(self, state: CollectionState) -> int: return state.count("Lunge Skill", self.player) def charge_beam(self, state: CollectionState) -> bool: return self.charged(state) >= 3 # Main quest def holy_wounds(self, state: CollectionState) -> int: return state.count_group_unique("wounds", self.player) def masks(self, state: CollectionState) -> int: return state.count_group_unique("masks", self.player) def guilt_bead(self, state: CollectionState) -> bool: return state.has("Weight of True Guilt", self.player) # LOTL quest def cloth(self, state: CollectionState) -> bool: return state.has("Linen Cloth", self.player) def hand(self, state: CollectionState) -> bool: return state.has("Severed Hand", self.player) def hatched_egg(self, state: CollectionState) -> bool: return state.has("Hatched Egg of Deformity", self.player) # Tirso quest def herbs(self, state: CollectionState) -> int: return state.count_group_unique("tirso", self.player) # Tentudia quest def tentudia_remains(self, state: CollectionState) -> int: return state.count_group_unique("tentudia", self.player) # Gemino quest def empty_thimble(self, state: CollectionState) -> bool: return state.has("Empty Golden Thimble", self.player) def full_thimble(self, state: CollectionState) -> bool: return state.has("Golden Thimble Filled with Burning Oil", self.player) def dried_flowers(self, state: CollectionState) -> bool: return state.has("Dried Flowers bathed in Tears", self.player) # Altasgracias quest def ceremony_items(self, state: CollectionState) -> int: return state.count_group_unique("egg", self.player) def egg(self, state: CollectionState) -> bool: return state.has("Egg of Deformity", self.player) # Redento quest def limestones(self, state: CollectionState) -> int: return state.count_group_unique("toe", self.player) def knots(self, state: CollectionState) -> int: return state.count("Knot of Rosary Rope", self.player) if state.can_reach_region("D17Z01S07[NW]", self.player)\ else 0 # Cleofas quest def marks_of_refuge(self, state: CollectionState) -> int: return state.count_group_unique("marks", self.player) def cord(self, state: CollectionState) -> bool: return state.has("Cord of the True Burying", self.player) # Crisanta quest def scapular(self, state: CollectionState) -> bool: return state.has("Incomplete Scapular", self.player) def true_heart(self, state: CollectionState) -> bool: return state.has("Apodictic Heart of Mea Culpa", self.player) def traitor_eyes(self, state: CollectionState) -> int: return state.count_group_unique("eye", self.player) # Jibrael quest def bell(self, state: CollectionState) -> bool: return state.has("Petrified Bell", self.player) def verses(self, state: CollectionState) -> int: return state.count("Verses Spun from Gold", self.player) # Movement tech def can_air_stall(self, state: CollectionState) -> bool: return ( self.ranged(state) > 0 and self.world.options.difficulty >= 1 ) def can_dawn_jump(self, state: CollectionState) -> bool: return ( self.dawn_heart(state) and self.dash(state) and self.world.options.difficulty >= 1 ) def can_water_jump(self, state: CollectionState) -> bool: return ( self.nail(state) or self.double_jump(state) ) # Breakable tech def can_break_holes(self, state: CollectionState) -> bool: return ( self.charged(state) > 0 or self.dive(state) > 0 or self.lunge(state) >= 3 and self.dash(state) or self.can_use_any_prayer(state) ) def can_dive_laser(self, state: CollectionState) -> bool: return ( self.dive(state) >= 3 and self.world.options.difficulty >= 2 ) # Root tech def can_walk_on_root(self, state: CollectionState) -> bool: return self.root(state) def can_climb_on_root(self, state: CollectionState) -> bool: return ( self.root(state) and self.wall_climb(state) ) # Lung tech def can_survive_poison_1(self, state: CollectionState) -> bool: return ( self.lung(state) or self.world.options.difficulty >= 1 and self.tiento(state) or self.world.options.difficulty >= 2 ) def can_survive_poison_2(self, state: CollectionState) -> bool: return ( self.lung(state) or self.world.options.difficulty >= 1 and self.tiento(state) ) def can_survive_poison_3(self, state: CollectionState) -> bool: return ( self.lung(state) or self.world.options.difficulty >= 2 and self.tiento(state) and self.total_fervour(state) >= 120 ) # Enemy tech def can_enemy_bounce(self, state: CollectionState) -> bool: return self.enemy_skips_allowed(state) def can_enemy_upslash(self, state: CollectionState) -> bool: return ( self.combo(state) >= 2 and self.enemy_skips_allowed(state) ) # Crossing gaps def can_cross_gap_1(self, state: CollectionState) -> bool: return ( self.double_jump(state) or self.can_dawn_jump(state) or self.wheel(state) or self.can_air_stall(state) ) def can_cross_gap_2(self, state: CollectionState) -> bool: return ( self.double_jump(state) or self.can_dawn_jump(state) or self.wheel(state) ) def can_cross_gap_3(self, state: CollectionState) -> bool: return ( self.double_jump(state) or self.can_dawn_jump(state) or self.wheel(state) and self.can_air_stall(state) ) def can_cross_gap_4(self, state: CollectionState) -> bool: return ( self.double_jump(state) or self.can_dawn_jump(state) ) def can_cross_gap_5(self, state: CollectionState) -> bool: return ( self.double_jump(state) or self.can_dawn_jump(state) and self.can_air_stall(state) ) def can_cross_gap_6(self, state: CollectionState) -> bool: return self.double_jump(state) def can_cross_gap_7(self, state: CollectionState) -> bool: return ( self.double_jump(state) and ( self.can_dawn_jump(state) or self.wheel(state) or self.can_air_stall(state) ) ) def can_cross_gap_8(self, state: CollectionState) -> bool: return ( self.double_jump(state) and ( self.can_dawn_jump(state) or self.wheel(state) ) ) def can_cross_gap_9(self, state: CollectionState) -> bool: return ( self.double_jump(state) and ( self.can_dawn_jump(state) or self.wheel(state) and self.can_air_stall(state) ) ) def can_cross_gap_10(self, state: CollectionState) -> bool: return ( self.double_jump(state) and self.can_dawn_jump(state) ) def can_cross_gap_11(self, state: CollectionState) -> bool: return ( self.double_jump(state) and self.can_dawn_jump(state) and self.can_air_stall(state) ) # Events that trigger in different scenes def opened_dc_gate_w(self, state: CollectionState) -> bool: return ( state.can_reach_region("D20Z01S04[E]", self.player) or state.can_reach_region("D01Z05S23[W]", self.player) ) def opened_dc_gate_e(self, state: CollectionState) -> bool: return ( state.can_reach_region("D01Z05S10[SE]", self.player) or state.can_reach_region("D01Z04S09[W]", self.player) ) def opened_dc_ladder(self, state: CollectionState) -> bool: return ( state.can_reach_region("D01Z05S25[NE]", self.player) or state.can_reach_region("D01Z05S02[S]", self.player) ) def opened_wotw_cave(self, state: CollectionState) -> bool: return ( state.can_reach_region("D02Z01S01[SW]", self.player) or self.wall_climb(state) and state.can_reach_region("D02Z01S08[E]", self.player) or state.can_reach_region("D02Z01S02[]", self.player) ) def rode_gotp_elevator(self, state: CollectionState) -> bool: return ( state.can_reach_region("D02Z03S14[E]", self.player) or state.can_reach_region("D02Z02S13[W]", self.player) or state.can_reach_region("D02Z02S06[E]", self.player) or state.can_reach_region("D02Z02S12[W]", self.player) or state.can_reach_region("D02Z02S08[W]", self.player) ) def opened_convent_ladder(self, state: CollectionState) -> bool: return ( state.can_reach_region("D02Z03S02[N]", self.player) or state.can_reach_region("D02Z03S15[E]", self.player) or state.can_reach_region("D02Z03S19[E]", self.player) or state.can_reach_region("D02Z03S10[W]", self.player) or state.can_reach_region("D02Z03S22[W]", self.player) ) def broke_jondo_bell_w(self, state: CollectionState) -> bool: return ( state.can_reach_region("D03Z02S08[N]", self.player) or state.can_reach_region("D03Z02S12[E]", self.player) and self.dash(state) or state.can_reach_region("D03Z02S10[S]", self.player) or state.can_reach_region("D03Z02S10[-Cherubs]", self.player) ) def broke_jondo_bell_e(self, state: CollectionState) -> bool: return ( state.can_reach_region("D03Z02S04[NE]", self.player) or state.can_reach_region("D03Z02S11[W]", self.player) or state.can_reach_region("D03Z02S03[E]", self.player) and ( self.can_cross_gap_5(state) or self.can_enemy_bounce(state) and self.can_cross_gap_3(state) ) ) def opened_mom_ladder(self, state: CollectionState) -> bool: return ( state.can_reach_region("D04Z02S11[E]", self.player) or state.can_reach_region("D04Z02S09[W]", self.player) or state.can_reach_region("D06Z01S23[S]", self.player) or state.can_reach_region("D04Z02S04[N]", self.player) ) def opened_tsc_gate(self, state: CollectionState) -> bool: return ( state.can_reach_region("D05Z02S06[SE]", self.player) or state.can_reach_region("D05Z01S21[-Cherubs]", self.player) ) def opened_ar_ladder(self, state: CollectionState) -> bool: return ( state.can_reach_region("D06Z01S22[Sword]", self.player) or state.can_reach_region("D06Z01S20[W]", self.player) or state.can_reach_region("D04Z02S06[N]", self.player) or state.can_reach_region("D06Z01S01[-Cherubs]", self.player) ) def broke_bottc_statue(self, state: CollectionState) -> bool: return ( state.can_reach_region("D08Z03S03[W]", self.player) or state.can_reach_region("D08Z02S03[W]", self.player) ) def opened_wothp_gate(self, state: CollectionState) -> bool: return ( state.can_reach_region("D09Z01S13[E]", self.player) or state.can_reach_region("D09Z01S03[W]", self.player) or state.can_reach_region("D09Z01S08[W]", self.player) ) def opened_botss_ladder(self, state: CollectionState) -> bool: return ( state.can_reach_region("D17Z01S05[S]", self.player) or state.can_reach_region("D17BZ02S01[FrontR]", self.player) ) # Special skips def upwarp_skips_allowed(self, state: CollectionState) -> bool: return self.world.options.difficulty >= 2 def mourning_skip_allowed(self, state: CollectionState) -> bool: return self.world.options.difficulty >= 2 def enemy_skips_allowed(self, state: CollectionState) -> bool: return ( self.world.options.difficulty >= 2 and not self.world.options.enemy_randomizer ) def obscure_skips_allowed(self, state: CollectionState) -> bool: return self.world.options.difficulty >= 2 def precise_skips_allowed(self, state: CollectionState) -> bool: return self.world.options.difficulty >= 2 # Bosses def can_beat_brotherhood_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "warden") and ( state.can_reach_region("D17Z01S05[E]", self.player) or state.can_reach_region("D17Z01S03[W]", self.player) ) ) def can_beat_mercy_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "ten-piedad") and ( state.can_reach_region("D01Z04S19[E]", self.player) or state.can_reach_region("D01Z04S12[W]", self.player) ) ) def can_beat_convent_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "charred-visage") and ( state.can_reach_region("D02Z03S09[E]", self.player) or state.can_reach_region("D02Z03S21[W]", self.player) ) ) def can_beat_grievance_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "tres-angustias") and ( self.wall_climb(state) or self.double_jump(state) ) and ( state.can_reach_region("D03Z03S11[E]", self.player) or state.can_reach_region("D03Z03S16[W]", self.player) ) ) def can_beat_bridge_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "esdras") and ( state.can_reach_region("D01Z03S06[E]", self.player) or state.can_reach_region("D08Z02S01[W]", self.player) ) ) def can_beat_mothers_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "melquiades") and ( state.can_reach_region("D04Z02S15[E]", self.player) or state.can_reach_region("D04Z02S21[W]", self.player) ) ) def can_beat_canvases_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "exposito") and ( state.can_reach_region("D05Z02S06[NE]", self.player) or state.can_reach_region("D05Z01S21[SW]", self.player) ) ) def can_beat_prison_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "quirce") and ( state.can_reach_region("D09Z01S05[SE]", self.player) or state.can_reach_region("D09Z01S08[S]", self.player) ) ) def can_beat_rooftops_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "crisanta") and ( state.can_reach_region("D06Z01S19[E]", self.player) or state.can_reach_region("D07Z01S01[W]", self.player) ) ) def can_beat_ossuary_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "isidora") and state.can_reach_region("D01BZ06S01[E]", self.player) ) def can_beat_mourning_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "sierpes") and state.can_reach_region("D20Z02S07[W]", self.player) ) def can_beat_graveyard_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "amanecida") and self.wall_climb(state) and state.can_reach_region("D01Z06S01[Santos]", self.player) and state.can_reach_region("D02Z03S18[NW]", self.player) and state.can_reach_region("D02Z02S03[NE]", self.player) ) def can_beat_jondo_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "amanecida") and state.can_reach_region("D01Z06S01[Santos]", self.player) and ( state.can_reach_region("D20Z01S06[NE]", self.player) or state.can_reach_region("D20Z01S04[W]", self.player) ) and ( state.can_reach_region("D03Z01S04[E]", self.player) or state.can_reach_region("D03Z02S10[N]", self.player) ) ) def can_beat_patio_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "amanecida") and state.can_reach_region("D01Z06S01[Santos]", self.player) and state.can_reach_region("D06Z01S02[W]", self.player) and ( state.can_reach_region("D04Z01S03[E]", self.player) or state.can_reach_region("D04Z01S01[W]", self.player) or state.can_reach_region("D06Z01S18[-Cherubs]", self.player) ) ) def can_beat_wall_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "amanecida") and state.can_reach_region("D01Z06S01[Santos]", self.player) and state.can_reach_region("D09Z01S09[Cell24]", self.player) and ( state.can_reach_region("D09Z01S11[E]", self.player) or state.can_reach_region("D06Z01S13[W]", self.player) ) ) def can_beat_hall_boss(self, state: CollectionState) -> bool: return ( self.has_boss_strength(state, "laudes") and ( state.can_reach_region("D08Z01S02[NE]", self.player) or state.can_reach_region("D08Z03S02[NW]", self.player) ) ) def can_beat_perpetua(self, state: CollectionState) -> bool: return self.has_boss_strength(state, "perpetua") def can_beat_legionary(self, state: CollectionState) -> bool: return self.has_boss_strength(state, "legionary") def has_boss_strength(self, state: CollectionState, boss: str) -> bool: life: int = state.count("Life Upgrade", self.player) sword: int = state.count("Mea Culpa Upgrade", self.player) fervour: int = state.count("Fervour Upgrade", self.player) flasks: int = self.flasks(state) quicksilver: int = self.quicksilver(state) player_strength: float = ( min(6, life) * 0.25 / 6 + min(7, sword) * 0.25 / 7 + min(6, fervour) * 0.20 / 6 + min(8, flasks) * 0.15 / 8 + min(5, quicksilver) * 0.15 / 5 ) bosses: Dict[str, float] = { "warden": -0.10, "ten-piedad": 0.05, "charred-visage": 0.20, "tres-angustias": 0.15, "esdras": 0.25, "melquiades": 0.25, "exposito": 0.30, "quirce": 0.35, "crisanta": 0.50, "isidora": 0.70, "sierpes": 0.70, "amanecida": 0.60, "laudes": 0.60, "perpetua": -0.05, "legionary": 0.20 } boss_strength: float = bosses[boss] return player_strength >= (boss_strength - 0.10 if self.world.options.difficulty >= 2 else (boss_strength if self.world.options.difficulty >= 1 else boss_strength + 0.10)) def guilt_rooms(self, state: CollectionState) -> int: doors = [ "D01Z04S01[NE]", "D02Z02S11[W]", "D03Z03S02[NE]", "D04Z02S02[SE]", "D05Z01S05[NE]", "D09Z01S05[W]", "D17Z01S04[W]", ] return sum(state.can_reach_region(door, self.player) for door in doors) def sword_rooms(self, state: CollectionState) -> int: doors = [ ["D01Z02S07[E]", "D01Z02S02[SW]"], ["D20Z01S04[E]", "D01Z05S23[W]"], ["D02Z03S02[NE]"], ["D04Z02S21[NE]"], ["D05Z01S21[NW]"], ["D06Z01S15[NE]"], ["D17Z01S07[SW]"] ] total: int = 0 for subdoors in doors: for door in subdoors: if state.can_reach_region(door, self.player): total += 1 break return total def redento_rooms(self, state: CollectionState) -> int: if ( state.can_reach_region("D03Z01S04[E]", self.player) or state.can_reach_region("D03Z02S10[N]", self.player) ): if ( state.can_reach_region("D17Z01S05[S]", self.player) or state.can_reach_region("D17BZ02S01[FrontR]", self.player) ): if ( state.can_reach_region("D01Z03S04[E]", self.player) or state.can_reach_region("D08Z01S01[W]", self.player) ): if ( state.can_reach_region("D04Z01S03[E]", self.player) or state.can_reach_region("D04Z02S01[W]", self.player) or state.can_reach_region("D06Z01S18[-Cherubs]", self.player) ): if ( self.knots(state) >= 1 and self.limestones(state) >= 3 and ( state.can_reach_region("D04Z02S08[E]", self.player) or state.can_reach_region("D04BZ02S01[Redento]", self.player) ) ): return 5 return 4 return 3 return 2 return 1 return 0 def miriam_rooms(self, state: CollectionState) -> int: doors = [ "D02Z03S07[NWW]", "D03Z03S07[NW]", "D04Z04S01[E]", "D05Z01S06[W]", "D06Z01S17[E]" ] return sum(state.can_reach_region(door, self.player) for door in doors) def amanecida_rooms(self, state: CollectionState) -> int: total: int = 0 if self.can_beat_graveyard_boss(state): total += 1 if self.can_beat_jondo_boss(state): total += 1 if self.can_beat_patio_boss(state): total += 1 if self.can_beat_wall_boss(state): total += 1 return total def chalice_rooms(self, state: CollectionState) -> int: doors = [ ["D03Z01S02[E]", "D01Z05S02[W]", "D20Z01S03[N]"], ["D05Z01S11[SE]", "D05Z02S02[NW]"], ["D09Z01S09[E]", "D09Z01S10[W]", "D09Z01S08[SE]", "D09Z01S02[SW]"] ] total: int = 0 for subdoors in doors: for door in subdoors: if state.can_reach_region(door, self.player): total += 1 break return total