Inscryption: Implement new game (#3621)

* 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>
This commit is contained in:
DrBibop 2024-12-21 17:12:35 -05:00 committed by GitHub
parent 46613adceb
commit 4f590cdf7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1255 additions and 0 deletions

View File

@ -79,6 +79,7 @@ Currently, the following games are supported:
* Faxanadu
* Saving Princess
* Castlevania: Circle of the Moon
* Inscryption
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@ -81,6 +81,9 @@
# Hylics 2
/worlds/hylics2/ @TRPG0
# Inscryption
/worlds/inscryption/ @DrBibop @Glowbuzz
# Kirby's Dream Land 3
/worlds/kdl3/ @Silvris

158
worlds/inscryption/Items.py Normal file
View File

@ -0,0 +1,158 @@
from BaseClasses import ItemClassification
from typing import TypedDict, List
from BaseClasses import Item
base_id = 147000
class InscryptionItem(Item):
name: str = "Inscryption"
class ItemDict(TypedDict):
name: str
count: int
classification: ItemClassification
act1_items: List[ItemDict] = [
{'name': "Stinkbug Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Stunted Wolf Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Wardrobe Key",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Skink Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Ant Cards",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Caged Wolf Card",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Squirrel Totem Head",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Dagger",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Film Roll",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Ring",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Magnificus Eye",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Oil Painting's Clover Plant",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Extra Candle",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Bee Figurine",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Greater Smoke",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Angler Hook",
'count': 1,
'classification': ItemClassification.useful}
]
act2_items: List[ItemDict] = [
{'name': "Camera Replica",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Pile Of Meat",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Epitaph Piece",
'count': 9,
'classification': ItemClassification.progression},
{'name': "Epitaph Pieces",
'count': 3,
'classification': ItemClassification.progression},
{'name': "Monocle",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Bone Lord Femur",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Bone Lord Horn",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Bone Lord Holo Key",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Mycologists Holo Key",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Ancient Obol",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Great Kraken Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Drowned Soul Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Salmon Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Dock's Clover Plant",
'count': 1,
'classification': ItemClassification.useful}
]
act3_items: List[ItemDict] = [
{'name': "Extra Battery",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Nano Armor Generator",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Mrs. Bomb's Remote",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Inspectometer Battery",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Gems Module",
'count': 1,
'classification': ItemClassification.progression},
{'name': "Lonely Wizbot Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Fishbot Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Ourobot Card",
'count': 1,
'classification': ItemClassification.useful},
{'name': "Holo Pelt",
'count': 5,
'classification': ItemClassification.progression},
{'name': "Quill",
'count': 1,
'classification': ItemClassification.progression},
]
filler_items: List[ItemDict] = [
{'name': "Currency",
'count': 1,
'classification': ItemClassification.filler},
{'name': "Card Pack",
'count': 1,
'classification': ItemClassification.filler}
]

View File

@ -0,0 +1,127 @@
from typing import Dict, List
from BaseClasses import Location
base_id = 147000
class InscryptionLocation(Location):
game: str = "Inscryption"
act1_locations = [
"Act 1 - Boss Prospector",
"Act 1 - Boss Angler",
"Act 1 - Boss Trapper",
"Act 1 - Boss Leshy",
"Act 1 - Safe",
"Act 1 - Clock Main Compartment",
"Act 1 - Clock Upper Compartment",
"Act 1 - Dagger",
"Act 1 - Wardrobe Drawer 1",
"Act 1 - Wardrobe Drawer 2",
"Act 1 - Wardrobe Drawer 3",
"Act 1 - Wardrobe Drawer 4",
"Act 1 - Magnificus Eye",
"Act 1 - Painting 1",
"Act 1 - Painting 2",
"Act 1 - Painting 3",
"Act 1 - Greater Smoke"
]
act2_locations = [
"Act 2 - Boss Leshy",
"Act 2 - Boss Magnificus",
"Act 2 - Boss Grimora",
"Act 2 - Boss P03",
"Act 2 - Battle Prospector",
"Act 2 - Battle Angler",
"Act 2 - Battle Trapper",
"Act 2 - Battle Sawyer",
"Act 2 - Battle Royal",
"Act 2 - Battle Kaycee",
"Act 2 - Battle Goobert",
"Act 2 - Battle Pike Mage",
"Act 2 - Battle Lonely Wizard",
"Act 2 - Battle Inspector",
"Act 2 - Battle Melter",
"Act 2 - Battle Dredger",
"Act 2 - Dock Chest",
"Act 2 - Forest Cabin Chest",
"Act 2 - Forest Meadow Chest",
"Act 2 - Cabin Wardrobe Drawer",
"Act 2 - Cabin Safe",
"Act 2 - Crypt Casket 1",
"Act 2 - Crypt Casket 2",
"Act 2 - Crypt Well",
"Act 2 - Tower Chest 1",
"Act 2 - Tower Chest 2",
"Act 2 - Tower Chest 3",
"Act 2 - Tentacle",
"Act 2 - Factory Trash Can",
"Act 2 - Factory Drawer 1",
"Act 2 - Factory Drawer 2",
"Act 2 - Factory Chest 1",
"Act 2 - Factory Chest 2",
"Act 2 - Factory Chest 3",
"Act 2 - Factory Chest 4",
"Act 2 - Ancient Obol",
"Act 2 - Bone Lord Femur",
"Act 2 - Bone Lord Horn",
"Act 2 - Bone Lord Holo Key",
"Act 2 - Mycologists Holo Key",
"Act 2 - Camera Replica",
"Act 2 - Clover",
"Act 2 - Monocle",
"Act 2 - Epitaph Piece 1",
"Act 2 - Epitaph Piece 2",
"Act 2 - Epitaph Piece 3",
"Act 2 - Epitaph Piece 4",
"Act 2 - Epitaph Piece 5",
"Act 2 - Epitaph Piece 6",
"Act 2 - Epitaph Piece 7",
"Act 2 - Epitaph Piece 8",
"Act 2 - Epitaph Piece 9"
]
act3_locations = [
"Act 3 - Boss Photographer",
"Act 3 - Boss Archivist",
"Act 3 - Boss Unfinished",
"Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists",
"Act 3 - Bone Lord Room",
"Act 3 - Shop Holo Pelt",
"Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt",
"Act 3 - Crypt Holo Pelt",
"Act 3 - Tower Holo Pelt",
"Act 3 - Trader 1",
"Act 3 - Trader 2",
"Act 3 - Trader 3",
"Act 3 - Trader 4",
"Act 3 - Trader 5",
"Act 3 - Drawer 1",
"Act 3 - Drawer 2",
"Act 3 - Clock",
"Act 3 - Extra Battery",
"Act 3 - Nano Armor Generator",
"Act 3 - Chest",
"Act 3 - Goobert's Painting",
"Act 3 - Luke's File Entry 1",
"Act 3 - Luke's File Entry 2",
"Act 3 - Luke's File Entry 3",
"Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery",
"Act 3 - Gems Drone",
"Act 3 - The Great Transcendence",
"Act 3 - Well"
]
regions_to_locations: Dict[str, List[str]] = {
"Menu": [],
"Act 1": act1_locations,
"Act 2": act2_locations,
"Act 3": act3_locations,
"Epilogue": []
}

View File

@ -0,0 +1,137 @@
from dataclasses import dataclass
from Options import Toggle, Choice, DeathLinkMixin, StartInventoryPool, PerGameCommonOptions, DefaultOnToggle
class Act1DeathLinkBehaviour(Choice):
"""If DeathLink is enabled, determines what counts as a death in act 1. This affects deaths sent and received.
- Sacrificed: Send a death when sacrificed by Leshy. Receiving a death will extinguish all candles.
- Candle Extinguished: Send a death when a candle is extinguished. Receiving a death will extinguish a candle."""
display_name = "Act 1 Death Link Behaviour"
option_sacrificed = 0
option_candle_extinguished = 1
default = 0
class Goal(Choice):
"""Defines the goal to accomplish in order to complete the randomizer.
- Full Story In Order: Complete each act in order. You can return to previously completed acts.
- Full Story Any Order: Complete each act in any order. All acts are available from the start.
- First Act: Complete Act 1 by finding the New Game button. Great for a smaller scale randomizer."""
display_name = "Goal"
option_full_story_in_order = 0
option_full_story_any_order = 1
option_first_act = 2
default = 0
class RandomizeCodes(Toggle):
"""Randomize codes and passwords in the game (clocks, safes, etc.)"""
display_name = "Randomize Codes"
class RandomizeDeck(Choice):
"""Randomize cards in your deck into new cards.
Disable: Disable the feature.
- Every Encounter Within Same Type: Randomize cards within the same type every encounter (keep rarity/scrybe type).
- Every Encounter Any Type: Randomize cards into any possible card every encounter.
- Starting Only: Only randomize cards given at the beginning of runs and acts."""
display_name = "Randomize Deck"
option_disable = 0
option_every_encounter_within_same_type = 1
option_every_encounter_any_type = 2
option_starting_only = 3
default = 0
class RandomizeSigils(Choice):
"""Randomize sigils printed on the cards into new sigils every encounter.
- Disable: Disable the feature.
- Randomize Addons: Only randomize sigils added from sacrifices or other means.
- Randomize All: Randomize all sigils."""
display_name = "Randomize Abilities"
option_disable = 0
option_randomize_addons = 1
option_randomize_all = 2
default = 0
class OptionalDeathCard(Choice):
"""Add a moment after death in act 1 where you can decide to create a death card or not.
- Disable: Disable the feature.
- Always On: The choice is always offered after losing all candles.
- DeathLink Only: The choice is only offered after receiving a DeathLink event."""
display_name = "Optional Death Card"
option_disable = 0
option_always_on = 1
option_deathlink_only = 2
default = 2
class SkipTutorial(DefaultOnToggle):
"""Skips the first few tutorial runs of act 1. Bones are available from the start."""
display_name = "Skip Tutorial"
class SkipEpilogue(Toggle):
"""Completes the goal as soon as the required acts are completed without the need of completing the epilogue."""
display_name = "Skip Epilogue"
class EpitaphPiecesRandomization(Choice):
"""Determines how epitaph pieces in act 2 are randomized. This can affect your chances of getting stuck.
- All Pieces: Randomizes all nine pieces as their own item.
- In Groups: Randomizes pieces in groups of three.
- As One Item: Group all nine pieces as a single item."""
display_name = "Epitaph Pieces Randomization"
option_all_pieces = 0
option_in_groups = 1
option_as_one_item = 2
default = 0
class PaintingChecksBalancing(Choice):
"""Generation options for the second and third painting checks in act 1.
- None: Adds no progression logic to these painting checks. They will all count as sphere 1 (early game checks).
- Balanced: Adds rules to these painting checks. Early game items are less likely to appear into these paintings.
- Force Filler: For when you dislike doing these last two paintings. Their checks will only contain filler items."""
display_name = "Painting Checks Balancing"
option_none = 0
option_balanced = 1
option_force_filler = 2
default = 1
@dataclass
class InscryptionOptions(DeathLinkMixin, PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
act1_death_link_behaviour: Act1DeathLinkBehaviour
goal: Goal
randomize_codes: RandomizeCodes
randomize_deck: RandomizeDeck
randomize_sigils: RandomizeSigils
optional_death_card: OptionalDeathCard
skip_tutorial: SkipTutorial
skip_epilogue: SkipEpilogue
epitaph_pieces_randomization: EpitaphPiecesRandomization
painting_checks_balancing: PaintingChecksBalancing

View File

@ -0,0 +1,14 @@
from typing import Dict, List
inscryption_regions_all: Dict[str, List[str]] = {
"Menu": ["Act 1", "Act 2", "Act 3", "Epilogue"],
"Act 1": [],
"Act 2": [],
"Act 3": [],
"Epilogue": []
}
inscryption_regions_act_1: Dict[str, List[str]] = {
"Menu": ["Act 1"],
"Act 1": []
}

181
worlds/inscryption/Rules.py Normal file
View File

@ -0,0 +1,181 @@
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

View File

@ -0,0 +1,144 @@
from .Options import InscryptionOptions, Goal, EpitaphPiecesRandomization, PaintingChecksBalancing
from .Items import act1_items, act2_items, act3_items, filler_items, base_id, InscryptionItem, ItemDict
from .Locations import act1_locations, act2_locations, act3_locations, regions_to_locations
from .Regions import inscryption_regions_all, inscryption_regions_act_1
from typing import Dict, Any
from . import Rules
from BaseClasses import Region, Item, Tutorial, ItemClassification
from worlds.AutoWorld import World, WebWorld
class InscrypWeb(WebWorld):
theme = "dirt"
guide_en = Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Inscryption Archipelago Multiworld",
"English",
"setup_en.md",
"setup/en",
["DrBibop"]
)
guide_fr = Tutorial(
"Multiworld Setup Guide",
"Un guide pour configurer Inscryption Archipelago Multiworld",
"Français",
"setup_fr.md",
"setup/fr",
["Glowbuzz"]
)
tutorials = [guide_en, guide_fr]
bug_report_page = "https://github.com/DrBibop/Archipelago_Inscryption/issues"
class InscryptionWorld(World):
"""
Inscryption is an inky black card-based odyssey that blends the deckbuilding roguelike,
escape-room style puzzles, and psychological horror into a blood-laced smoothie.
Darker still are the secrets inscrybed upon the cards...
"""
game = "Inscryption"
web = InscrypWeb()
options_dataclass = InscryptionOptions
options: InscryptionOptions
all_items = act1_items + act2_items + act3_items + filler_items
item_name_to_id = {item["name"]: i + base_id for i, item in enumerate(all_items)}
all_locations = act1_locations + act2_locations + act3_locations
location_name_to_id = {location: i + base_id for i, location in enumerate(all_locations)}
required_epitaph_pieces_count = 9
required_epitaph_pieces_name = "Epitaph Piece"
def generate_early(self) -> None:
self.all_items = [item.copy() for item in self.all_items]
if self.options.epitaph_pieces_randomization == EpitaphPiecesRandomization.option_all_pieces:
self.required_epitaph_pieces_name = "Epitaph Piece"
self.required_epitaph_pieces_count = 9
elif self.options.epitaph_pieces_randomization == EpitaphPiecesRandomization.option_in_groups:
self.required_epitaph_pieces_name = "Epitaph Pieces"
self.required_epitaph_pieces_count = 3
else:
self.required_epitaph_pieces_name = "Epitaph Pieces"
self.required_epitaph_pieces_count = 1
if self.options.painting_checks_balancing == PaintingChecksBalancing.option_balanced:
self.all_items[6]["classification"] = ItemClassification.progression
self.all_items[11]["classification"] = ItemClassification.progression
if self.options.painting_checks_balancing == PaintingChecksBalancing.option_force_filler \
and self.options.goal == Goal.option_first_act:
self.all_items[3]["classification"] = ItemClassification.filler
if self.options.epitaph_pieces_randomization != EpitaphPiecesRandomization.option_all_pieces:
self.all_items[len(act1_items) + 3]["count"] = self.required_epitaph_pieces_count
def get_filler_item_name(self) -> str:
return self.random.choice(filler_items)["name"]
def create_item(self, name: str) -> Item:
item_id = self.item_name_to_id[name]
item_data = self.all_items[item_id - base_id]
return InscryptionItem(name, item_data["classification"], item_id, self.player)
def create_items(self) -> None:
nb_items_added = 0
useful_items = self.all_items.copy()
if self.options.goal != Goal.option_first_act:
useful_items = [item for item in useful_items
if not any(filler_item["name"] == item["name"] for filler_item in filler_items)]
if self.options.epitaph_pieces_randomization == EpitaphPiecesRandomization.option_all_pieces:
useful_items.pop(len(act1_items) + 3)
else:
useful_items.pop(len(act1_items) + 2)
else:
useful_items = [item for item in useful_items
if any(act1_item["name"] == item["name"] for act1_item in act1_items)]
for item in useful_items:
for _ in range(item["count"]):
new_item = self.create_item(item["name"])
self.multiworld.itempool.append(new_item)
nb_items_added += 1
filler_count = len(self.all_locations if self.options.goal != Goal.option_first_act else act1_locations)
filler_count -= nb_items_added
for i in range(filler_count):
index = i % len(filler_items)
filler_item = filler_items[index]
new_item = self.create_item(filler_item["name"])
self.multiworld.itempool.append(new_item)
def create_regions(self) -> None:
used_regions = inscryption_regions_all if self.options.goal != Goal.option_first_act \
else inscryption_regions_act_1
for region_name in used_regions.keys():
self.multiworld.regions.append(Region(region_name, self.player, self.multiworld))
for region_name, region_connections in used_regions.items():
region = self.get_region(region_name)
region.add_exits(region_connections)
region.add_locations({
location: self.location_name_to_id[location] for location in regions_to_locations[region_name]
})
def set_rules(self) -> None:
Rules.InscryptionRules(self).set_all_rules()
def fill_slot_data(self) -> Dict[str, Any]:
return self.options.as_dict(
"death_link",
"act1_death_link_behaviour",
"goal",
"randomize_codes",
"randomize_deck",
"randomize_sigils",
"optional_death_card",
"skip_tutorial",
"skip_epilogue",
"epitaph_pieces_randomization"
)

View File

@ -0,0 +1,22 @@
# Inscryption
## Where is the options page?
You can configure your player options with the Inscryption options page. [Click here](../player-options) to start configuring them to your liking.
## What does randomization do to this game?
Due to the nature of the randomizer, you are allowed to return to a previous act you've previously completed if there are location checks you've missed. The "New Game" option is replaced with a "Chapter Select" option and is enabled after you beat act 1. If you prefer, you can also make all acts available from the start by changing the goal option. All items that you can find lying around, in containers, or from puzzles are randomized and replaced with location checks. Boss fights from all acts and battles from act 2 also count as location checks.
## What is the goal of Inscryption when randomized?
By default, the goal is considered reached once you open the OLD_DATA file. This means playing through all three acts in order and the epilogue. You can change the goal option to instead complete all acts in any order or simply complete act 1.
## Which items can be in another player's world?
All key items necessary for progression such as the film roll, the dagger, Grimora's epitaphs, etc. Unique cards that aren't randomly found in the base game (e.g. talking cards) are also included. For filler items, you can receive currency which will be added to every act's bank or card packs that you can open at any time when inspecting your deck.
## What does another world's item look like in Inscryption?
Items from other worlds usually take the appearance of a normal card from the current act you're playing. The card's name contains the item that will be sent when picked up and its portrait is the Archipelago logo (a ring of six circles). Picking up these cards does not add them to your deck.
## When the player receives an item, what happens?
The item is instantly granted to you. A yellow message appears in the Archipelago logs at the top-right of your screen. An audio cue is also played. If the item received is a holdable item (wardrobe key, inspectometer battery, gems module), the item will be placed where you would usually collect it in a vanilla playthrough (safe, inspectometer, drone).
## How many items can I find or receive in my world?
By default, if all three acts are played, there are **100** randomized locations in your world and **100** of your items shuffled in the multiworld. There are **17** locations in act 1 (this will be the total amount if you decide to only play act 1), **52** locations in act 2, and **31** locations in act 3.

View File

@ -0,0 +1,65 @@
# Inscryption Randomizer Setup Guide
## Required Software
- [Inscryption](https://store.steampowered.com/app/1092790/Inscryption/)
- For easy setup (recommended):
- [r2modman](https://inscryption.thunderstore.io/package/ebkr/r2modman/) OR [Thunderstore Mod Manager](https://www.overwolf.com/app/Thunderstore-Thunderstore_Mod_Manager)
- For manual setup:
- [BepInEx pack for Inscryption](https://inscryption.thunderstore.io/package/BepInEx/BepInExPack_Inscryption/)
- [ArchipelagoMod](https://inscryption.thunderstore.io/package/Ballin_Inc/ArchipelagoMod/)
## Installation
Before starting the installation process, here's what you should know:
- Only install the mods mentioned in this guide if you want a guaranteed smooth experience! Other mods were NOT tested with ArchipelagoMod and could cause unwanted issues.
- The ArchipelagoMod uses its own save file system when playing, but for safety measures, back up your save file by going to your Inscryption installation directory and copy the `SaveFile.gwsave` file to another folder.
- It is strongly recommended to use a mod manager if you want a quicker and easier installation process, but if you don't like installing extra software and are comfortable moving files around, you can refer to the manual setup guide instead.
### Easy setup (mod manager)
1. Download [r2modman](https://inscryption.thunderstore.io/package/ebkr/r2modman/) using the "Manual Download" button, then install it using the executable in the downloaded zip package (You can also use [Thunderstore Mod Manager](https://www.overwolf.com/app/Thunderstore-Thunderstore_Mod_Manager) which works the same, but it requires [Overwolf](https://www.overwolf.com/))
2. Open the mod manager and select Inscryption in the game selection screen.
3. Select the default profile or create a new one.
4. Open the `Online` tab on the left, then search for `ArchipelagoMod`.
5. Expand ArchipelagoMod and click the `Download` button to install the latest version and all its dependencies.
6. Click `Start Modded` to open the game with the mods (a console should appear if everything was done correctly).
### Manual setup
1. Download the following mods using the `Manual Download` button:
- [BepInEx pack for Inscryption](https://inscryption.thunderstore.io/package/BepInEx/BepInExPack_Inscryption/)
- [ArchipelagoMod](https://inscryption.thunderstore.io/package/Ballin_Inc/ArchipelagoMod/)
2. Open your Inscryption installation directory. On Steam, you can find it easily by right-clicking the game and clicking `Manage` > `Browse local files`.
3. Open the BepInEx pack zip file, then open the `BepInExPack_Inscryption` folder.
4. Drag all folders and files located inside the `BepInExPack_Inscryption` folder and drop them in your Inscryption directory.
5. Open the `BepInEx` folder in your Inscryption directory.
6. Open the ArchipelagoMod zip file.
7. Drag and drop the `plugins` folder in the `BepInEx` folder to fuse with the existing `plugins` folder.
8. Open the game normally to play with mods (if BepInEx was installed correctly, a console should appear).
## Joining a new MultiWorld Game
1. After opening the game, you should see a new menu for browsing and creating save files.
2. Click on the `New Game` button, then write a unique name for your save file.
3. On the next screen, enter the information needed to connect to the MultiWorld server, then press the `Connect` button.
4. If successful, the status on the top-right will change to "Connected". If not, a red error message will appear.
5. After connecting to the server and receiving items, the game menu will appear.
## Continuing a MultiWorld Game
1. After opening the game, you should see a list of your save files and a button to add a new one.
2. Find the save file you want to use, then click its `Play` button.
3. On the next screen, the input fields will be filled with the information you've written previously. You can adjust some fields if needed, then press the `Connect` button.
4. If successful, the status on the top-right will change to "connected". If not, a red error message will appear.
5. After connecting to the server and receiving items, the game menu will appear.
## Troubleshooting
### The game opens normally without the new menu.
If the new menu mentioned previously doesn't appear, it can be one of two issues:
- If there was no console appearing when opening the game, this means the mods didn't load correctly. Here's what you can try:
- If you are using the mod manager, make sure to open it and press `Start Modded`. Opening the game normally from Steam won't load any mods.
- Check if the mod manager correctly found the game path. In the mod manager, click `Settings` then go to the `Locations` tab. Make sure the path listed under `Change Inscryption directory` is correct. You can verify the real path if you right-click the game on steam and click `Manage` > `Browse local files`. If the path is wrong, click that setting and change the path.
- If you installed the mods manually, this usually means BepInEx was not correctly installed. Make sure to read the installation guide carefully.
- If there is still no console when opening the game modded, try asking in the [Archipelago Discord Server](https://discord.gg/8Z65BR2) for help.
- If there is a console, this means the mods loaded but the ArchipelagoMod wasn't found or had errors while loading.
- Look in the console and make sure you can find a message about ArchipelagoMod being loaded.
- If you see any red text, there was an error. Report the issue in the [Archipelago Discord Server](https://discord.gg/8Z65BR2) or create an issue in our [GitHub](https://github.com/DrBibop/Archipelago_Inscryption/issues).
### I'm getting a different issue.
You can ask for help in the [Archipelago Discord Server](https://discord.gg/8Z65BR2) or, if you think you've found a bug with the mod, create an issue in our [GitHub](https://github.com/DrBibop/Archipelago_Inscryption/issues).

View File

@ -0,0 +1,67 @@
# Guide d'Installation de Inscryption Randomizer
## Logiciel Exigé
- [Inscryption](https://store.steampowered.com/app/1092790/Inscryption/)
- Pour une installation facile (recommandé):
- [r2modman](https://inscryption.thunderstore.io/package/ebkr/r2modman/) OU [Thunderstore Mod Manager](https://www.overwolf.com/app/Thunderstore-Thunderstore_Mod_Manager)
- Pour une installation manuelle:
- [BepInEx pack for Inscryption](https://inscryption.thunderstore.io/package/BepInEx/BepInExPack_Inscryption/)
- [MonoMod Loader for Inscryption](https://inscryption.thunderstore.io/package/BepInEx/MonoMod_Loader_Inscryption/)
- [Inscryption API](https://inscryption.thunderstore.io/package/API_dev/API/)
- [ArchipelagoMod](https://inscryption.thunderstore.io/package/Ballin_Inc/ArchipelagoMod/)
## Installation
Avant de commencer le processus d'installation, voici ce que vous deviez savoir:
- Installez uniquement les mods mentionnés dans ce guide si vous souhaitez une expérience stable! Les autres mods n'ont PAS été testés avec ArchipelagoMod et peuvent provoquer des problèmes.
- ArchipelagoMod utilise son propre système de sauvegarde lorsque vous jouez, mais pour des raisons de sécurité, sauvegardez votre fichier de sauvegarde en accédant à votre répertoire d'installation Inscryption et copiez le fichier `SaveFile.gwsave` dans un autre dossier.
- Il est fortement recommandé d'utiliser un mod manager si vous souhaitez avoir un processus d'installation plus rapide et plus facile, mais si vous n'aimez pas installer de logiciels supplémentaires et que vous êtes à l'aise pour déplacer des fichiers, vous pouvez vous référer au guide de configuration manuelle.
### Installation facile (mod manager)
1. Téléchargez [r2modman](https://inscryption.thunderstore.io/package/ebkr/r2modman/) à l'aide du bouton `Manual Download`, puis installez-le à l'aide de l'exécutable contenu dans le zip téléchargé (vous pouvez également utiliser [Thunderstore Mod Manager](https://www.overwolf.com/app/Thunderstore-Thunderstore_Mod_Manager) qui fonctionne de la même manière, mais cela nécessite [Overwolf](https://www.overwolf.com/))
2. Ouvrez le mod manager et sélectionnez Inscryption dans l'écran de sélection de jeu.
3. Sélectionnez le profil par défaut ou créez-en un nouveau.
4. Ouvrez l'onglet `Online` à gauche, puis recherchez `ArchipelagoMod`.
5. Développez ArchipelagoMod et cliquez sur le bouton `Download` pour installer la dernière version disponible et toutes ses dépendances.
6. Cliquez sur `Start Modded` pour ouvrir le jeu avec les mods (une console devrait apparaître si tout a été fait correctement).
### Installation manuelle
1. Téléchargez les mods suivants en utilisant le bouton `Manual Download`:
- [BepInEx pack for Inscryption](https://inscryption.thunderstore.io/package/BepInEx/BepInExPack_Inscryption/)
- [ArchipelagoMod](https://inscryption.thunderstore.io/package/Ballin_Inc/ArchipelagoMod/)
2. Ouvrez votre dossier d'installation d'Inscryption. Sur Steam, vous pouvez le trouver facilement en faisant un clic droit sur le jeu et en cliquant sur `Gérer` > `Parcourir les fichiers locaux`.
3. Ouvrez le fichier zip du pack BepInEx, puis ouvrez le dossier `BepInExPack_Inscryption`.
4. Prenez tous les dossiers et fichiers situés dans le dossier `BepInExPack_Inscryption` et déposez-les dans votre dossier Inscryption.
5. Ouvrez le dossier `BepInEx` dans votre dossier Inscryption.
6. Ouvrez le fichier zip d'ArchipelagoMod.
7. Prenez et déposez le dossier `plugins` dans le dossier `BepInEx` pour fusionner avec le dossier `plugins` existant.
8. Ouvrez le jeu normalement pour jouer avec les mods (si BepInEx a été correctement installé, une console devrait apparaitre).
## Rejoindre un nouveau MultiWorld
1. Après avoir ouvert le jeu, vous devriez voir un nouveau menu pour parcourir et créer des fichiers de sauvegarde.
2. Cliquez sur le bouton `New Game`, puis écrivez un nom unique pour votre fichier de sauvegarde.
3. Sur l'écran suivant, saisissez les informations nécessaires pour vous connecter au serveur MultiWorld, puis appuyez sur le bouton `Connect`.
4. En cas de succès, l'état de connexion en haut à droite changera pour "Connected". Sinon, un message d'erreur rouge apparaîtra.
5. Après s'être connecté au server et avoir reçu les items, le menu du jeu apparaîtra.
## Poursuivre une session MultiWorld
1. Après avoir ouvert le jeu, vous devriez voir une liste de vos fichiers de sauvegarde et un bouton pour en ajouter un nouveau.
2. Choisissez le fichier de sauvegarde que vous souhaitez utiliser, puis cliquez sur son bouton `Play`.
3. Sur l'écran suivant, les champs de texte seront remplis avec les informations que vous avez écrites précédemment. Vous pouvez ajuster certains champs si nécessaire, puis appuyer sur le bouton `Connect`.
4. En cas de succès, l'état de connexion en haut à droite changera pour "Connected". Sinon, un message d'erreur rouge apparaîtra.
5. Après s'être connecté au server et avoir reçu les items, le menu du jeu apparaîtra.
## Dépannage
### Le jeu ouvre normalement sans nouveau menu.
Si le nouveau menu mentionné précédemment n'apparaît pas, c'est peut-être l'un des deux problèmes suivants:
- Si aucune console n'apparait à l'ouverture du jeu, cela signifie que les mods ne se sont pas chargés correctement. Voici ce que vous pouvez essayer:
- Si vous utilisez le mod manager, assurez-vous de l'ouvrir et d'appuyer sur `Start Modded`. Ouvrir le jeu normalement depuis Steam ne chargera aucun mod.
- Vérifiez si le mod manager a correctement trouvé le répertoire du jeu. Dans le mod manager, cliquez sur `Settings` puis allez dans l'onglet `Locations`. Assurez-vous que le répertoire sous `Change Inscryption directory` est correct. Vous pouvez vérifier le répertoire correct si vous faites un clic droit sur le jeu Inscription sur Steam et cliquez sur `Gérer` > `Parcourir les fichiers locaux`. Si le répertoire est erroné, cliquez sur ce paramètre et modifiez le répertoire.
- Si vous avez installé les mods manuellement, cela signifie généralement que BepInEx n'a pas été correctement installé. Assurez-vous de lire attentivement le guide d'installation.
- S'il n'y a toujours pas de console lors de l'ouverture du jeu modifié, essayez de demander de l'aide sur [Archipelago Discord Server](https://discord.gg/8Z65BR2).
- S'il y a une console, cela signifie que les mods ont été chargés, mais que ArchipelagoMod n'a pas été trouvé ou a eu des erreurs lors du chargement.
- Regardez dans la console et assurez-vous que vous trouvez un message concernant le chargement d'ArchipelagoMod.
- Si vous voyez du texte rouge, il y a eu une erreur. Signalez le problème dans [Archipelago Discord Server](https://discord.gg/8Z65BR2) ou dans notre [GitHub](https://github.com/DrBibop/Archipelago_Inscryption/issues).
### J'ai un autre problème.
Vous pouvez demander de l'aide sur [le serveur Discord d'Archipelago](https://discord.gg/8Z65BR2) ou, si vous pensez avoir trouvé un bug avec le mod, signalez-le dans notre [GitHub](https://github.com/DrBibop/Archipelago_Inscryption/issues).

View File

@ -0,0 +1,221 @@
from . import InscryptionTestBase
class AccessTestGeneral(InscryptionTestBase):
def test_dagger(self) -> None:
self.assertAccessDependency(["Act 1 - Magnificus Eye"], [["Dagger"]])
def test_caged_wolf(self) -> None:
self.assertAccessDependency(["Act 1 - Dagger"], [["Caged Wolf Card"]])
def test_magnificus_eye(self) -> None:
self.assertAccessDependency(["Act 1 - Clock Main Compartment"], [["Magnificus Eye"]])
def test_wardrobe_key(self) -> None:
self.assertAccessDependency(
["Act 1 - Wardrobe Drawer 1", "Act 1 - Wardrobe Drawer 2",
"Act 1 - Wardrobe Drawer 3", "Act 1 - Wardrobe Drawer 4"],
[["Wardrobe Key"]]
)
def test_ancient_obol(self) -> None:
self.assertAccessDependency(
["Act 2 - Bone Lord Femur", "Act 2 - Bone Lord Horn", "Act 2 - Bone Lord Holo Key"],
[["Ancient Obol"]]
)
def test_holo_pelt(self) -> None:
self.assertAccessDependency(
["Act 3 - Trader 1", "Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5"],
[["Holo Pelt"]]
)
def test_inspectometer_battery(self) -> None:
self.assertAccessDependency(
["Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Trader 1", "Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5",
"Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt", "Act 3 - Forest Holo Pelt", "Act 3 - Clock",
"Act 3 - Crypt Holo Pelt", "Act 3 - Gems Drone", "Act 3 - Nano Armor Generator", "Act 3 - Extra Battery",
"Act 3 - Tower Holo Pelt", "Act 3 - The Great Transcendence", "Act 3 - Boss Mycologists",
"Act 3 - Bone Lord Room", "Act 3 - Well", "Act 3 - Luke's File Entry 1", "Act 3 - Luke's File Entry 2",
"Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4", "Act 3 - Goobert's Painting"],
[["Inspectometer Battery"]]
)
def test_gem_drone(self) -> None:
self.assertAccessDependency(
["Act 3 - Boss Unfinished", "Act 3 - Boss G0lly", "Act 3 - Trader 1", "Act 3 - Trader 2",
"Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Shop Holo Pelt", "Act 3 - Clock",
"Act 3 - Tower Holo Pelt", "Act 3 - The Great Transcendence", "Act 3 - Luke's File Entry 4",
"Act 3 - Boss Mycologists", "Act 3 - Nano Armor Generator", "Act 3 - Goobert's Painting"],
[["Gems Module"]]
)
def test_mycologists_holo_key(self) -> None:
self.assertAccessDependency(
["Act 3 - Boss Mycologists"],
[["Mycologists Holo Key"]]
)
def test_bone_lord_holo_key(self) -> None:
self.assertAccessDependency(
["Act 3 - Bone Lord Room"],
[["Bone Lord Holo Key"]]
)
def test_quill(self) -> None:
self.assertAccessDependency(
["Act 3 - Boss Archivist", "Act 3 - Luke's File Entry 1", "Act 3 - Luke's File Entry 2",
"Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4", "Act 3 - The Great Transcendence",
"Act 3 - Boss Mycologists"],
[["Quill"]]
)
class AccessTestOrdered(InscryptionTestBase):
options = {
"goal": 0,
}
def test_film_roll(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Prospector", "Act 2 - Battle Angler", "Act 2 - Battle Trapper", "Act 2 - Battle Sawyer",
"Act 2 - Battle Royal", "Act 2 - Battle Kaycee", "Act 2 - Battle Pike Mage", "Act 2 - Battle Goobert",
"Act 2 - Battle Lonely Wizard", "Act 2 - Battle Inspector", "Act 2 - Battle Melter",
"Act 2 - Battle Dredger", "Act 2 - Tower Chest 1", "Act 2 - Tower Chest 2", "Act 2 - Tower Chest 3",
"Act 2 - Forest Meadow Chest", "Act 2 - Forest Cabin Chest", "Act 2 - Cabin Wardrobe Drawer",
"Act 2 - Cabin Safe", "Act 2 - Crypt Casket 1", "Act 2 - Crypt Casket 2", "Act 2 - Crypt Well",
"Act 2 - Camera Replica", "Act 2 - Clover", "Act 2 - Epitaph Piece 1", "Act 2 - Epitaph Piece 2",
"Act 2 - Epitaph Piece 3", "Act 2 - Epitaph Piece 4", "Act 2 - Epitaph Piece 5", "Act 2 - Epitaph Piece 6",
"Act 2 - Epitaph Piece 7", "Act 2 - Epitaph Piece 8", "Act 2 - Epitaph Piece 9", "Act 2 - Dock Chest",
"Act 2 - Tentacle", "Act 2 - Factory Trash Can", "Act 2 - Factory Drawer 1",
"Act 2 - Ancient Obol", "Act 2 - Factory Drawer 2", "Act 2 - Factory Chest 1", "Act 2 - Factory Chest 2",
"Act 2 - Factory Chest 3", "Act 2 - Factory Chest 4", "Act 2 - Monocle", "Act 2 - Boss Leshy",
"Act 2 - Boss Grimora", "Act 2 - Boss Magnificus", "Act 2 - Boss P03", "Act 2 - Mycologists Holo Key",
"Act 2 - Bone Lord Femur", "Act 2 - Bone Lord Horn", "Act 2 - Bone Lord Holo Key",
"Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists", "Act 3 - Bone Lord Room", "Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt", "Act 3 - Crypt Holo Pelt", "Act 3 - Tower Holo Pelt", "Act 3 - Trader 1",
"Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Drawer 1",
"Act 3 - Drawer 2", "Act 3 - Clock", "Act 3 - Extra Battery", "Act 3 - Nano Armor Generator",
"Act 3 - Chest", "Act 3 - Goobert's Painting", "Act 3 - Luke's File Entry 1", "Act 3 - Gems Drone",
"Act 3 - Luke's File Entry 2", "Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery", "Act 3 - Gems Drone", "Act 3 - The Great Transcendence", "Act 3 - Well"],
[["Film Roll"]]
)
def test_epitaphs_and_forest_items(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Prospector", "Act 2 - Battle Angler", "Act 2 - Battle Trapper",
"Act 2 - Battle Pike Mage", "Act 2 - Battle Goobert", "Act 2 - Battle Lonely Wizard",
"Act 2 - Battle Inspector", "Act 2 - Battle Melter", "Act 2 - Battle Dredger",
"Act 2 - Tower Chest 1", "Act 2 - Tower Chest 2", "Act 2 - Tower Chest 3", "Act 2 - Forest Meadow Chest",
"Act 2 - Tentacle", "Act 2 - Factory Trash Can", "Act 2 - Factory Drawer 1", "Act 2 - Ancient Obol",
"Act 2 - Factory Drawer 2", "Act 2 - Factory Chest 1", "Act 2 - Factory Chest 2",
"Act 2 - Factory Chest 3", "Act 2 - Factory Chest 4", "Act 2 - Monocle", "Act 2 - Boss Leshy",
"Act 2 - Boss Grimora", "Act 2 - Boss Magnificus", "Act 2 - Boss P03", "Act 2 - Mycologists Holo Key",
"Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists", "Act 3 - Bone Lord Room", "Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt", "Act 3 - Crypt Holo Pelt", "Act 3 - Tower Holo Pelt", "Act 3 - Trader 1",
"Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Drawer 1",
"Act 3 - Drawer 2", "Act 3 - Clock", "Act 3 - Extra Battery", "Act 3 - Nano Armor Generator",
"Act 3 - Chest", "Act 3 - Goobert's Painting", "Act 3 - Luke's File Entry 1", "Act 3 - Gems Drone",
"Act 3 - Luke's File Entry 2", "Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery", "Act 3 - Gems Drone", "Act 3 - The Great Transcendence", "Act 3 - Well"],
[["Epitaph Piece", "Camera Replica", "Pile Of Meat"]]
)
def test_epitaphs(self) -> None:
self.assertAccessDependency(
["Act 2 - Boss Grimora",
"Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists", "Act 3 - Bone Lord Room", "Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt", "Act 3 - Crypt Holo Pelt", "Act 3 - Tower Holo Pelt", "Act 3 - Trader 1",
"Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Drawer 1",
"Act 3 - Drawer 2", "Act 3 - Clock", "Act 3 - Extra Battery", "Act 3 - Nano Armor Generator",
"Act 3 - Chest", "Act 3 - Goobert's Painting", "Act 3 - Luke's File Entry 1", "Act 3 - Gems Drone",
"Act 3 - Luke's File Entry 2", "Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery", "Act 3 - Gems Drone", "Act 3 - The Great Transcendence", "Act 3 - Well"],
[["Epitaph Piece"]]
)
def test_forest_items(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Prospector", "Act 2 - Battle Angler", "Act 2 - Battle Trapper",
"Act 2 - Boss Leshy", "Act 2 - Forest Meadow Chest",
"Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists", "Act 3 - Bone Lord Room", "Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt", "Act 3 - Crypt Holo Pelt", "Act 3 - Tower Holo Pelt", "Act 3 - Trader 1",
"Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Drawer 1",
"Act 3 - Drawer 2", "Act 3 - Clock", "Act 3 - Extra Battery", "Act 3 - Nano Armor Generator",
"Act 3 - Chest", "Act 3 - Goobert's Painting", "Act 3 - Luke's File Entry 1", "Act 3 - Gems Drone",
"Act 3 - Luke's File Entry 2", "Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery", "Act 3 - Gems Drone", "Act 3 - The Great Transcendence", "Act 3 - Well"],
[["Camera Replica", "Pile Of Meat"]]
)
def test_monocle(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Goobert", "Act 2 - Battle Pike Mage", "Act 2 - Battle Lonely Wizard",
"Act 2 - Boss Magnificus", "Act 2 - Tower Chest 2", "Act 2 - Tower Chest 3",
"Act 2 - Tentacle", "Act 2 - Ancient Obol", "Act 2 - Mycologists Holo Key",
"Act 3 - Boss Photographer", "Act 3 - Boss Archivist", "Act 3 - Boss Unfinished", "Act 3 - Boss G0lly",
"Act 3 - Boss Mycologists", "Act 3 - Bone Lord Room", "Act 3 - Shop Holo Pelt", "Act 3 - Middle Holo Pelt",
"Act 3 - Forest Holo Pelt", "Act 3 - Crypt Holo Pelt", "Act 3 - Tower Holo Pelt", "Act 3 - Trader 1",
"Act 3 - Trader 2", "Act 3 - Trader 3", "Act 3 - Trader 4", "Act 3 - Trader 5", "Act 3 - Drawer 1",
"Act 3 - Drawer 2", "Act 3 - Clock", "Act 3 - Extra Battery", "Act 3 - Nano Armor Generator",
"Act 3 - Chest", "Act 3 - Goobert's Painting", "Act 3 - Luke's File Entry 1", "Act 3 - Gems Drone",
"Act 3 - Luke's File Entry 2", "Act 3 - Luke's File Entry 3", "Act 3 - Luke's File Entry 4",
"Act 3 - Inspectometer Battery", "Act 3 - Gems Drone", "Act 3 - The Great Transcendence", "Act 3 - Well"],
[["Monocle"]]
)
class AccessTestUnordered(InscryptionTestBase):
options = {
"goal": 1,
}
def test_epitaphs_and_forest_items(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Prospector", "Act 2 - Battle Angler", "Act 2 - Battle Trapper",
"Act 2 - Battle Pike Mage", "Act 2 - Battle Goobert", "Act 2 - Battle Lonely Wizard",
"Act 2 - Battle Inspector", "Act 2 - Battle Melter", "Act 2 - Battle Dredger",
"Act 2 - Tower Chest 1", "Act 2 - Tower Chest 2", "Act 2 - Tower Chest 3", "Act 2 - Forest Meadow Chest",
"Act 2 - Tentacle", "Act 2 - Factory Trash Can", "Act 2 - Factory Drawer 1", "Act 2 - Ancient Obol",
"Act 2 - Factory Drawer 2", "Act 2 - Factory Chest 1", "Act 2 - Factory Chest 2",
"Act 2 - Factory Chest 3", "Act 2 - Factory Chest 4", "Act 2 - Monocle", "Act 2 - Boss Leshy",
"Act 2 - Boss Grimora", "Act 2 - Boss Magnificus", "Act 2 - Boss P03", "Act 2 - Mycologists Holo Key"],
[["Epitaph Piece", "Camera Replica", "Pile Of Meat"]]
)
def test_epitaphs(self) -> None:
self.assertAccessDependency(
["Act 2 - Boss Grimora"],
[["Epitaph Piece"]]
)
def test_forest_items(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Prospector", "Act 2 - Battle Angler", "Act 2 - Battle Trapper",
"Act 2 - Boss Leshy", "Act 2 - Forest Meadow Chest"],
[["Camera Replica", "Pile Of Meat"]]
)
def test_monocle(self) -> None:
self.assertAccessDependency(
["Act 2 - Battle Goobert", "Act 2 - Battle Pike Mage", "Act 2 - Battle Lonely Wizard",
"Act 2 - Boss Magnificus", "Act 2 - Tower Chest 2", "Act 2 - Tower Chest 3",
"Act 2 - Tentacle", "Act 2 - Ancient Obol", "Act 2 - Mycologists Holo Key"],
[["Monocle"]]
)
class AccessTestBalancedPaintings(InscryptionTestBase):
options = {
"painting_checks_balancing": 1,
}
def test_paintings(self) -> None:
self.assertAccessDependency(["Act 1 - Painting 2", "Act 1 - Painting 3"],
[["Oil Painting's Clover Plant", "Squirrel Totem Head"]])

View File

@ -0,0 +1,108 @@
from . import InscryptionTestBase
class GoalTestOrdered(InscryptionTestBase):
options = {
"goal": 0,
}
def test_beatable(self) -> None:
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.collect(item)
for i in range(9):
item = self.get_item_by_name("Epitaph Piece")
self.collect(item)
self.assertBeatable(True)
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.remove(item)
self.assertBeatable(False)
self.collect(item)
item = self.get_item_by_name("Epitaph Piece")
self.remove(item)
self.assertBeatable(False)
self.collect(item)
class GoalTestUnordered(InscryptionTestBase):
options = {
"goal": 1,
}
def test_beatable(self) -> None:
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.collect(item)
for i in range(9):
item = self.get_item_by_name("Epitaph Piece")
self.collect(item)
self.assertBeatable(True)
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.remove(item)
self.assertBeatable(False)
self.collect(item)
item = self.get_item_by_name("Epitaph Piece")
self.remove(item)
self.assertBeatable(False)
self.collect(item)
class GoalTestAct1(InscryptionTestBase):
options = {
"goal": 2,
}
def test_beatable(self) -> None:
self.assertBeatable(False)
film_roll = self.get_item_by_name("Film Roll")
self.collect(film_roll)
self.assertBeatable(True)
class GoalTestGroupedEpitaphs(InscryptionTestBase):
options = {
"epitaph_pieces_randomization": 1,
}
def test_beatable(self) -> None:
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.collect(item)
for i in range(3):
item = self.get_item_by_name("Epitaph Pieces")
self.collect(item)
self.assertBeatable(True)
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.remove(item)
self.assertBeatable(False)
self.collect(item)
item = self.get_item_by_name("Epitaph Pieces")
self.remove(item)
self.assertBeatable(False)
self.collect(item)
class GoalTestEpitaphsAsOne(InscryptionTestBase):
options = {
"epitaph_pieces_randomization": 2,
}
def test_beatable(self) -> None:
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.collect(item)
item = self.get_item_by_name("Epitaph Pieces")
self.collect(item)
self.assertBeatable(True)
for item_name in self.required_items_all_acts:
item = self.get_item_by_name(item_name)
self.remove(item)
self.assertBeatable(False)
self.collect(item)
item = self.get_item_by_name("Epitaph Pieces")
self.remove(item)
self.assertBeatable(False)
self.collect(item)

View File

@ -0,0 +1,7 @@
from test.bases import WorldTestBase
class InscryptionTestBase(WorldTestBase):
game = "Inscryption"
required_items_all_acts = ["Film Roll", "Camera Replica", "Pile Of Meat", "Monocle",
"Inspectometer Battery", "Gems Module", "Quill"]