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:
@@ -1,6 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from typing import TypedDict
|
||||
from Options import DefaultOnToggle, Toggle, Range, Choice, OptionSet
|
||||
from Options import DefaultOnToggle, PerGameCommonOptions, Toggle, Range, Choice, OptionSet
|
||||
from .Overcooked2Levels import Overcooked2Dlc
|
||||
|
||||
class LocationBalancingMode(IntEnum):
|
||||
@@ -167,32 +168,30 @@ class StarThresholdScale(Range):
|
||||
default = 35
|
||||
|
||||
|
||||
overcooked_options = {
|
||||
@dataclass
|
||||
class OC2Options(PerGameCommonOptions):
|
||||
# generator options
|
||||
"location_balancing": LocationBalancing,
|
||||
"ramp_tricks": RampTricks,
|
||||
|
||||
location_balancing: LocationBalancing
|
||||
ramp_tricks: RampTricks
|
||||
|
||||
# deathlink
|
||||
"deathlink": DeathLink,
|
||||
|
||||
deathlink: DeathLink
|
||||
|
||||
# randomization options
|
||||
"shuffle_level_order": ShuffleLevelOrder,
|
||||
"include_dlcs": DLCOptionSet,
|
||||
"include_horde_levels": IncludeHordeLevels,
|
||||
"prep_levels": PrepLevels,
|
||||
"kevin_levels": KevinLevels,
|
||||
|
||||
shuffle_level_order: ShuffleLevelOrder
|
||||
include_dlcs: DLCOptionSet
|
||||
include_horde_levels: IncludeHordeLevels
|
||||
prep_levels: PrepLevels
|
||||
kevin_levels: KevinLevels
|
||||
|
||||
# quality of life options
|
||||
"fix_bugs": FixBugs,
|
||||
"shorter_level_duration": ShorterLevelDuration,
|
||||
"short_horde_levels": ShortHordeLevels,
|
||||
"always_preserve_cooking_progress": AlwaysPreserveCookingProgress,
|
||||
"always_serve_oldest_order": AlwaysServeOldestOrder,
|
||||
"display_leaderboard_scores": DisplayLeaderboardScores,
|
||||
|
||||
fix_bugs: FixBugs
|
||||
shorter_level_duration: ShorterLevelDuration
|
||||
short_horde_levels: ShortHordeLevels
|
||||
always_preserve_cooking_progress: AlwaysPreserveCookingProgress
|
||||
always_serve_oldest_order: AlwaysServeOldestOrder
|
||||
display_leaderboard_scores: DisplayLeaderboardScores
|
||||
|
||||
# difficulty settings
|
||||
"stars_to_win": StarsToWin,
|
||||
"star_threshold_scale": StarThresholdScale,
|
||||
}
|
||||
|
||||
OC2Options = TypedDict("OC2Options", {option.__name__: option for option in overcooked_options.values()})
|
||||
stars_to_win: StarsToWin
|
||||
star_threshold_scale: StarThresholdScale
|
||||
|
||||
@@ -6,7 +6,7 @@ from worlds.AutoWorld import World, WebWorld
|
||||
|
||||
from .Overcooked2Levels import Overcooked2Dlc, Overcooked2Level, Overcooked2GenericLevel
|
||||
from .Locations import Overcooked2Location, oc2_location_name_to_id, oc2_location_id_to_name
|
||||
from .Options import overcooked_options, OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
|
||||
from .Options import OC2Options, OC2OnToggle, LocationBalancingMode, DeathLinkMode
|
||||
from .Items import item_table, Overcooked2Item, item_name_to_id, item_id_to_name, item_to_unlock_event, item_frequencies, dlc_exclusives
|
||||
from .Logic import has_requirements_for_level_star, has_requirements_for_level_access, level_shuffle_factory, is_item_progression, is_useful
|
||||
|
||||
@@ -47,7 +47,6 @@ class Overcooked2World(World):
|
||||
game = "Overcooked! 2"
|
||||
web = Overcooked2Web()
|
||||
required_client_version = (0, 3, 8)
|
||||
option_definitions = overcooked_options
|
||||
topology_present: bool = False
|
||||
data_version = 3
|
||||
|
||||
@@ -57,13 +56,14 @@ class Overcooked2World(World):
|
||||
location_id_to_name = oc2_location_id_to_name
|
||||
location_name_to_id = oc2_location_name_to_id
|
||||
|
||||
options: Dict[str, Any]
|
||||
options_dataclass = OC2Options
|
||||
options: OC2Options
|
||||
itempool: List[Overcooked2Item]
|
||||
|
||||
# Helper Functions
|
||||
|
||||
def is_level_horde(self, level_id: int) -> bool:
|
||||
return self.options["IncludeHordeLevels"] and \
|
||||
return self.options.include_horde_levels and \
|
||||
(self.level_mapping is not None) and \
|
||||
level_id in self.level_mapping.keys() and \
|
||||
self.level_mapping[level_id].is_horde
|
||||
@@ -145,11 +145,6 @@ class Overcooked2World(World):
|
||||
location
|
||||
)
|
||||
|
||||
def get_options(self) -> Dict[str, Any]:
|
||||
return OC2Options({option.__name__: getattr(self.multiworld, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.multiworld, name)[self.player].value
|
||||
for name, option in overcooked_options.items()})
|
||||
|
||||
def get_n_random_locations(self, n: int) -> List[int]:
|
||||
"""Return a list of n random non-repeating level locations"""
|
||||
levels = list()
|
||||
@@ -160,7 +155,7 @@ class Overcooked2World(World):
|
||||
for level in Overcooked2Level():
|
||||
if level.level_id == 36:
|
||||
continue
|
||||
elif not self.options["KevinLevels"] and level.level_id > 36:
|
||||
elif not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
levels.append(level.level_id)
|
||||
@@ -231,26 +226,25 @@ class Overcooked2World(World):
|
||||
|
||||
def generate_early(self):
|
||||
self.player_name = self.multiworld.player_name[self.player]
|
||||
self.options = self.get_options()
|
||||
|
||||
# 0.0 to 1.0 where 1.0 is World Record
|
||||
self.star_threshold_scale = self.options["StarThresholdScale"] / 100.0
|
||||
self.star_threshold_scale = self.options.star_threshold_scale / 100.0
|
||||
|
||||
# Parse DLCOptionSet back into enums
|
||||
self.enabled_dlc = {Overcooked2Dlc(x) for x in self.options["DLCOptionSet"]}
|
||||
self.enabled_dlc = {Overcooked2Dlc(x) for x in self.options.include_dlcs.value}
|
||||
|
||||
# Generate level unlock requirements such that the levels get harder to unlock
|
||||
# the further the game has progressed, and levels progress radially rather than linearly
|
||||
self.level_unlock_counts = level_unlock_requirement_factory(self.options["StarsToWin"])
|
||||
self.level_unlock_counts = level_unlock_requirement_factory(self.options.stars_to_win.value)
|
||||
|
||||
# Assign new kitchens to each spot on the overworld using pure random chance and nothing else
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
if self.options.shuffle_level_order:
|
||||
self.level_mapping = \
|
||||
level_shuffle_factory(
|
||||
self.multiworld.random,
|
||||
self.options["PrepLevels"] != PrepLevelMode.excluded,
|
||||
self.options["IncludeHordeLevels"],
|
||||
self.options["KevinLevels"],
|
||||
self.options.prep_levels != PrepLevelMode.excluded,
|
||||
self.options.include_horde_levels.result,
|
||||
self.options.kevin_levels.result,
|
||||
self.enabled_dlc,
|
||||
self.player_name,
|
||||
)
|
||||
@@ -277,7 +271,7 @@ class Overcooked2World(World):
|
||||
|
||||
# Create and populate "regions" (a.k.a. levels)
|
||||
for level in Overcooked2Level():
|
||||
if not self.options["KevinLevels"] and level.level_id > 36:
|
||||
if not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
# Create Region (e.g. "1-1")
|
||||
@@ -336,7 +330,7 @@ class Overcooked2World(World):
|
||||
|
||||
level_access_rule: Callable[[CollectionState], bool] = \
|
||||
lambda state, level_name=level.level_name, previous_level_completed_event_name=previous_level_completed_event_name, required_star_count=required_star_count: \
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options["RampTricks"], self.player)
|
||||
has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.options.ramp_tricks.result, self.player)
|
||||
self.connect_regions("Overworld", level.level_name, level_access_rule)
|
||||
|
||||
# Level --> Overworld
|
||||
@@ -369,11 +363,11 @@ class Overcooked2World(World):
|
||||
# Item is always useless with these settings
|
||||
continue
|
||||
|
||||
if not self.options["IncludeHordeLevels"] and item_name in ["Calmer Unbread", "Coin Purse"]:
|
||||
if not self.options.include_horde_levels and item_name in ["Calmer Unbread", "Coin Purse"]:
|
||||
# skip horde-specific items if no horde levels
|
||||
continue
|
||||
|
||||
if not self.options["KevinLevels"]:
|
||||
if not self.options.kevin_levels:
|
||||
if item_name.startswith("Kevin"):
|
||||
# skip kevin items if no kevin levels
|
||||
continue
|
||||
@@ -382,7 +376,7 @@ class Overcooked2World(World):
|
||||
# skip dark green ramp if there's no Kevin-1 to reveal it
|
||||
continue
|
||||
|
||||
if is_item_progression(item_name, self.level_mapping, self.options["KevinLevels"]):
|
||||
if is_item_progression(item_name, self.level_mapping, self.options.kevin_levels):
|
||||
# progression.append(item_name)
|
||||
classification = ItemClassification.progression
|
||||
else:
|
||||
@@ -404,7 +398,7 @@ class Overcooked2World(World):
|
||||
|
||||
# Fill any free space with filler
|
||||
pool_count = len(oc2_location_name_to_id)
|
||||
if not self.options["KevinLevels"]:
|
||||
if not self.options.kevin_levels:
|
||||
pool_count -= 8
|
||||
|
||||
while len(self.itempool) < pool_count:
|
||||
@@ -416,7 +410,7 @@ class Overcooked2World(World):
|
||||
def place_events(self):
|
||||
# Add Events (Star Acquisition)
|
||||
for level in Overcooked2Level():
|
||||
if not self.options["KevinLevels"] and level.level_id > 36:
|
||||
if not self.options.kevin_levels and level.level_id > 36:
|
||||
break
|
||||
|
||||
if level.level_id != 36:
|
||||
@@ -449,7 +443,7 @@ class Overcooked2World(World):
|
||||
# Serialize Level Order
|
||||
story_level_order = dict()
|
||||
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
if self.options.shuffle_level_order:
|
||||
for level_id in self.level_mapping:
|
||||
level: Overcooked2GenericLevel = self.level_mapping[level_id]
|
||||
story_level_order[str(level_id)] = {
|
||||
@@ -481,7 +475,7 @@ class Overcooked2World(World):
|
||||
level_unlock_requirements[str(level_id)] = level_id - 1
|
||||
|
||||
# Set Kevin Unlock Requirements
|
||||
if self.options["KevinLevels"]:
|
||||
if self.options.kevin_levels:
|
||||
def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
|
||||
location = self.multiworld.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
if location.player != self.player:
|
||||
@@ -506,7 +500,7 @@ class Overcooked2World(World):
|
||||
on_level_completed[level_id] = [item_to_unlock_event(location.item.name)]
|
||||
|
||||
# Put it all together
|
||||
star_threshold_scale = self.options["StarThresholdScale"] / 100
|
||||
star_threshold_scale = self.options.star_threshold_scale / 100
|
||||
|
||||
base_data = {
|
||||
# Changes Inherent to rando
|
||||
@@ -528,13 +522,13 @@ class Overcooked2World(World):
|
||||
"SaveFolderName": mod_name,
|
||||
"CustomOrderTimeoutPenalty": 10,
|
||||
"LevelForceHide": [37, 38, 39, 40, 41, 42, 43, 44],
|
||||
"LocalDeathLink": self.options["DeathLink"] != DeathLinkMode.disabled,
|
||||
"BurnTriggersDeath": self.options["DeathLink"] == DeathLinkMode.death_and_overcook,
|
||||
"LocalDeathLink": self.options.deathlink != DeathLinkMode.disabled,
|
||||
"BurnTriggersDeath": self.options.deathlink == DeathLinkMode.death_and_overcook,
|
||||
|
||||
# Game Modifications
|
||||
"LevelPurchaseRequirements": level_purchase_requirements,
|
||||
"Custom66TimerScale": max(0.4, 0.25 + (1.0 - star_threshold_scale)*0.6),
|
||||
"ShortHordeLevels": self.options["ShortHordeLevels"],
|
||||
"ShortHordeLevels": self.options.short_horde_levels,
|
||||
"CustomLevelOrder": custom_level_order,
|
||||
|
||||
# Items (Starting Inventory)
|
||||
@@ -580,28 +574,27 @@ class Overcooked2World(World):
|
||||
# Set remaining data in the options dict
|
||||
bugs = ["FixDoubleServing", "FixSinkBug", "FixControlStickThrowBug", "FixEmptyBurnerThrow"]
|
||||
for bug in bugs:
|
||||
self.options[bug] = self.options["FixBugs"]
|
||||
self.options["PreserveCookingProgress"] = self.options["AlwaysPreserveCookingProgress"]
|
||||
self.options["TimerAlwaysStarts"] = self.options["PrepLevels"] == PrepLevelMode.ayce
|
||||
self.options["LevelTimerScale"] = 0.666 if self.options["ShorterLevelDuration"] else 1.0
|
||||
self.options["LeaderboardScoreScale"] = {
|
||||
base_data[bug] = self.options.fix_bugs.result
|
||||
base_data["PreserveCookingProgress"] = self.options.always_preserve_cooking_progress.result
|
||||
base_data["TimerAlwaysStarts"] = self.options.prep_levels == PrepLevelMode.ayce
|
||||
base_data["LevelTimerScale"] = 0.666 if self.options.shorter_level_duration else 1.0
|
||||
base_data["LeaderboardScoreScale"] = {
|
||||
"FourStars": 1.0,
|
||||
"ThreeStars": star_threshold_scale,
|
||||
"TwoStars": star_threshold_scale * 0.75,
|
||||
"OneStar": star_threshold_scale * 0.35,
|
||||
}
|
||||
|
||||
base_data.update(self.options)
|
||||
return base_data
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
return self.fill_json_data()
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO) -> None:
|
||||
if not self.options["ShuffleLevelOrder"]:
|
||||
if not self.options.shuffle_level_order:
|
||||
return
|
||||
|
||||
world: Overcooked2World = self.multiworld.worlds[self.player]
|
||||
world: Overcooked2World = self
|
||||
spoiler_handle.write(f"\n\n{self.player_name}'s Level Order:\n\n")
|
||||
for overworld_id in world.level_mapping:
|
||||
overworld_name = Overcooked2GenericLevel(overworld_id).shortname.split("Story ")[1]
|
||||
|
||||
Reference in New Issue
Block a user