 7193182294
			
		
	
	7193182294
	
	
	
		
			
			🤞 * map option objects to a `World.options` dict * convert RoR2 to options dict system for testing * add temp behavior for lttp with notes * copy/paste bad * convert `set_default_common_options` to a namespace property * reorganize test call order * have fill_restrictive use the new options system * update world api * update soe tests * fix world api * core: auto initialize a dataclass on the World class with the option results * core: auto initialize a dataclass on the World class with the option results: small tying improvement * add `as_dict` method to the options dataclass * fix namespace issues with tests * have current option updates use `.value` instead of changing the option * update ror2 to use the new options system again * revert the junk pool dict since it's cased differently * fix begin_with_loop typo * write new and old options to spoiler * change factorio option behavior back * fix comparisons * move common and per_game_common options to new system * core: automatically create missing options_dataclass from legacy option_definitions * remove spoiler special casing and add back the Factorio option changing but in new system * give ArchipIDLE the default options_dataclass so its options get generated and spoilered properly * reimplement `inspect.get_annotations` * move option info generation for webhost to new system * need to include Common and PerGame common since __annotations__ doesn't include super * use get_type_hints for the options dictionary * typing.get_type_hints returns the bases too. * forgot to sweep through generate * sweep through all the tests * swap to a metaclass property * move remaining usages from get_type_hints to metaclass property * move remaining usages from __annotations__ to metaclass property * move remaining usages from legacy dictionaries to metaclass property * remove legacy dictionaries * cache the metaclass property * clarify inheritance in world api * move the messenger to new options system * add an assert for my dumb * update the doc * rename o to options * missed a spot * update new messenger options * comment spacing Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * fix tests * fix missing import * make the documentation definition more accurate * use options system for loc creation * type cast MessengerWorld * fix typo and use quotes for cast * LTTP: set random seed in tests * ArchipIdle: remove change here as it's default on AutoWorld * Stardew: Need to set state because `set_default_common_options` used to * The Messenger: update shop rando and helpers to new system; optimize imports * Add a kwarg to `as_dict` to do the casing for you * RoR2: use new kwarg for less code * RoR2: revert some accidental reverts * The Messenger: remove an unnecessary variable * remove TypeVar that isn't used * CommonOptions not abstract * Docs: fix mistake in options api.md Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * create options for item link worlds * revert accidental doc removals * Item Links: set default options on group * change Zillion to new options dataclass * remove unused parameter to function * use TypeGuard for Literal narrowing * move dlc quest to new api * move overcooked 2 to new api * fixed some missed code in oc2 * - Tried to be compliant with 993 (WIP?) * - I think it all works now * - Removed last trace of me touching core * typo * It now passes all tests! * Improve options, fix all issues I hope * - Fixed init options * dlcquest: fix bad imports * missed a file * - Reduce code duplication * add as_dict documentation * - Use .items(), get option name more directly, fix slot data content * - Remove generic options from the slot data * improve slot data documentation * remove `CommonOptions.get_value` (#21) * better slot data description Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> Co-authored-by: Doug Hoskisson <beauxq@yahoo.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Alex Gilbert <alexgilbert@yahoo.com>
		
			
				
	
	
		
			271 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import Callable, Dict, TYPE_CHECKING
 | |
| 
 | |
| from BaseClasses import CollectionState
 | |
| from worlds.generic.Rules import add_rule, allow_self_locking_items, set_rule
 | |
| from .constants import NOTES, PHOBEKINS
 | |
| from .options import Goal, MessengerAccessibility
 | |
| from .subclasses import MessengerShopLocation
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from . import MessengerWorld
 | |
| else:
 | |
|     MessengerWorld = object
 | |
| 
 | |
| 
 | |
| class MessengerRules:
 | |
|     player: int
 | |
|     world: MessengerWorld
 | |
|     region_rules: Dict[str, Callable[[CollectionState], bool]]
 | |
|     location_rules: Dict[str, Callable[[CollectionState], bool]]
 | |
| 
 | |
|     def __init__(self, world: MessengerWorld) -> None:
 | |
|         self.player = world.player
 | |
|         self.world = world
 | |
| 
 | |
|         self.region_rules = {
 | |
|             "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,
 | |
|             "Cloud Ruins": lambda state: self.has_vertical(state) and state.has("Ruxxtin's Amulet", self.player),
 | |
|             "Cloud Ruins Right": lambda state: self.has_wingsuit(state) and
 | |
|                                                (self.has_dart(state) or self.can_dboost(state)),
 | |
|             "Underworld": self.has_tabi,
 | |
|             "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("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
 | |
|             "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),
 | |
|             "Howling Grotto - Emerald Golem": self.has_wingsuit,
 | |
|             # searing crags
 | |
|             "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.can_destroy_projectiles,
 | |
|             # cloud ruins
 | |
|             "Cloud Ruins Seal - Ghost Pit": self.has_dart,
 | |
|             # tower of time
 | |
|             "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.is_aerobatic,
 | |
|             "Underworld Seal - Rising Fanta": self.has_dart,
 | |
|             # sunken shrine
 | |
|             "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
 | |
|             "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
 | |
|             "Elemental Skylands - Key of Symbiosis": self.has_dart,
 | |
|             "Elemental Skylands Seal - Air": self.has_wingsuit,
 | |
|             "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
 | |
|             "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:
 | |
|         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:
 | |
|         return state.has("Lightfoot Tabi", self.player)
 | |
| 
 | |
|     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:
 | |
|         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
 | |
| 
 | |
|         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]
 | |
|             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 self.world.options.goal == Goal.option_power_seal_hunt:
 | |
|             set_rule(multiworld.get_entrance("Tower HQ -> Music Box", self.player),
 | |
|                      lambda state: state.has("Shop Chest", self.player))
 | |
| 
 | |
|         multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player)
 | |
|         if multiworld.accessibility[self.player] > MessengerAccessibility.option_locations:
 | |
|             set_self_locking_items(self.world, self.player)
 | |
| 
 | |
| 
 | |
| class MessengerHardRules(MessengerRules):
 | |
|     extra_rules: Dict[str, Callable[[CollectionState], bool]]
 | |
| 
 | |
|     def __init__(self, world: MessengerWorld) -> None:
 | |
|         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,
 | |
|             "Riviere Turquoise": self.true,
 | |
|             "Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(set(PHOBEKINS), self.player),
 | |
|             "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": lambda state: self.has_vertical(state) or self.can_dboost(state),
 | |
|             "Glacial Peak Seal - Projectile Spike Pit": self.true,
 | |
|             "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),
 | |
|             "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 = {
 | |
|             "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),
 | |
|             "Underworld Seal - Fireball Wave": self.has_windmill,
 | |
|         }
 | |
| 
 | |
|     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():
 | |
|             if not self.world.options.shuffle_seals and "Seal" in loc:
 | |
|                 continue
 | |
|             if not self.world.options.shuffle_shards and "Shard" in loc:
 | |
|                 continue
 | |
|             add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or")
 | |
| 
 | |
| 
 | |
| 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({"Windmill Shuriken", "Wingsuit", "Rope Dart", "Magic Firefly"}, self.player),
 | |
|             "Music Box": lambda state: state.has_all(set(NOTES), self.player)
 | |
|         }
 | |
| 
 | |
|         self.location_rules = {
 | |
|             "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": self.has_dart,
 | |
|             "Shop Chest": self.has_enough_seals
 | |
|         }
 | |
| 
 | |
|     def set_messenger_rules(self) -> None:
 | |
|         super().set_messenger_rules()
 | |
|         self.world.multiworld.completion_condition[self.player] = lambda state: True
 | |
|         self.world.options.accessibility.value = MessengerAccessibility.option_minimal
 | |
| 
 | |
| 
 | |
| def set_self_locking_items(world: MessengerWorld, player: int) -> None:
 | |
|     multiworld = world.multiworld
 | |
| 
 | |
|     # do the ones for seal shuffle on and off first
 | |
|     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
 | |
|     if world.options.shuffle_seals:
 | |
|         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 world.options.shuffle_shards:
 | |
|         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)
 | |
|         allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS)
 |