Archipelago/worlds/messenger/options.py

240 lines
8.1 KiB
Python

from dataclasses import dataclass
from schema import And, Optional, Or, Schema
from Options import Choice, DeathLinkMixin, DefaultOnToggle, ItemsAccessibility, OptionDict, PerGameCommonOptions, \
PlandoConnections, Range, StartInventoryPool, Toggle, Visibility
from .portals import CHECKPOINTS, PORTALS, SHOP_POINTS
class MessengerAccessibility(ItemsAccessibility):
# defaulting to locations accessibility since items makes certain items self-locking
default = ItemsAccessibility.option_full
__doc__ = ItemsAccessibility.__doc__
class PortalPlando(PlandoConnections):
"""
Plando connections to be used with portal shuffle. Direction is ignored.
List of valid connections can be found here: https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/messenger/portals.py#L12.
The entering Portal should *not* have "Portal" appended.
For the exits, those in checkpoints and shops should just be the name of the spot, while portals should have " Portal" at the end.
Example:
- entrance: Riviere Turquoise
exit: Wingsuit
- entrance: Sunken Shrine
exit: Sunny Day
- entrance: Searing Crags
exit: Glacial Peak Portal
"""
portals = [f"{portal} Portal" for portal in PORTALS]
shop_points = [point for points in SHOP_POINTS.values() for point in points]
checkpoints = [point for points in CHECKPOINTS.values() for point in points]
portal_entrances = PORTALS
portal_exits = portals + shop_points + checkpoints
entrances = portal_entrances
exits = portal_exits
# for back compatibility. To later be replaced with transition plando
class HiddenPortalPlando(PortalPlando):
visibility = Visibility.none
entrances = PortalPlando.entrances
exits = PortalPlando.exits
class Logic(Choice):
"""
The level of logic to use when determining what locations in your world are accessible.
Normal: Can require damage boosts, but otherwise approachable for someone who has beaten the game.
Hard: Expects more knowledge and tighter execution. Has leashing, normal clips and much tighter d-boosting in logic.
"""
display_name = "Logic Level"
option_normal = 0
option_hard = 1
alias_oob = 1
alias_challenging = 1
class MegaShards(Toggle):
"""Whether mega shards should be item locations."""
display_name = "Shuffle Mega Time Shards"
class LimitedMovement(Toggle):
"""
Removes either rope dart or wingsuit from the itempool. Forces logic to at least hard and accessibility to minimal.
"""
display_name = "Limited Movement"
class EarlyMed(Toggle):
"""Guarantees meditation will be found early"""
display_name = "Early Meditation"
class AvailablePortals(Range):
"""Number of portals that are available from the start. Autumn Hills, Howling Grotto, and Glacial Peak are always available. If portal outputs are not randomized, Searing Crags will also be available."""
display_name = "Available Starting Portals"
range_start = 3
range_end = 6
default = 6
class ShufflePortals(Choice):
"""
Whether the portals lead to random places.
Entering a portal from its vanilla area will always lead to HQ, and will unlock it if relevant.
Supports plando.
None: Portals will take you where they're supposed to.
Shops: Portals can lead to any area except Music Box and Elemental Skylands, with each portal output guaranteed to not overlap with another portal's. Will only put you at a portal or a shop.
Checkpoints: Like Shops except checkpoints without shops are also valid drop points.
Anywhere: Like Checkpoints except it's possible for multiple portals to output to the same map.
"""
display_name = "Shuffle Portal Outputs"
option_none = 0
alias_off = 0
option_shops = 1
option_checkpoints = 2
option_anywhere = 3
class ShuffleTransitions(Choice):
"""
Whether the transitions between the levels should be randomized.
Supports plando.
None: Level transitions lead where they should.
Coupled: Returning through a transition will take you from whence you came.
Decoupled: Any level transition can take you to any other level transition.
"""
display_name = "Shuffle Level Transitions"
option_none = 0
alias_off = 0
option_coupled = 1
option_decoupled = 2
class Goal(Choice):
"""Requirement to finish the game. To win with the power seal hunt goal, you must enter the Music Box through the shop chest."""
display_name = "Goal"
option_open_music_box = 0
option_power_seal_hunt = 1
class MusicBox(DefaultOnToggle):
"""Whether the music box gauntlet needs to be done."""
display_name = "Music Box Gauntlet"
class NotesNeeded(Range):
"""How many notes are needed to access the Music Box."""
display_name = "Notes Needed"
range_start = 1
range_end = 6
default = range_end
class AmountSeals(Range):
"""Number of power seals that exist in the item pool when power seal hunt is the goal."""
display_name = "Total Power Seals"
range_start = 1
range_end = 85
default = 45
class RequiredSeals(Range):
"""Percentage of total seals required to open the shop chest."""
display_name = "Percent Seals Required"
range_start = 10
range_end = 100
default = range_end
class Traps(Toggle):
"""Whether traps should be included in the itempool."""
display_name = "Include Traps"
class ShopPrices(Range):
"""Percentage modifier for shuffled item prices in shops"""
display_name = "Shop Prices Modifier"
range_start = 25
range_end = 400
default = 100
def planned_price(location: str) -> dict[Optional, Or]:
return {
Optional(location): Or(
And(int, lambda n: n >= 0),
{
Optional(And(int, lambda n: n >= 0)): And(int, lambda n: n >= 0)
}
)
}
class PlannedShopPrices(OptionDict):
"""Plan specific prices on shop slots. Supports weighting"""
display_name = "Shop Price Plando"
schema = Schema({
**planned_price("Karuta Plates"),
**planned_price("Serendipitous Bodies"),
**planned_price("Path of Resilience"),
**planned_price("Kusari Jacket"),
**planned_price("Energy Shuriken"),
**planned_price("Serendipitous Minds"),
**planned_price("Prepared Mind"),
**planned_price("Meditation"),
**planned_price("Rejuvenative Spirit"),
**planned_price("Centered Mind"),
**planned_price("Strike of the Ninja"),
**planned_price("Second Wind"),
**planned_price("Currents Master"),
**planned_price("Aerobatics Warrior"),
**planned_price("Demon's Bane"),
**planned_price("Devil's Due"),
**planned_price("Time Sense"),
**planned_price("Power Sense"),
**planned_price("Focused Power Sense"),
**planned_price("Green Kappa Figurine"),
**planned_price("Blue Kappa Figurine"),
**planned_price("Ountarde Figurine"),
**planned_price("Red Kappa Figurine"),
**planned_price("Demon King Figurine"),
**planned_price("Quillshroom Figurine"),
**planned_price("Jumping Quillshroom Figurine"),
**planned_price("Scurubu Figurine"),
**planned_price("Jumping Scurubu Figurine"),
**planned_price("Wallaxer Figurine"),
**planned_price("Barmath'azel Figurine"),
**planned_price("Queen of Quills Figurine"),
**planned_price("Demon Hive Figurine"),
})
@dataclass
class MessengerOptions(DeathLinkMixin, PerGameCommonOptions):
accessibility: MessengerAccessibility
start_inventory: StartInventoryPool
logic_level: Logic
shuffle_shards: MegaShards
limited_movement: LimitedMovement
early_meditation: EarlyMed
available_portals: AvailablePortals
shuffle_portals: ShufflePortals
# shuffle_transitions: ShuffleTransitions
goal: Goal
music_box: MusicBox
notes_needed: NotesNeeded
total_seals: AmountSeals
percent_seals_required: RequiredSeals
traps: Traps
shop_price: ShopPrices
shop_price_plan: PlannedShopPrices
portal_plando: PortalPlando
plando_connections: HiddenPortalPlando