Core: move option results to the World class instead of MultiWorld (#993)

🤞 

* 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>
This commit is contained in:
Aaron Wagener
2023-10-10 15:30:20 -05:00
committed by GitHub
parent a7b4914bb7
commit 7193182294
69 changed files with 1587 additions and 1603 deletions

View File

@@ -1,18 +1,20 @@
import logging
from typing import Dict, Any, Iterable, Optional, Union, Set
from typing import Dict, Any, Iterable, Optional, Union, Set, List
from BaseClasses import Region, Entrance, Location, Item, Tutorial, CollectionState, ItemClassification, MultiWorld
from Options import PerGameCommonOptions
from worlds.AutoWorld import World, WebWorld
from . import rules, logic, options
from . import rules
from .bundles import get_all_bundles, Bundle
from .items import item_table, create_items, ItemData, Group, items_by_group
from .locations import location_table, create_locations, LocationData
from .logic import StardewLogic, StardewRule, True_, MAX_MONTHS
from .options import stardew_valley_options, StardewOptions, fetch_options
from .options import StardewValleyOptions, SeasonRandomization, Goal, BundleRandomization, BundlePrice, NumberOfLuckBuffs, NumberOfMovementBuffs, \
BackpackProgression, BuildingProgression, ExcludeGingerIsland
from .regions import create_regions
from .rules import set_rules
from worlds.generic.Rules import set_rule
from .strings.goal_names import Goal
from .strings.goal_names import Goal as GoalName
client_version = 0
@@ -50,7 +52,6 @@ class StardewValleyWorld(World):
befriend villagers, and uncover dark secrets.
"""
game = "Stardew Valley"
option_definitions = stardew_valley_options
topology_present = False
item_name_to_id = {name: data.code for name, data in item_table.items()}
@@ -59,7 +60,8 @@ class StardewValleyWorld(World):
data_version = 3
required_client_version = (0, 4, 0)
options: StardewOptions
options_dataclass = StardewValleyOptions
options: StardewValleyOptions
logic: StardewLogic
web = StardewWebWorld()
@@ -72,25 +74,24 @@ class StardewValleyWorld(World):
self.all_progression_items = set()
def generate_early(self):
self.options = fetch_options(self.multiworld, self.player)
self.force_change_options_if_incompatible()
self.logic = StardewLogic(self.player, self.options)
self.modified_bundles = get_all_bundles(self.multiworld.random,
self.logic,
self.options[options.BundleRandomization],
self.options[options.BundlePrice])
self.options.bundle_randomization,
self.options.bundle_price)
def force_change_options_if_incompatible(self):
goal_is_walnut_hunter = self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter
goal_is_perfection = self.options[options.Goal] == options.Goal.option_perfection
goal_is_walnut_hunter = self.options.goal == Goal.option_greatest_walnut_hunter
goal_is_perfection = self.options.goal == Goal.option_perfection
goal_is_island_related = goal_is_walnut_hunter or goal_is_perfection
exclude_ginger_island = self.options[options.ExcludeGingerIsland] == options.ExcludeGingerIsland.option_true
exclude_ginger_island = self.options.exclude_ginger_island == ExcludeGingerIsland.option_true
if goal_is_island_related and exclude_ginger_island:
self.options[options.ExcludeGingerIsland] = options.ExcludeGingerIsland.option_false
goal = options.Goal.name_lookup[self.options[options.Goal]]
self.options.exclude_ginger_island.value = ExcludeGingerIsland.option_false
goal_name = self.options.goal.current_key
player_name = self.multiworld.player_name[self.player]
logging.warning(f"Goal '{goal}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
logging.warning(f"Goal '{goal_name}' requires Ginger Island. Exclude Ginger Island setting forced to 'False' for player {self.player} ({player_name})")
def create_regions(self):
def create_region(name: str, exits: Iterable[str]) -> Region:
@@ -116,7 +117,7 @@ class StardewValleyWorld(World):
if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK,
Group.FRIENDSHIP_PACK)]
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
if self.options.season_randomization == SeasonRandomization.option_disabled:
items_to_exclude = [item for item in items_to_exclude
if item_table[item.name] not in items_by_group[Group.SEASON]]
@@ -134,12 +135,12 @@ class StardewValleyWorld(World):
self.setup_victory()
def precollect_starting_season(self) -> Optional[StardewItem]:
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_progressive:
if self.options.season_randomization == SeasonRandomization.option_progressive:
return
season_pool = items_by_group[Group.SEASON]
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_disabled:
if self.options.season_randomization == SeasonRandomization.option_disabled:
for season in season_pool:
self.multiworld.push_precollected(self.create_item(season))
return
@@ -148,18 +149,18 @@ class StardewValleyWorld(World):
if item.name in {season.name for season in items_by_group[Group.SEASON]}]:
return
if self.options[options.SeasonRandomization] == options.SeasonRandomization.option_randomized_not_winter:
if self.options.season_randomization == SeasonRandomization.option_randomized_not_winter:
season_pool = [season for season in season_pool if season.name != "Winter"]
starting_season = self.create_item(self.multiworld.random.choice(season_pool))
self.multiworld.push_precollected(starting_season)
def setup_early_items(self):
if (self.options[options.BuildingProgression] ==
options.BuildingProgression.option_progressive_early_shipping_bin):
if (self.options.building_progression ==
BuildingProgression.option_progressive_early_shipping_bin):
self.multiworld.early_items[self.player]["Shipping Bin"] = 1
if self.options[options.BackpackProgression] == options.BackpackProgression.option_early_progressive:
if self.options.backpack_progression == BackpackProgression.option_early_progressive:
self.multiworld.early_items[self.player]["Progressive Backpack"] = 1
def setup_month_events(self):
@@ -172,40 +173,40 @@ class StardewValleyWorld(World):
self.create_event_location(month_end, self.logic.received("Month End", i).simplify(), "Month End")
def setup_victory(self):
if self.options[options.Goal] == options.Goal.option_community_center:
self.create_event_location(location_table[Goal.community_center],
if self.options.goal == Goal.option_community_center:
self.create_event_location(location_table[GoalName.community_center],
self.logic.can_complete_community_center().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_grandpa_evaluation:
self.create_event_location(location_table[Goal.grandpa_evaluation],
elif self.options.goal == Goal.option_grandpa_evaluation:
self.create_event_location(location_table[GoalName.grandpa_evaluation],
self.logic.can_finish_grandpa_evaluation().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_bottom_of_the_mines:
self.create_event_location(location_table[Goal.bottom_of_the_mines],
elif self.options.goal == Goal.option_bottom_of_the_mines:
self.create_event_location(location_table[GoalName.bottom_of_the_mines],
self.logic.can_mine_to_floor(120).simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_cryptic_note:
self.create_event_location(location_table[Goal.cryptic_note],
elif self.options.goal == Goal.option_cryptic_note:
self.create_event_location(location_table[GoalName.cryptic_note],
self.logic.can_complete_quest("Cryptic Note").simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_master_angler:
self.create_event_location(location_table[Goal.master_angler],
elif self.options.goal == Goal.option_master_angler:
self.create_event_location(location_table[GoalName.master_angler],
self.logic.can_catch_every_fish().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_complete_collection:
self.create_event_location(location_table[Goal.complete_museum],
elif self.options.goal == Goal.option_complete_collection:
self.create_event_location(location_table[GoalName.complete_museum],
self.logic.can_complete_museum().simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_full_house:
self.create_event_location(location_table[Goal.full_house],
elif self.options.goal == Goal.option_full_house:
self.create_event_location(location_table[GoalName.full_house],
(self.logic.has_children(2) & self.logic.can_reproduce()).simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_greatest_walnut_hunter:
self.create_event_location(location_table[Goal.greatest_walnut_hunter],
elif self.options.goal == Goal.option_greatest_walnut_hunter:
self.create_event_location(location_table[GoalName.greatest_walnut_hunter],
self.logic.has_walnut(130).simplify(),
"Victory")
elif self.options[options.Goal] == options.Goal.option_perfection:
self.create_event_location(location_table[Goal.perfection],
elif self.options.goal == Goal.option_perfection:
self.create_event_location(location_table[GoalName.perfection],
self.logic.has_everything(self.all_progression_items).simplify(),
"Victory")
@@ -230,7 +231,7 @@ class StardewValleyWorld(World):
location.place_locked_item(self.create_item(item))
def set_rules(self):
set_rules(self.multiworld, self.player, self.options, self.logic, self.modified_bundles)
set_rules(self)
self.force_first_month_once_all_early_items_are_found()
def force_first_month_once_all_early_items_are_found(self):
@@ -276,11 +277,12 @@ class StardewValleyWorld(World):
key, value = self.modified_bundles[bundle_key].to_pair()
modified_bundles[key] = value
excluded_options = [options.BundleRandomization, options.BundlePrice,
options.NumberOfMovementBuffs, options.NumberOfLuckBuffs]
slot_data = dict(self.options.options)
for option in excluded_options:
slot_data.pop(option.internal_name)
excluded_options = [BundleRandomization, BundlePrice, NumberOfMovementBuffs, NumberOfLuckBuffs]
excluded_option_names = [option.internal_name for option in excluded_options]
generic_option_names = [option_name for option_name in PerGameCommonOptions.type_hints]
excluded_option_names.extend(generic_option_names)
included_option_names: List[str] = [option_name for option_name in self.options_dataclass.type_hints if option_name not in excluded_option_names]
slot_data = self.options.as_dict(*included_option_names)
slot_data.update({
"seed": self.multiworld.per_slot_randoms[self.player].randrange(1000000000), # Seed should be max 9 digits
"randomized_entrances": self.randomized_entrances,