2023-11-23 23:38:57 +00:00
|
|
|
from typing import Dict, TYPE_CHECKING
|
2023-03-12 14:05:50 +00:00
|
|
|
|
2023-10-10 20:30:20 +00:00
|
|
|
from BaseClasses import CollectionState
|
2023-11-23 23:38:57 +00:00
|
|
|
from worlds.generic.Rules import add_rule, allow_self_locking_items, CollectionRule
|
2023-10-08 12:33:39 +00:00
|
|
|
from .constants import NOTES, PHOBEKINS
|
2023-11-11 04:49:55 +00:00
|
|
|
from .options import MessengerAccessibility
|
2023-03-12 14:05:50 +00:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from . import MessengerWorld
|
|
|
|
|
|
|
|
|
|
|
|
class MessengerRules:
|
|
|
|
player: int
|
2023-11-23 23:38:57 +00:00
|
|
|
world: "MessengerWorld"
|
|
|
|
region_rules: Dict[str, CollectionRule]
|
|
|
|
location_rules: Dict[str, CollectionRule]
|
|
|
|
maximum_price: int
|
|
|
|
required_seals: int
|
2023-03-12 14:05:50 +00:00
|
|
|
|
2023-11-23 23:38:57 +00:00
|
|
|
def __init__(self, world: "MessengerWorld") -> None:
|
2023-03-12 14:05:50 +00:00
|
|
|
self.player = world.player
|
|
|
|
self.world = world
|
|
|
|
|
2023-11-23 23:38:57 +00:00
|
|
|
# these locations are at the top of the shop tree, and the entire shop tree needs to be purchased
|
|
|
|
maximum_price = (world.multiworld.get_location("The Shop - Demon's Bane", self.player).cost +
|
|
|
|
world.multiworld.get_location("The Shop - Focused Power Sense", self.player).cost)
|
|
|
|
self.maximum_price = min(maximum_price, world.total_shards)
|
|
|
|
self.required_seals = max(1, world.required_seals)
|
|
|
|
|
2023-03-21 20:21:27 +00:00
|
|
|
self.region_rules = {
|
2023-03-12 14:05:50 +00:00
|
|
|
"Ninja Village": self.has_wingsuit,
|
|
|
|
"Autumn Hills": self.has_wingsuit,
|
|
|
|
"Catacombs": self.has_wingsuit,
|
|
|
|
"Bamboo Creek": self.has_wingsuit,
|
|
|
|
"Searing Crags Upper": self.has_vertical,
|
2023-04-06 08:48:30 +00:00
|
|
|
"Cloud Ruins": lambda state: self.has_vertical(state) and state.has("Ruxxtin's Amulet", self.player),
|
2023-06-27 23:39:52 +00:00
|
|
|
"Cloud Ruins Right": lambda state: self.has_wingsuit(state) and
|
|
|
|
(self.has_dart(state) or self.can_dboost(state)),
|
2023-03-12 14:05:50 +00:00
|
|
|
"Underworld": self.has_tabi,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Riviere Turquoise": lambda state: self.has_dart(state) or
|
|
|
|
(self.has_wingsuit(state) and self.can_destroy_projectiles(state)),
|
|
|
|
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player) and self.can_dboost(state),
|
2023-03-12 14:05:50 +00:00
|
|
|
"Glacial Peak": self.has_vertical,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Elemental Skylands": lambda state: state.has("Magic Firefly", self.player) and self.has_wingsuit(state),
|
2023-11-23 23:38:57 +00:00
|
|
|
"Music Box": lambda state: (state.has_all(NOTES, self.player)
|
|
|
|
or self.has_enough_seals(state)) and self.has_dart(state),
|
|
|
|
"The Craftsman's Corner": lambda state: state.has("Money Wrench", self.player) and self.can_shop(state),
|
2023-03-12 14:05:50 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 20:21:27 +00:00
|
|
|
self.location_rules = {
|
2023-03-12 14:05:50 +00:00
|
|
|
# ninja village
|
|
|
|
"Ninja Village Seal - Tree House": self.has_dart,
|
|
|
|
# autumn hills
|
2023-06-27 23:39:52 +00:00
|
|
|
"Autumn Hills - Key of Hope": self.has_dart,
|
|
|
|
"Autumn Hills Seal - Spike Ball Darts": self.is_aerobatic,
|
|
|
|
# bamboo creek
|
|
|
|
"Bamboo Creek - Claustro": lambda state: self.has_dart(state) or self.can_dboost(state),
|
2023-03-12 14:05:50 +00:00
|
|
|
# howling grotto
|
|
|
|
"Howling Grotto Seal - Windy Saws and Balls": self.has_wingsuit,
|
|
|
|
"Howling Grotto Seal - Crushing Pits": lambda state: self.has_wingsuit(state) and self.has_dart(state),
|
2023-06-27 23:39:52 +00:00
|
|
|
"Howling Grotto - Emerald Golem": self.has_wingsuit,
|
2023-03-12 14:05:50 +00:00
|
|
|
# searing crags
|
2023-06-27 23:39:52 +00:00
|
|
|
"Searing Crags Seal - Triple Ball Spinner": self.has_vertical,
|
|
|
|
"Searing Crags - Astral Tea Leaves":
|
|
|
|
lambda state: state.can_reach("Ninja Village - Astral Seed", "Location", self.player),
|
2023-12-06 17:20:18 +00:00
|
|
|
"Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player)
|
|
|
|
and (self.has_dart(state)
|
|
|
|
or (self.has_wingsuit(state)
|
|
|
|
and self.can_destroy_projectiles(state))),
|
2023-03-12 14:05:50 +00:00
|
|
|
# glacial peak
|
|
|
|
"Glacial Peak Seal - Ice Climbers": self.has_dart,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Glacial Peak Seal - Projectile Spike Pit": self.can_destroy_projectiles,
|
|
|
|
# cloud ruins
|
|
|
|
"Cloud Ruins Seal - Ghost Pit": self.has_dart,
|
2023-03-12 14:05:50 +00:00
|
|
|
# tower of time
|
2023-06-27 23:39:52 +00:00
|
|
|
"Tower of Time Seal - Time Waster": self.has_dart,
|
|
|
|
"Tower of Time Seal - Lantern Climb": lambda state: self.has_wingsuit(state) and self.has_dart(state),
|
2023-03-12 14:05:50 +00:00
|
|
|
"Tower of Time Seal - Arcane Orbs": lambda state: self.has_wingsuit(state) and self.has_dart(state),
|
|
|
|
# underworld
|
|
|
|
"Underworld Seal - Sharp and Windy Climb": self.has_wingsuit,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Underworld Seal - Fireball Wave": self.is_aerobatic,
|
2023-03-12 14:05:50 +00:00
|
|
|
"Underworld Seal - Rising Fanta": self.has_dart,
|
|
|
|
# sunken shrine
|
2023-06-27 23:39:52 +00:00
|
|
|
"Sunken Shrine - Sun Crest": self.has_tabi,
|
|
|
|
"Sunken Shrine - Moon Crest": self.has_tabi,
|
|
|
|
"Sunken Shrine - Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
|
2023-03-12 14:05:50 +00:00
|
|
|
"Sunken Shrine Seal - Waterfall Paradise": self.has_tabi,
|
|
|
|
"Sunken Shrine Seal - Tabi Gauntlet": self.has_tabi,
|
2023-04-06 08:48:30 +00:00
|
|
|
"Mega Shard of the Moon": self.has_tabi,
|
|
|
|
"Mega Shard of the Sun": self.has_tabi,
|
2023-03-12 14:05:50 +00:00
|
|
|
# riviere turquoise
|
2023-06-27 23:39:52 +00:00
|
|
|
"Riviere Turquoise Seal - Bounces and Balls": self.can_dboost,
|
|
|
|
"Riviere Turquoise Seal - Launch of Faith": lambda state: self.can_dboost(state) or self.has_dart(state),
|
2023-03-12 14:05:50 +00:00
|
|
|
# elemental skylands
|
2023-06-27 23:39:52 +00:00
|
|
|
"Elemental Skylands - Key of Symbiosis": self.has_dart,
|
2023-03-12 14:05:50 +00:00
|
|
|
"Elemental Skylands Seal - Air": self.has_wingsuit,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Elemental Skylands Seal - Water": lambda state: self.has_dart(state) and
|
|
|
|
state.has("Currents Master", self.player),
|
|
|
|
"Elemental Skylands Seal - Fire": lambda state: self.has_dart(state) and self.can_destroy_projectiles(state),
|
2023-04-06 08:48:30 +00:00
|
|
|
"Earth Mega Shard": self.has_dart,
|
|
|
|
"Water Mega Shard": self.has_dart,
|
2023-03-12 14:05:50 +00:00
|
|
|
# corrupted future
|
2023-06-27 23:39:52 +00:00
|
|
|
"Corrupted Future - Key of Courage": lambda state: state.has_all({"Demon King Crown", "Magic Firefly"},
|
|
|
|
self.player),
|
|
|
|
# tower hq
|
|
|
|
"Money Wrench": self.can_shop,
|
2023-03-12 14:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def has_wingsuit(self, state: CollectionState) -> bool:
|
|
|
|
return state.has("Wingsuit", self.player)
|
|
|
|
|
|
|
|
def has_dart(self, state: CollectionState) -> bool:
|
|
|
|
return state.has("Rope Dart", self.player)
|
|
|
|
|
|
|
|
def has_tabi(self, state: CollectionState) -> bool:
|
2023-06-27 23:39:52 +00:00
|
|
|
return state.has("Lightfoot Tabi", self.player)
|
2023-03-12 14:05:50 +00:00
|
|
|
|
|
|
|
def has_vertical(self, state: CollectionState) -> bool:
|
|
|
|
return self.has_wingsuit(state) or self.has_dart(state)
|
|
|
|
|
|
|
|
def has_enough_seals(self, state: CollectionState) -> bool:
|
2023-11-23 23:38:57 +00:00
|
|
|
return state.has("Power Seal", self.player, self.required_seals)
|
2023-03-21 20:21:27 +00:00
|
|
|
|
2023-06-27 23:39:52 +00:00
|
|
|
def can_destroy_projectiles(self, state: CollectionState) -> bool:
|
|
|
|
return state.has("Strike of the Ninja", self.player)
|
|
|
|
|
|
|
|
def can_dboost(self, state: CollectionState) -> bool:
|
|
|
|
return state.has_any({"Path of Resilience", "Meditation"}, self.player) and \
|
|
|
|
state.has("Second Wind", self.player)
|
2023-10-08 12:33:39 +00:00
|
|
|
|
2023-06-27 23:39:52 +00:00
|
|
|
def is_aerobatic(self, state: CollectionState) -> bool:
|
|
|
|
return self.has_wingsuit(state) and state.has("Aerobatics Warrior", self.player)
|
|
|
|
|
2023-03-21 20:21:27 +00:00
|
|
|
def true(self, state: CollectionState) -> bool:
|
|
|
|
"""I know this is stupid, but it's easier to read in the dicts."""
|
|
|
|
return True
|
2023-03-12 14:05:50 +00:00
|
|
|
|
2023-06-27 23:39:52 +00:00
|
|
|
def can_shop(self, state: CollectionState) -> bool:
|
2023-11-23 23:38:57 +00:00
|
|
|
return state.has("Shards", self.player, self.maximum_price)
|
2023-06-27 23:39:52 +00:00
|
|
|
|
2023-03-12 14:05:50 +00:00
|
|
|
def set_messenger_rules(self) -> None:
|
|
|
|
multiworld = self.world.multiworld
|
|
|
|
|
|
|
|
for region in multiworld.get_regions(self.player):
|
|
|
|
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]
|
|
|
|
|
2023-03-21 20:21:27 +00:00
|
|
|
multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player)
|
2023-11-11 04:49:55 +00:00
|
|
|
if multiworld.accessibility[self.player]: # not locations accessibility
|
2023-10-10 20:30:20 +00:00
|
|
|
set_self_locking_items(self.world, self.player)
|
2023-03-12 14:05:50 +00:00
|
|
|
|
|
|
|
|
2023-03-21 20:21:27 +00:00
|
|
|
class MessengerHardRules(MessengerRules):
|
2023-11-23 23:38:57 +00:00
|
|
|
extra_rules: Dict[str, CollectionRule]
|
2023-03-21 20:21:27 +00:00
|
|
|
|
2023-11-23 23:38:57 +00:00
|
|
|
def __init__(self, world: "MessengerWorld") -> None:
|
2023-03-21 20:21:27 +00:00
|
|
|
super().__init__(world)
|
|
|
|
|
|
|
|
self.region_rules.update({
|
|
|
|
"Ninja Village": self.has_vertical,
|
|
|
|
"Autumn Hills": self.has_vertical,
|
|
|
|
"Catacombs": self.has_vertical,
|
|
|
|
"Bamboo Creek": self.has_vertical,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Riviere Turquoise": self.true,
|
2023-11-23 23:38:57 +00:00
|
|
|
"Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(PHOBEKINS, self.player),
|
2023-06-27 23:39:52 +00:00
|
|
|
"Searing Crags Upper": lambda state: self.can_destroy_projectiles(state) or self.has_windmill(state)
|
|
|
|
or self.has_vertical(state),
|
|
|
|
"Glacial Peak": lambda state: self.can_destroy_projectiles(state) or self.has_windmill(state)
|
|
|
|
or self.has_vertical(state),
|
|
|
|
"Elemental Skylands": lambda state: state.has("Magic Firefly", self.player) or
|
|
|
|
self.has_windmill(state) or
|
|
|
|
self.has_dart(state),
|
2023-03-21 20:21:27 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
self.location_rules.update({
|
|
|
|
"Howling Grotto Seal - Windy Saws and Balls": self.true,
|
2023-06-27 23:39:52 +00:00
|
|
|
"Searing Crags Seal - Triple Ball Spinner": self.true,
|
|
|
|
"Searing Crags Seal - Raining Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
|
|
|
"Searing Crags Seal - Rhythm Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
|
|
|
"Searing Crags - Power Thistle": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
2023-09-30 10:35:07 +00:00
|
|
|
"Glacial Peak Seal - Ice Climbers": lambda state: self.has_vertical(state) or self.can_dboost(state),
|
2023-03-21 20:21:27 +00:00
|
|
|
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
2023-09-30 10:35:07 +00:00
|
|
|
"Glacial Peak Seal - Glacial Air Swag": lambda state: self.has_windmill(state) or self.has_vertical(state),
|
|
|
|
"Glacial Peak Mega Shard": lambda state: self.has_windmill(state) or self.has_vertical(state),
|
2023-06-27 23:39:52 +00:00
|
|
|
"Cloud Ruins Seal - Ghost Pit": self.true,
|
|
|
|
"Bamboo Creek - Claustro": self.has_wingsuit,
|
|
|
|
"Tower of Time Seal - Lantern Climb": self.has_wingsuit,
|
|
|
|
"Elemental Skylands Seal - Water": lambda state: self.has_dart(state) or self.can_dboost(state)
|
|
|
|
or self.has_windmill(state),
|
|
|
|
"Elemental Skylands Seal - Fire": lambda state: (self.has_dart(state) or self.can_dboost(state)
|
|
|
|
or self.has_windmill(state)) and
|
|
|
|
self.can_destroy_projectiles(state),
|
|
|
|
"Earth Mega Shard": lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state),
|
|
|
|
"Water Mega Shard": lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state),
|
2023-03-21 20:21:27 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
self.extra_rules = {
|
2023-06-27 23:39:52 +00:00
|
|
|
"Searing Crags - Key of Strength": lambda state: self.has_dart(state) or self.has_windmill(state),
|
|
|
|
"Elemental Skylands - Key of Symbiosis": lambda state: self.has_windmill(state) or self.can_dboost(state),
|
2023-11-11 04:49:55 +00:00
|
|
|
"Autumn Hills Seal - Spike Ball Darts": lambda state: self.has_dart(state) or self.has_windmill(state),
|
2023-09-30 10:35:07 +00:00
|
|
|
"Underworld Seal - Fireball Wave": self.has_windmill,
|
2023-03-21 20:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def has_windmill(self, state: CollectionState) -> bool:
|
|
|
|
return state.has("Windmill Shuriken", self.player)
|
|
|
|
|
|
|
|
def set_messenger_rules(self) -> None:
|
|
|
|
super().set_messenger_rules()
|
|
|
|
for loc, rule in self.extra_rules.items():
|
2023-10-10 20:30:20 +00:00
|
|
|
if not self.world.options.shuffle_seals and "Seal" in loc:
|
2023-03-24 23:55:24 +00:00
|
|
|
continue
|
2023-10-10 20:30:20 +00:00
|
|
|
if not self.world.options.shuffle_shards and "Shard" in loc:
|
2023-04-06 08:48:30 +00:00
|
|
|
continue
|
2023-03-21 20:21:27 +00:00
|
|
|
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
|
|
|
|
|
|
|
|
|
|
|
|
class MessengerOOBRules(MessengerRules):
|
2023-11-23 23:38:57 +00:00
|
|
|
def __init__(self, world: "MessengerWorld") -> None:
|
2023-03-21 20:21:27 +00:00
|
|
|
self.world = world
|
|
|
|
self.player = world.player
|
|
|
|
|
2023-11-23 23:38:57 +00:00
|
|
|
self.required_seals = max(1, world.required_seals)
|
2023-03-21 20:21:27 +00:00
|
|
|
self.region_rules = {
|
2023-06-27 23:39:52 +00:00
|
|
|
"Elemental Skylands":
|
|
|
|
lambda state: state.has_any({"Windmill Shuriken", "Wingsuit", "Rope Dart", "Magic Firefly"}, self.player),
|
2023-11-23 23:38:57 +00:00
|
|
|
"Music Box": lambda state: state.has_all(set(NOTES), self.player) or self.has_enough_seals(state),
|
2023-03-21 20:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.location_rules = {
|
2023-06-27 23:39:52 +00:00
|
|
|
"Bamboo Creek - Claustro": self.has_wingsuit,
|
|
|
|
"Searing Crags - Key of Strength": self.has_wingsuit,
|
|
|
|
"Sunken Shrine - Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
|
|
|
|
"Searing Crags - Pyro": self.has_tabi,
|
|
|
|
"Underworld - Key of Chaos": self.has_tabi,
|
|
|
|
"Corrupted Future - Key of Courage":
|
|
|
|
lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player),
|
2023-03-21 20:21:27 +00:00
|
|
|
"Autumn Hills Seal - Spike Ball Darts": self.has_dart,
|
|
|
|
"Ninja Village Seal - Tree House": self.has_dart,
|
|
|
|
"Underworld Seal - Fireball Wave": lambda state: state.has_any({"Wingsuit", "Windmill Shuriken"},
|
|
|
|
self.player),
|
2023-06-27 23:39:52 +00:00
|
|
|
"Tower of Time Seal - Time Waster": self.has_dart,
|
2023-03-21 20:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def set_messenger_rules(self) -> None:
|
|
|
|
super().set_messenger_rules()
|
2023-10-10 20:30:20 +00:00
|
|
|
self.world.options.accessibility.value = MessengerAccessibility.option_minimal
|
2023-03-21 20:21:27 +00:00
|
|
|
|
|
|
|
|
2023-11-23 23:38:57 +00:00
|
|
|
def set_self_locking_items(world: "MessengerWorld", player: int) -> None:
|
2023-10-10 20:30:20 +00:00
|
|
|
multiworld = world.multiworld
|
|
|
|
|
2023-03-12 14:05:50 +00:00
|
|
|
# do the ones for seal shuffle on and off first
|
2023-06-27 23:39:52 +00:00
|
|
|
allow_self_locking_items(multiworld.get_location("Searing Crags - Key of Strength", player), "Power Thistle")
|
|
|
|
allow_self_locking_items(multiworld.get_location("Sunken Shrine - Key of Love", player), "Sun Crest", "Moon Crest")
|
|
|
|
allow_self_locking_items(multiworld.get_location("Corrupted Future - Key of Courage", player), "Demon King Crown")
|
|
|
|
|
|
|
|
# add these locations when seals are shuffled
|
2023-10-10 20:30:20 +00:00
|
|
|
if world.options.shuffle_seals:
|
2023-06-27 23:39:52 +00:00
|
|
|
allow_self_locking_items(multiworld.get_location("Elemental Skylands Seal - Water", player), "Currents Master")
|
|
|
|
# add these locations when seals and shards aren't shuffled
|
2023-10-10 20:30:20 +00:00
|
|
|
elif not world.options.shuffle_shards:
|
2023-10-04 16:23:29 +00:00
|
|
|
for entrance in multiworld.get_region("Cloud Ruins", player).entrances:
|
|
|
|
entrance.access_rule = lambda state: state.has("Wingsuit", player) or state.has("Rope Dart", player)
|
2023-03-12 14:05:50 +00:00
|
|
|
allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)
|