mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Messenger: Add Shop Rando (#1834)
* add shop shuffle options and items * add logic for the shop slots * write cost tests * start on shop item logic * make strike and second wind early items * some cleanup * remove 5 shards * double cost requirement for really expensive items and raise the rates * add test for shop shuffle with minimum other locations * put power seal in front of shards * rename locations and items * update rules, regions, and shop * update tests and misc fixes * minor cleanup * implement money wrench and figurines * clean out now unneeded info from slot_data * docs update and fix a failure when not shuffling shops * remove shop shuffle option * Finish out shop rules * make seals generation easier to read and fix tests * rule adjustments * oop * adjust the prices to be a bit more generous * add max price to slot data for tracker * update the hard rules a bit * remove unnecessary test * update data_version * bump version and remove info for fixed issues * remove now unneeded assert * review updates * minor bug fix * add a test for minimum locations shop costing * minor optimizations and cleanup * remove whitespace
This commit is contained in:
@@ -4,6 +4,7 @@ from BaseClasses import CollectionState, MultiWorld
|
||||
from worlds.generic.Rules import set_rule, allow_self_locking_items, add_rule
|
||||
from .Options import MessengerAccessibility, Goal
|
||||
from .Constants import NOTES, PHOBEKINS
|
||||
from .SubClasses import MessengerShopLocation
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import MessengerWorld
|
||||
@@ -28,62 +29,73 @@ class MessengerRules:
|
||||
"Bamboo Creek": self.has_wingsuit,
|
||||
"Searing Crags Upper": self.has_vertical,
|
||||
"Cloud Ruins": lambda state: self.has_vertical(state) and state.has("Ruxxtin's Amulet", self.player),
|
||||
"Cloud Ruins Right": self.has_wingsuit,
|
||||
"Cloud Ruins Right": lambda state: self.has_wingsuit(state) and
|
||||
(self.has_dart(state) or self.can_dboost(state)),
|
||||
"Underworld": self.has_tabi,
|
||||
"Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player),
|
||||
"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),
|
||||
"Glacial Peak": self.has_vertical,
|
||||
"Elemental Skylands": lambda state: state.has("Fairy Bottle", self.player),
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player) and self.has_vertical(state),
|
||||
"Elemental Skylands": lambda state: state.has("Magic Firefly", self.player) and self.has_wingsuit(state),
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player) and self.has_dart(state),
|
||||
}
|
||||
|
||||
self.location_rules = {
|
||||
# ninja village
|
||||
"Ninja Village Seal - Tree House": self.has_dart,
|
||||
# autumn hills
|
||||
"Key of Hope": self.has_dart,
|
||||
"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),
|
||||
# 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),
|
||||
"Emerald Golem": self.has_wingsuit,
|
||||
"Howling Grotto - Emerald Golem": self.has_wingsuit,
|
||||
# searing crags
|
||||
"Astral Tea Leaves": lambda state: state.can_reach("Astral Seed", "Location", self.player),
|
||||
"Key of Strength": lambda state: state.has("Power Thistle", self.player),
|
||||
"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),
|
||||
"Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player),
|
||||
# glacial peak
|
||||
"Glacial Peak Seal - Ice Climbers": self.has_dart,
|
||||
"Glacial Peak Seal - Projectile Spike Pit": self.has_vertical,
|
||||
"Glacial Peak Seal - Glacial Air Swag": self.has_vertical,
|
||||
"Glacial Peak Seal - Projectile Spike Pit": self.can_destroy_projectiles,
|
||||
# cloud ruins
|
||||
"Cloud Ruins Seal - Ghost Pit": self.has_dart,
|
||||
# tower of time
|
||||
"Tower of Time Seal - Time Waster Seal": self.has_dart,
|
||||
"Tower of Time Seal - Lantern Climb": self.has_wingsuit,
|
||||
"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),
|
||||
"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,
|
||||
"Underworld Seal - Fireball Wave": self.has_wingsuit,
|
||||
"Underworld Seal - Fireball Wave": self.is_aerobatic,
|
||||
"Underworld Seal - Rising Fanta": self.has_dart,
|
||||
# sunken shrine
|
||||
"Sun Crest": self.has_tabi,
|
||||
"Moon Crest": self.has_tabi,
|
||||
"Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
|
||||
"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),
|
||||
"Sunken Shrine Seal - Waterfall Paradise": self.has_tabi,
|
||||
"Sunken Shrine Seal - Tabi Gauntlet": self.has_tabi,
|
||||
"Mega Shard of the Moon": self.has_tabi,
|
||||
"Mega Shard of the Sun": self.has_tabi,
|
||||
# riviere turquoise
|
||||
"Fairy Bottle": self.has_vertical,
|
||||
"Riviere Turquoise Seal - Flower Power": self.has_vertical,
|
||||
"Quick Restock Mega Shard 1": self.has_vertical,
|
||||
"Quick Restock Mega Shard 2": self.has_vertical,
|
||||
"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),
|
||||
# elemental skylands
|
||||
"Key of Symbiosis": self.has_dart,
|
||||
"Elemental Skylands - Key of Symbiosis": self.has_dart,
|
||||
"Elemental Skylands Seal - Air": self.has_wingsuit,
|
||||
"Elemental Skylands Seal - Water": self.has_dart,
|
||||
"Elemental Skylands Seal - Fire": self.has_dart,
|
||||
"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),
|
||||
"Earth Mega Shard": self.has_dart,
|
||||
"Water Mega Shard": self.has_dart,
|
||||
# corrupted future
|
||||
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
|
||||
"Corrupted Future - Key of Courage": lambda state: state.has_all({"Demon King Crown", "Magic Firefly"},
|
||||
self.player),
|
||||
# the shop
|
||||
"Shop Chest": self.has_enough_seals,
|
||||
# tower hq
|
||||
"Money Wrench": self.can_shop,
|
||||
}
|
||||
|
||||
def has_wingsuit(self, state: CollectionState) -> bool:
|
||||
@@ -93,7 +105,7 @@ class MessengerRules:
|
||||
return state.has("Rope Dart", self.player)
|
||||
|
||||
def has_tabi(self, state: CollectionState) -> bool:
|
||||
return state.has("Ninja Tabi", self.player)
|
||||
return state.has("Lightfoot Tabi", self.player)
|
||||
|
||||
def has_vertical(self, state: CollectionState) -> bool:
|
||||
return self.has_wingsuit(state) or self.has_dart(state)
|
||||
@@ -101,10 +113,25 @@ class MessengerRules:
|
||||
def has_enough_seals(self, state: CollectionState) -> bool:
|
||||
return not self.world.required_seals or state.has("Power Seal", self.player, self.world.required_seals)
|
||||
|
||||
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)
|
||||
|
||||
def is_aerobatic(self, state: CollectionState) -> bool:
|
||||
return self.has_wingsuit(state) and state.has("Aerobatics Warrior", self.player)
|
||||
|
||||
def true(self, state: CollectionState) -> bool:
|
||||
"""I know this is stupid, but it's easier to read in the dicts."""
|
||||
return True
|
||||
|
||||
def can_shop(self, state: CollectionState) -> bool:
|
||||
prices = self.world.shop_prices
|
||||
most_expensive_loc = max(prices, key=prices.get)
|
||||
return state.can_reach(f"The Shop - {most_expensive_loc}", "Location", self.player)
|
||||
|
||||
def set_messenger_rules(self) -> None:
|
||||
multiworld = self.world.multiworld
|
||||
|
||||
@@ -115,6 +142,9 @@ class MessengerRules:
|
||||
for loc in region.locations:
|
||||
if loc.name in self.location_rules:
|
||||
loc.access_rule = self.location_rules[loc.name]
|
||||
if region.name == "The Shop":
|
||||
for loc in [location for location in region.locations if isinstance(location, MessengerShopLocation)]:
|
||||
loc.access_rule = loc.can_afford
|
||||
if multiworld.goal[self.player] == Goal.option_power_seal_hunt:
|
||||
set_rule(multiworld.get_entrance("Tower HQ -> Music Box", self.player),
|
||||
lambda state: state.has("Shop Chest", self.player))
|
||||
@@ -135,29 +165,45 @@ class MessengerHardRules(MessengerRules):
|
||||
"Autumn Hills": self.has_vertical,
|
||||
"Catacombs": self.has_vertical,
|
||||
"Bamboo Creek": self.has_vertical,
|
||||
"Riviere Turquoise": self.true,
|
||||
"Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player),
|
||||
"Searing Crags Upper": self.true,
|
||||
"Glacial Peak": self.true,
|
||||
"Elemental Skylands": lambda state: state.has("Fairy Bottle", self.player) or self.has_windmill(state),
|
||||
"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),
|
||||
})
|
||||
|
||||
self.location_rules.update({
|
||||
"Howling Grotto Seal - Windy Saws and Balls": self.true,
|
||||
"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),
|
||||
"Glacial Peak Seal - Ice Climbers": self.has_vertical,
|
||||
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
||||
"Claustro": self.has_wingsuit,
|
||||
"Elemental Skylands Seal - Water": self.true,
|
||||
"Elemental Skylands Seal - Fire": self.true,
|
||||
"Earth Mega Shard": self.true,
|
||||
"Water Mega Shard": self.true,
|
||||
"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),
|
||||
})
|
||||
|
||||
self.extra_rules = {
|
||||
"Key of Strength": lambda state: self.has_dart(state) or self.has_windmill(state),
|
||||
"Key of Symbiosis": self.has_windmill,
|
||||
"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),
|
||||
"Autumn Hills Seal - Spike Ball Darts": lambda state: (self.has_dart(state) and self.has_windmill(state))
|
||||
or self.has_wingsuit(state),
|
||||
"Glacial Peak Seal - Glacial Air Swag": self.has_windmill,
|
||||
"Underworld Seal - Fireball Wave": lambda state: state.has_all({"Ninja Tabi", "Windmill Shuriken"},
|
||||
"Glacial Peak Seal - Ice Climbers": lambda state: self.has_wingsuit(state) or self.can_dboost(state),
|
||||
"Underworld Seal - Fireball Wave": lambda state: state.has_all({"Lightfoot Tabi", "Windmill Shuriken"},
|
||||
self.player),
|
||||
}
|
||||
|
||||
@@ -174,53 +220,31 @@ class MessengerHardRules(MessengerRules):
|
||||
add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
|
||||
|
||||
|
||||
class MessengerChallengeRules(MessengerHardRules):
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
super().__init__(world)
|
||||
|
||||
self.region_rules.update({
|
||||
"Forlorn Temple": lambda state: (self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player))
|
||||
or state.has_all({"Wingsuit", "Windmill Shuriken"}, self.player),
|
||||
"Elemental Skylands": lambda state: self.has_wingsuit(state) or state.has("Fairy Bottle", self.player)
|
||||
or self.has_windmill(state),
|
||||
})
|
||||
|
||||
self.location_rules.update({
|
||||
"Fairy Bottle": self.true,
|
||||
"Howling Grotto Seal - Crushing Pits": self.true,
|
||||
"Underworld Seal - Sharp and Windy Climb": self.true,
|
||||
"Riviere Turquoise Seal - Flower Power": self.true,
|
||||
})
|
||||
|
||||
self.extra_rules.update({
|
||||
"Key of Hope": self.has_vertical,
|
||||
"Key of Symbiosis": lambda state: self.has_vertical(state) or self.has_windmill(state),
|
||||
})
|
||||
|
||||
|
||||
class MessengerOOBRules(MessengerRules):
|
||||
def __init__(self, world: MessengerWorld) -> None:
|
||||
self.world = world
|
||||
self.player = world.player
|
||||
|
||||
self.region_rules = {
|
||||
"Elemental Skylands": lambda state: state.has_any({"Wingsuit", "Rope Dart", "Fairy Bottle"}, self.player),
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player),
|
||||
"Elemental Skylands":
|
||||
lambda state: state.has_any({"Windmill Shuriken", "Wingsuit", "Rope Dart", "Magic Firefly"}, self.player),
|
||||
"Music Box": lambda state: state.has_all(set(NOTES), self.player)
|
||||
}
|
||||
|
||||
self.location_rules = {
|
||||
"Claustro": self.has_wingsuit,
|
||||
"Key of Strength": lambda state: self.has_vertical(state) or state.has("Power Thistle", self.player),
|
||||
"Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player),
|
||||
"Pyro": self.has_tabi,
|
||||
"Key of Chaos": self.has_tabi,
|
||||
"Key of Courage": lambda state: state.has_all({"Demon King Crown", "Fairy Bottle"}, self.player),
|
||||
"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),
|
||||
"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),
|
||||
"Tower of Time Seal - Time Waster Seal": self.has_dart,
|
||||
"Shop Chest": self.has_enough_seals,
|
||||
"Tower of Time Seal - Time Waster": self.has_dart,
|
||||
"Shop Chest": self.has_enough_seals
|
||||
}
|
||||
|
||||
def set_messenger_rules(self) -> None:
|
||||
@@ -231,11 +255,14 @@ class MessengerOOBRules(MessengerRules):
|
||||
|
||||
def set_self_locking_items(multiworld: MultiWorld, player: int) -> None:
|
||||
# do the ones for seal shuffle on and off first
|
||||
allow_self_locking_items(multiworld.get_location("Key of Strength", player), "Power Thistle")
|
||||
allow_self_locking_items(multiworld.get_location("Key of Love", player), "Sun Crest", "Moon Crest")
|
||||
allow_self_locking_items(multiworld.get_location("Key of Courage", player), "Demon King Crown")
|
||||
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 aren't shuffled
|
||||
if not multiworld.shuffle_seals[player] and not multiworld.shuffle_shards[player]:
|
||||
# add these locations when seals are shuffled
|
||||
if multiworld.shuffle_seals[player]:
|
||||
allow_self_locking_items(multiworld.get_location("Elemental Skylands Seal - Water", player), "Currents Master")
|
||||
# add these locations when seals and shards aren't shuffled
|
||||
elif not multiworld.shuffle_shards[player]:
|
||||
allow_self_locking_items(multiworld.get_region("Cloud Ruins Right", player), "Ruxxtin's Amulet")
|
||||
allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)
|
||||
|
||||
Reference in New Issue
Block a user