mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00

* Worked locally before that so this is a lot of work . So, initial push * Changes in init with better create_regions (Thanks to Phar on discord). Add a rule for victory. Change the regions list to remove menu in the destination. * Added tests for location rules and changed rule locations to lists instead of sets * Fixed game var in InscryptionLocation * Fixed location access by using the same system from The Messenger * Remove unuse rules in init and add region rules. Add all the act 2 locations and items. * Add locations rule for the left of the bridge in act 2 * Added test for bridge requirement and added a dash to locationfor clarity * Added more act 2 rules and removed completion rule * Created docs for website, added Salmon Card item, marked multiple items as "progression", renamed tomb checks, added more location rules, re-added completion rule * Renamed tower bath check to "Tentacle", added monocle as requirement for some checks, adjusted setup doc a bit * Added tentacle to monocle test * Added forest burrow chest rule * Switch the two clock location because the id was swapped and screwed with the logic * Added Ancient Obol rule and adjusted docs * Added act 3 locations/items/rules/tests * Added drone & battery to trader rules * Fixed tutorial docs, added more act 3 rules, renamed holo pelt locations * Add an option for the optional death card feature * Added well check and quill item, added rules and tests * Renamed Gems module and Gems drone * Added slot data options * Added rule for act 3 middle pelt * Added option for randomize ability and uptade the randomize deck option to fit the new setup * Added randomize ability in slot data * Added more requirements for mycologists boss since it's pretty much an impossible fight early on * Finished the french translation of the installation guide * Changed the french title in the guide * Added goal option and tests associated to it + fixed goal requirement missing quill * Added goal option to docs and removed references to the now discarded API mod. Fixed some french translations. * Added ourobot item + renamed some goal settings * Fixed locations and items for act 1 goal * Added skip tutorial option. Cleanup and rename of some options. Added tower requirement for Mycologist Key check. Fixed missing comma in act 2 locations oopsies. * Added missing rules for Extra Battery, Nano Armor and Goobert's painting * Added act 1 deathlink behaviour and epitaph pieces randomization options + made pieces progressive + adjusted docs * Fixed some docs typos * Added act 3 clock rule. Paintings 2, 3 and Goobert's painting can no longer contain progression items. * New options system and fixed act 1 goal option breaking * Added skip epilogue and painting checks balancing options. Renamed randomize abilities to randomize sigils. Fixed generation issue with epitaph pieces randomization. Goobert's painting no longer forces filler. Removed traps option for now. Reworded some option descriptions. * Attempting type fix for python 3.8 * Attempting type fix for python 3.8 again * Added starting only option for randomize deck * Fixed arbitrary rule error * Import fix attempt * Migrated to DeathLinkMixin instead of creating a custom DeathLink option, cleaned up imports, renamed Death Link related options to include "death_link" instead of "deathlink", replaced numeral values for option checking into class attributes for readability, slight optimization to tower rule, fixed typo in codes option description. * Added bug report page to web class, condensed pelt rules to one function, added items/locations count in game docs and adjusted some sections * Added Inscryption to CODEOWNERS * Implemented a bunch of suggestions: Better handling of painting option, options as dict for slot data, remove redundant auto_display_name, use of has_all, better goal tests, demote skink card to filler if goal is act 1 and force filler on paintings * Makes clover plant and squirrel head progression items if paintings are balanced + fixed other issues * filler items, start inventory from pool, '->" * Fix bleeding issue * Copy the list instead * Fixed bleeding using proper deep copy * Remove unnecessary for loops in tests * Add defaults to choice options --------- Co-authored-by: Benjamin Gregoire <benjamingregoire@outlook.com> Co-authored-by: Exempt-Medic <ExemptMedic@Gmail.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
182 lines
10 KiB
Python
182 lines
10 KiB
Python
from typing import Dict, Callable, TYPE_CHECKING
|
|
from BaseClasses import CollectionState, LocationProgressType
|
|
from .Options import Goal, PaintingChecksBalancing
|
|
|
|
if TYPE_CHECKING:
|
|
from . import InscryptionWorld
|
|
else:
|
|
InscryptionWorld = object
|
|
|
|
|
|
# Based on The Messenger's implementation
|
|
class InscryptionRules:
|
|
player: int
|
|
world: InscryptionWorld
|
|
location_rules: Dict[str, Callable[[CollectionState], bool]]
|
|
region_rules: Dict[str, Callable[[CollectionState], bool]]
|
|
|
|
def __init__(self, world: InscryptionWorld) -> None:
|
|
self.player = world.player
|
|
self.world = world
|
|
self.location_rules = {
|
|
"Act 1 - Wardrobe Drawer 1": self.has_wardrobe_key,
|
|
"Act 1 - Wardrobe Drawer 2": self.has_wardrobe_key,
|
|
"Act 1 - Wardrobe Drawer 3": self.has_wardrobe_key,
|
|
"Act 1 - Wardrobe Drawer 4": self.has_wardrobe_key,
|
|
"Act 1 - Dagger": self.has_caged_wolf,
|
|
"Act 1 - Magnificus Eye": self.has_dagger,
|
|
"Act 1 - Clock Main Compartment": self.has_magnificus_eye,
|
|
"Act 2 - Battle Prospector": self.has_camera_and_meat,
|
|
"Act 2 - Battle Angler": self.has_camera_and_meat,
|
|
"Act 2 - Battle Trapper": self.has_camera_and_meat,
|
|
"Act 2 - Battle Pike Mage": self.has_tower_requirements,
|
|
"Act 2 - Battle Goobert": self.has_tower_requirements,
|
|
"Act 2 - Battle Lonely Wizard": self.has_tower_requirements,
|
|
"Act 2 - Battle Inspector": self.has_act2_bridge_requirements,
|
|
"Act 2 - Battle Melter": self.has_act2_bridge_requirements,
|
|
"Act 2 - Battle Dredger": self.has_act2_bridge_requirements,
|
|
"Act 2 - Forest Meadow Chest": self.has_camera_and_meat,
|
|
"Act 2 - Tower Chest 1": self.has_act2_bridge_requirements,
|
|
"Act 2 - Tower Chest 2": self.has_tower_requirements,
|
|
"Act 2 - Tower Chest 3": self.has_tower_requirements,
|
|
"Act 2 - Tentacle": self.has_tower_requirements,
|
|
"Act 2 - Factory Trash Can": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Drawer 1": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Drawer 2": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Chest 1": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Chest 2": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Chest 3": self.has_act2_bridge_requirements,
|
|
"Act 2 - Factory Chest 4": self.has_act2_bridge_requirements,
|
|
"Act 2 - Monocle": self.has_act2_bridge_requirements,
|
|
"Act 2 - Boss Grimora": self.has_all_epitaph_pieces,
|
|
"Act 2 - Boss Leshy": self.has_camera_and_meat,
|
|
"Act 2 - Boss Magnificus": self.has_tower_requirements,
|
|
"Act 2 - Boss P03": self.has_act2_bridge_requirements,
|
|
"Act 2 - Bone Lord Femur": self.has_obol,
|
|
"Act 2 - Bone Lord Horn": self.has_obol,
|
|
"Act 2 - Bone Lord Holo Key": self.has_obol,
|
|
"Act 2 - Mycologists Holo Key": self.has_tower_requirements, # Could need money
|
|
"Act 2 - Ancient Obol": self.has_tower_requirements, # Need money for the pieces? Use the tower mannequin.
|
|
"Act 3 - Boss Photographer": self.has_inspectometer_battery,
|
|
"Act 3 - Boss Archivist": self.has_battery_and_quill,
|
|
"Act 3 - Boss Unfinished": self.has_gems_and_battery,
|
|
"Act 3 - Boss G0lly": self.has_gems_and_battery,
|
|
"Act 3 - Extra Battery": self.has_inspectometer_battery, # Hard to miss but soft lock still possible.
|
|
"Act 3 - Nano Armor Generator": self.has_gems_and_battery, # Costs money, so can need multiple battles.
|
|
"Act 3 - Shop Holo Pelt": self.has_gems_and_battery, # Costs money, so can need multiple battles.
|
|
"Act 3 - Middle Holo Pelt": self.has_inspectometer_battery, # Can be reached without but possible soft lock
|
|
"Act 3 - Forest Holo Pelt": self.has_inspectometer_battery,
|
|
"Act 3 - Crypt Holo Pelt": self.has_inspectometer_battery,
|
|
"Act 3 - Tower Holo Pelt": self.has_gems_and_battery,
|
|
"Act 3 - Trader 1": self.has_pelts(1),
|
|
"Act 3 - Trader 2": self.has_pelts(2),
|
|
"Act 3 - Trader 3": self.has_pelts(3),
|
|
"Act 3 - Trader 4": self.has_pelts(4),
|
|
"Act 3 - Trader 5": self.has_pelts(5),
|
|
"Act 3 - Goobert's Painting": self.has_gems_and_battery,
|
|
"Act 3 - The Great Transcendence": self.has_transcendence_requirements,
|
|
"Act 3 - Boss Mycologists": self.has_mycologists_boss_requirements,
|
|
"Act 3 - Bone Lord Room": self.has_bone_lord_room_requirements,
|
|
"Act 3 - Luke's File Entry 1": self.has_battery_and_quill,
|
|
"Act 3 - Luke's File Entry 2": self.has_battery_and_quill,
|
|
"Act 3 - Luke's File Entry 3": self.has_battery_and_quill,
|
|
"Act 3 - Luke's File Entry 4": self.has_transcendence_requirements,
|
|
"Act 3 - Well": self.has_inspectometer_battery,
|
|
"Act 3 - Gems Drone": self.has_inspectometer_battery,
|
|
"Act 3 - Clock": self.has_gems_and_battery, # Can be brute-forced, but the solution needs those items.
|
|
}
|
|
self.region_rules = {
|
|
"Act 2": self.has_act2_requirements,
|
|
"Act 3": self.has_act3_requirements,
|
|
"Epilogue": self.has_epilogue_requirements
|
|
}
|
|
|
|
def has_wardrobe_key(self, state: CollectionState) -> bool:
|
|
return state.has("Wardrobe Key", self.player)
|
|
|
|
def has_caged_wolf(self, state: CollectionState) -> bool:
|
|
return state.has("Caged Wolf Card", self.player)
|
|
|
|
def has_dagger(self, state: CollectionState) -> bool:
|
|
return state.has("Dagger", self.player)
|
|
|
|
def has_magnificus_eye(self, state: CollectionState) -> bool:
|
|
return state.has("Magnificus Eye", self.player)
|
|
|
|
def has_useful_act1_items(self, state: CollectionState) -> bool:
|
|
return state.has_all(("Oil Painting's Clover Plant", "Squirrel Totem Head"), self.player)
|
|
|
|
def has_all_epitaph_pieces(self, state: CollectionState) -> bool:
|
|
return state.has(self.world.required_epitaph_pieces_name, self.player, self.world.required_epitaph_pieces_count)
|
|
|
|
def has_camera_and_meat(self, state: CollectionState) -> bool:
|
|
return state.has_all(("Camera Replica", "Pile Of Meat"), self.player)
|
|
|
|
def has_monocle(self, state: CollectionState) -> bool:
|
|
return state.has("Monocle", self.player)
|
|
|
|
def has_obol(self, state: CollectionState) -> bool:
|
|
return state.has("Ancient Obol", self.player)
|
|
|
|
def has_epitaphs_and_forest_items(self, state: CollectionState) -> bool:
|
|
return self.has_camera_and_meat(state) and self.has_all_epitaph_pieces(state)
|
|
|
|
def has_act2_bridge_requirements(self, state: CollectionState) -> bool:
|
|
return self.has_camera_and_meat(state) or self.has_all_epitaph_pieces(state)
|
|
|
|
def has_tower_requirements(self, state: CollectionState) -> bool:
|
|
return self.has_monocle(state) and self.has_act2_bridge_requirements(state)
|
|
|
|
def has_inspectometer_battery(self, state: CollectionState) -> bool:
|
|
return state.has("Inspectometer Battery", self.player)
|
|
|
|
def has_gems_and_battery(self, state: CollectionState) -> bool:
|
|
return state.has("Gems Module", self.player) and self.has_inspectometer_battery(state)
|
|
|
|
def has_pelts(self, count: int) -> Callable[[CollectionState], bool]:
|
|
return lambda state: state.has("Holo Pelt", self.player, count) and self.has_gems_and_battery(state)
|
|
|
|
def has_mycologists_boss_requirements(self, state: CollectionState) -> bool:
|
|
return state.has("Mycologists Holo Key", self.player) and self.has_transcendence_requirements(state)
|
|
|
|
def has_bone_lord_room_requirements(self, state: CollectionState) -> bool:
|
|
return state.has("Bone Lord Holo Key", self.player) and self.has_inspectometer_battery(state)
|
|
|
|
def has_battery_and_quill(self, state: CollectionState) -> bool:
|
|
return state.has("Quill", self.player) and self.has_inspectometer_battery(state)
|
|
|
|
def has_transcendence_requirements(self, state: CollectionState) -> bool:
|
|
return state.has("Quill", self.player) and self.has_gems_and_battery(state)
|
|
|
|
def has_act2_requirements(self, state: CollectionState) -> bool:
|
|
return state.has("Film Roll", self.player)
|
|
|
|
def has_act3_requirements(self, state: CollectionState) -> bool:
|
|
return self.has_act2_requirements(state) and self.has_all_epitaph_pieces(state) and \
|
|
self.has_camera_and_meat(state) and self.has_monocle(state)
|
|
|
|
def has_epilogue_requirements(self, state: CollectionState) -> bool:
|
|
return self.has_act3_requirements(state) and self.has_transcendence_requirements(state)
|
|
|
|
def set_all_rules(self) -> None:
|
|
multiworld = self.world.multiworld
|
|
if self.world.options.goal != Goal.option_first_act:
|
|
multiworld.completion_condition[self.player] = self.has_epilogue_requirements
|
|
else:
|
|
multiworld.completion_condition[self.player] = self.has_act2_requirements
|
|
for region in multiworld.get_regions(self.player):
|
|
if self.world.options.goal == Goal.option_full_story_in_order:
|
|
if region.name in self.region_rules:
|
|
for entrance in region.entrances:
|
|
entrance.access_rule = self.region_rules[region.name]
|
|
for loc in region.locations:
|
|
if loc.name in self.location_rules:
|
|
loc.access_rule = self.location_rules[loc.name]
|
|
|
|
if self.world.options.painting_checks_balancing == PaintingChecksBalancing.option_balanced:
|
|
self.world.get_location("Act 1 - Painting 2").access_rule = self.has_useful_act1_items
|
|
self.world.get_location("Act 1 - Painting 3").access_rule = self.has_useful_act1_items
|
|
elif self.world.options.painting_checks_balancing == PaintingChecksBalancing.option_force_filler:
|
|
self.world.get_location("Act 1 - Painting 2").progress_type = LocationProgressType.EXCLUDED
|
|
self.world.get_location("Act 1 - Painting 3").progress_type = LocationProgressType.EXCLUDED
|