Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: move option results to the World class instead of MultiWorld #993

Merged
merged 120 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
24471ce
map option objects to a `World.options` dict
alwaysintreble Sep 2, 2022
220571f
convert RoR2 to options dict system for testing
alwaysintreble Sep 2, 2022
643205c
add temp behavior for lttp with notes
alwaysintreble Sep 2, 2022
695c94c
copy/paste bad
alwaysintreble Sep 25, 2022
caaf051
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Sep 25, 2022
d481eed
convert `set_default_common_options` to a namespace property
alwaysintreble Sep 29, 2022
f9a8bb2
reorganize test call order
alwaysintreble Sep 29, 2022
cded105
have fill_restrictive use the new options system
alwaysintreble Sep 29, 2022
be3fa3a
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Oct 18, 2022
52290df
update world api
alwaysintreble Oct 18, 2022
5896e6c
update soe tests
alwaysintreble Oct 18, 2022
cdcd06c
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Oct 29, 2022
2581d57
fix world api
alwaysintreble Oct 29, 2022
121152b
core: auto initialize a dataclass on the World class with the option …
el-u Jan 9, 2023
82ff125
core: auto initialize a dataclass on the World class with the option …
el-u Jan 9, 2023
ad2f59e
Merge pull request #14 from el-u/options_dict
alwaysintreble Jan 9, 2023
43353de
add `as_dict` method to the options dataclass
alwaysintreble Jan 9, 2023
af5647d
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Jan 9, 2023
76833d5
fix namespace issues with tests
alwaysintreble Jan 9, 2023
d85fb69
have current option updates use `.value` instead of changing the option
alwaysintreble Feb 12, 2023
42c1abb
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Feb 12, 2023
cfdc0b1
update ror2 to use the new options system again
alwaysintreble Feb 12, 2023
eaadb6e
revert the junk pool dict since it's cased differently
alwaysintreble Feb 12, 2023
d890de4
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Feb 14, 2023
0658a5b
fix begin_with_loop typo
alwaysintreble Feb 14, 2023
effbca0
write new and old options to spoiler
alwaysintreble Feb 14, 2023
b78b7d3
change factorio option behavior back
alwaysintreble Feb 14, 2023
94d18dc
fix comparisons
alwaysintreble Feb 14, 2023
0c0663b
move common and per_game_common options to new system
alwaysintreble Feb 14, 2023
a638582
core: automatically create missing options_dataclass from legacy opti…
el-u Feb 14, 2023
11214aa
Merge pull request #15 from el-u/options_dict
alwaysintreble Feb 14, 2023
fe679fc
remove spoiler special casing and add back the Factorio option changi…
alwaysintreble Feb 14, 2023
c5684bb
give ArchipIDLE the default options_dataclass so its options get gene…
alwaysintreble Feb 15, 2023
cecd3f7
reimplement `inspect.get_annotations`
alwaysintreble Feb 17, 2023
1fbc1a4
move option info generation for webhost to new system
alwaysintreble Feb 17, 2023
c3ad00b
need to include Common and PerGame common since __annotations__ doesn…
alwaysintreble Feb 17, 2023
1730eaf
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Feb 17, 2023
1b1ee31
use get_type_hints for the options dictionary
alwaysintreble Feb 21, 2023
85e98a0
typing.get_type_hints returns the bases too.
alwaysintreble Feb 21, 2023
a24bb2e
forgot to sweep through generate
alwaysintreble Feb 21, 2023
9df025d
sweep through all the tests
alwaysintreble Feb 21, 2023
337133c
swap to a metaclass property
alwaysintreble Feb 22, 2023
aadbd56
move remaining usages from get_type_hints to metaclass property
el-u Feb 25, 2023
2584535
move remaining usages from __annotations__ to metaclass property
el-u Feb 25, 2023
0ae2acc
move remaining usages from legacy dictionaries to metaclass property
el-u Feb 25, 2023
a147aae
remove legacy dictionaries
el-u Feb 25, 2023
88db4f7
cache the metaclass property
el-u Feb 25, 2023
01374e0
clarify inheritance in world api
el-u Feb 25, 2023
9d4ab5b
Merge pull request #16 from el-u/options_dict
alwaysintreble Feb 25, 2023
069328c
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Mar 6, 2023
b831bac
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Mar 13, 2023
ac123db
move the messenger to new options system
alwaysintreble Mar 13, 2023
e6806ed
add an assert for my dumb
alwaysintreble Mar 13, 2023
7c7a49f
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Mar 14, 2023
2c8afbc
update the doc
alwaysintreble Mar 14, 2023
f8ba777
rename o to options
alwaysintreble Mar 14, 2023
984d659
missed a spot
alwaysintreble Mar 14, 2023
61b82f1
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Apr 1, 2023
e959c49
update new messenger options
alwaysintreble Apr 1, 2023
ec8c75b
comment spacing
alwaysintreble Apr 1, 2023
5f32194
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble May 21, 2023
2b46533
fix tests
alwaysintreble May 21, 2023
e6424e2
fix missing import
alwaysintreble May 21, 2023
f189f5d
make the documentation definition more accurate
alwaysintreble May 25, 2023
73e5875
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble May 31, 2023
2ba7464
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Jun 28, 2023
3e1be1e
use options system for loc creation
alwaysintreble Jun 28, 2023
fc93f97
type cast MessengerWorld
alwaysintreble Jun 28, 2023
0b9959e
fix typo and use quotes for cast
alwaysintreble Jun 28, 2023
996697f
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Jul 19, 2023
266acd2
LTTP: set random seed in tests
alwaysintreble Jul 19, 2023
c39081a
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Jul 23, 2023
4596be9
ArchipIdle: remove change here as it's default on AutoWorld
alwaysintreble Jul 23, 2023
41ed545
Stardew: Need to set state because `set_default_common_options` used to
alwaysintreble Jul 23, 2023
26976b8
The Messenger: update shop rando and helpers to new system; optimize …
alwaysintreble Jul 23, 2023
7a114d4
Add a kwarg to `as_dict` to do the casing for you
alwaysintreble Jul 23, 2023
bb06b3a
RoR2: use new kwarg for less code
alwaysintreble Jul 23, 2023
8c8adbf
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Sep 1, 2023
f5da39e
RoR2: revert some accidental reverts
alwaysintreble Sep 2, 2023
9cf454b
The Messenger: remove an unnecessary variable
alwaysintreble Sep 2, 2023
37f6d8b
remove TypeVar that isn't used
beauxq Sep 2, 2023
d381d1d
CommonOptions not abstract
beauxq Sep 2, 2023
6d418f9
Docs: fix mistake in options api.md
alwaysintreble Sep 2, 2023
c02be73
remove unused TypeVar
alwaysintreble Sep 2, 2023
2e30f07
create options for item link worlds
alwaysintreble Sep 3, 2023
8583c2a
revert accidental doc removals
alwaysintreble Sep 3, 2023
c58f4a5
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Sep 26, 2023
c3a666f
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Sep 27, 2023
833f09c
Item Links: set default options on group
alwaysintreble Sep 27, 2023
06ba9ba
change Zillion to new options dataclass
beauxq Sep 28, 2023
7d09402
remove unused parameter to function
beauxq Sep 28, 2023
ddb630b
use TypeGuard for Literal narrowing
beauxq Oct 1, 2023
de98a33
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Oct 3, 2023
cf0e267
Merge remote-tracking branch 'Main/main' into options_dict
alwaysintreble Oct 6, 2023
dc041df
Merge branch 'main' into options_dict
black-sliver Oct 7, 2023
366a069
Merge branch 'options_dict' into treble/options_dict
beauxq Oct 7, 2023
668d598
move dlc quest to new api
alwaysintreble Oct 7, 2023
b952aaa
move overcooked 2 to new api
alwaysintreble Oct 7, 2023
555ac4c
Merge remote-tracking branch 'TrebleAP/options_dict' into options_dict
alwaysintreble Oct 7, 2023
75e2c6a
Zillion: move to new options dataclass
alwaysintreble Oct 7, 2023
8a5c157
fixed some missed code in oc2
alwaysintreble Oct 7, 2023
4a9e222
Merge remote-tracking branch 'TrebleAP/options_dict' into options_dict
alwaysintreble Oct 7, 2023
bcec62e
- Tried to be compliant with 993 (WIP?)
agilbert1412 Oct 7, 2023
0c1b3b2
- I think it all works now
agilbert1412 Oct 8, 2023
db38f11
- Removed last trace of me touching core
agilbert1412 Oct 8, 2023
5eb17a1
typo
alwaysintreble Oct 8, 2023
16ae3ad
It now passes all tests!
agilbert1412 Oct 8, 2023
228dfaa
Improve options, fix all issues I hope
agilbert1412 Oct 9, 2023
92f08a7
- Fixed init options
agilbert1412 Oct 9, 2023
095195a
dlcquest: fix bad imports
alwaysintreble Oct 9, 2023
f5c0e23
missed a file
alwaysintreble Oct 9, 2023
c38cce9
- Reduce code duplication
agilbert1412 Oct 9, 2023
4876639
add as_dict documentation
alwaysintreble Oct 9, 2023
b0f7671
- Use .items(), get option name more directly, fix slot data content
agilbert1412 Oct 10, 2023
82ae9df
- Remove generic options from the slot data
agilbert1412 Oct 10, 2023
db21617
Make Stardew Valley compliant
alwaysintreble Oct 10, 2023
86e956b
resolve conflicts
alwaysintreble Oct 10, 2023
130da25
improve slot data documentation
alwaysintreble Oct 10, 2023
d8fab8e
remove `CommonOptions.get_value` (#21)
beauxq Oct 10, 2023
1e707c7
better slot data description
alwaysintreble Oct 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 15 additions & 25 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,25 +226,24 @@ def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optio
range(1, self.players + 1)}

def set_options(self, args: Namespace) -> None:
for option_key in Options.common_options:
setattr(self, option_key, getattr(args, option_key, {}))
for option_key in Options.per_game_common_options:
setattr(self, option_key, getattr(args, option_key, {}))

for player in self.player_ids:
self.custom_data[player] = {}
world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]]
for option_key in world_type.option_definitions:
setattr(self, option_key, getattr(args, option_key, {}))

self.worlds[player] = world_type(self, player)
self.worlds[player].random = self.per_slot_randoms[player]
for option_key in world_type.options_dataclass.type_hints:
option_values = getattr(args, option_key, {})
setattr(self, option_key, option_values)
# TODO - remove this loop once all worlds use options dataclasses
options_dataclass: typing.Type[Options.PerGameCommonOptions] = self.worlds[player].options_dataclass
self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player]
for option_key in options_dataclass.type_hints})

def set_item_links(self):
item_links = {}
replacement_prio = [False, True, None]
for player in self.player_ids:
for item_link in self.item_links[player].value:
for item_link in self.worlds[player].options.item_links.value:
if item_link["name"] in item_links:
if item_links[item_link["name"]]["game"] != self.game[player]:
raise Exception(f"Cannot ItemLink across games. Link: {item_link['name']}")
Expand Down Expand Up @@ -299,14 +298,6 @@ def set_item_links(self):
group["non_local_items"] = item_link["non_local_items"]
group["link_replacement"] = replacement_prio[item_link["link_replacement"]]

# intended for unittests
def set_default_common_options(self):
for option_key, option in Options.common_options.items():
setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids})
for option_key, option in Options.per_game_common_options.items():
setattr(self, option_key, {player_id: option(option.default) for player_id in self.player_ids})
self.state = CollectionState(self)

def secure(self):
self.random = ThreadBarrierProxy(secrets.SystemRandom())
self.is_race = True
Expand Down Expand Up @@ -863,31 +854,31 @@ def add_locations(self, locations: Dict[str, Optional[int]],
"""
Adds locations to the Region object, where location_type is your Location class and locations is a dict of
location names to address.

:param locations: dictionary of locations to be created and added to this Region `{name: ID}`
:param location_type: Location class to be used to create the locations with"""
if location_type is None:
location_type = Location
for location, address in locations.items():
self.locations.append(location_type(self.player, location, address, self))

def connect(self, connecting_region: Region, name: Optional[str] = None,
rule: Optional[Callable[[CollectionState], bool]] = None) -> None:
"""
Connects this Region to another Region, placing the provided rule on the connection.

:param connecting_region: Region object to connect to path is `self -> exiting_region`
:param name: name of the connection being created
:param rule: callable to determine access of this connection to go from self to the exiting_region"""
exit_ = self.create_exit(name if name else f"{self.name} -> {connecting_region.name}")
if rule:
exit_.access_rule = rule
exit_.connect(connecting_region)

def create_exit(self, name: str) -> Entrance:
"""
Creates and returns an Entrance object as an exit of this region.

:param name: name of the Entrance being created
"""
exit_ = self.entrance_type(self.player, name, self)
Expand Down Expand Up @@ -1257,7 +1248,7 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st

def to_file(self, filename: str) -> None:
def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
res = getattr(self.multiworld, option_key)[player]
res = getattr(self.multiworld.worlds[player].options, option_key)
display_name = getattr(option_obj, "display_name", option_key)
outfile.write(f"{display_name + ':':33}{res.current_option_name}\n")

Expand All @@ -1275,8 +1266,7 @@ def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
outfile.write('\nPlayer %d: %s\n' % (player, self.multiworld.get_player_name(player)))
outfile.write('Game: %s\n' % self.multiworld.game[player])

options = ChainMap(Options.per_game_common_options, self.multiworld.worlds[player].option_definitions)
for f_option, option in options.items():
for f_option, option in self.multiworld.worlds[player].options_dataclass.type_hints.items():
write_option(f_option, option)

AutoWorld.call_single(self.multiworld, "write_spoiler_header", player, outfile)
Expand Down
12 changes: 7 additions & 5 deletions Fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from collections import Counter, deque

from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld
from Options import Accessibility

from worlds.AutoWorld import call_all
from worlds.generic.Rules import add_item_rule

Expand Down Expand Up @@ -70,7 +72,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
spot_to_fill: typing.Optional[Location] = None

# if minimal accessibility, only check whether location is reachable if game not beatable
if world.accessibility[item_to_place.player] == 'minimal':
if world.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal:
perform_access_check = not world.has_beaten_game(maximum_exploration_state,
item_to_place.player) \
if single_player_placement else not has_beaten_game
Expand Down Expand Up @@ -265,7 +267,7 @@ def fast_fill(world: MultiWorld,

def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]):
maximum_exploration_state = sweep_from_pool(state, pool)
minimal_players = {player for player in world.player_ids if world.accessibility[player] == "minimal"}
minimal_players = {player for player in world.player_ids if world.worlds[player].options.accessibility == "minimal"}
unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and
not location.can_reach(maximum_exploration_state)]
for location in unreachable_locations:
Expand All @@ -288,7 +290,7 @@ def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locat
unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)]
if unreachable_locations:
def forbid_important_item_rule(item: Item):
return not ((item.classification & 0b0011) and world.accessibility[item.player] != 'minimal')
return not ((item.classification & 0b0011) and world.worlds[item.player].options.accessibility != 'minimal')

for location in unreachable_locations:
add_item_rule(location, forbid_important_item_rule)
Expand Down Expand Up @@ -531,9 +533,9 @@ def balance_multiworld_progression(world: MultiWorld) -> None:
# If other players are below the threshold value, swap progression in this sphere into earlier spheres,
# which gives more locations available by this sphere.
balanceable_players: typing.Dict[int, float] = {
player: world.progression_balancing[player] / 100
player: world.worlds[player].options.progression_balancing / 100
for player in world.player_ids
if world.progression_balancing[player] > 0
if world.worlds[player].options.progression_balancing > 0
}
if not balanceable_players:
logging.info('Skipping multiworld progression balancing.')
Expand Down
18 changes: 7 additions & 11 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ def main(args=None, callback=ERmain):
for yaml in weights_cache[path]:
if category_name is None:
for category in yaml:
if category in AutoWorldRegister.world_types and key in Options.common_options:
if category in AutoWorldRegister.world_types and \
key in Options.CommonOptions.type_hints:
yaml[category][key] = option
elif category_name not in yaml:
logging.warning(f"Meta: Category {category_name} is not present in {path}.")
Expand Down Expand Up @@ -340,7 +341,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
return get_choice(option_key, category_dict)
if game in AutoWorldRegister.world_types:
game_world = AutoWorldRegister.world_types[game]
options = ChainMap(game_world.option_definitions, Options.per_game_common_options)
options = game_world.options_dataclass.type_hints
if option_key in options:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
Expand Down Expand Up @@ -445,8 +446,8 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
f"which is not enabled.")

ret = argparse.Namespace()
for option_key in Options.per_game_common_options:
if option_key in weights and option_key not in Options.common_options:
for option_key in Options.PerGameCommonOptions.type_hints:
if option_key in weights and option_key not in Options.CommonOptions.type_hints:
raise Exception(f"Option {option_key} has to be in a game's section, not on its own.")

ret.game = get_choice("game", weights)
Expand All @@ -466,16 +467,11 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
game_weights = weights[ret.game]

ret.name = get_choice('name', weights)
for option_key, option in Options.common_options.items():
for option_key, option in Options.CommonOptions.type_hints.items():
setattr(ret, option_key, option.from_any(get_choice(option_key, weights, option.default)))

for option_key, option in world_type.option_definitions.items():
for option_key, option in world_type.options_dataclass.type_hints.items():
handle_option(ret, game_weights, option_key, option, plando_options)
for option_key, option in Options.per_game_common_options.items():
# skip setting this option if already set from common_options, defaulting to root option
if option_key not in world_type.option_definitions and \
(option_key not in Options.common_options or option_key in game_weights):
handle_option(ret, game_weights, option_key, option, plando_options)
if PlandoOptions.items in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
if ret.game == "Minecraft" or ret.game == "Ocarina of Time":
Expand Down
22 changes: 11 additions & 11 deletions Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
logger.info('')

for player in world.player_ids:
for item_name, count in world.start_inventory[player].value.items():
for item_name, count in world.worlds[player].options.start_inventory.value.items():
for _ in range(count):
world.push_precollected(world.create_item(item_name, player))

Expand All @@ -130,15 +130,15 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No

for player in world.player_ids:
# items can't be both local and non-local, prefer local
world.non_local_items[player].value -= world.local_items[player].value
world.non_local_items[player].value -= set(world.local_early_items[player])
world.worlds[player].options.non_local_items.value -= world.worlds[player].options.local_items.value
world.worlds[player].options.non_local_items.value -= set(world.local_early_items[player])

AutoWorld.call_all(world, "set_rules")

for player in world.player_ids:
exclusion_rules(world, player, world.exclude_locations[player].value)
world.priority_locations[player].value -= world.exclude_locations[player].value
for location_name in world.priority_locations[player].value:
exclusion_rules(world, player, world.worlds[player].options.exclude_locations.value)
world.worlds[player].options.priority_locations.value -= world.worlds[player].options.exclude_locations.value
for location_name in world.worlds[player].options.priority_locations.value:
try:
location = world.get_location(location_name, player)
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
Expand All @@ -151,8 +151,8 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
if world.players > 1:
locality_rules(world)
else:
world.non_local_items[1].value = set()
world.local_items[1].value = set()
world.worlds[1].options.non_local_items.value = set()
world.worlds[1].options.local_items.value = set()

AutoWorld.call_all(world, "generate_basic")

Expand Down Expand Up @@ -360,11 +360,11 @@ def precollect_hint(location):
f" {location}"
locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags
if location.name in world.start_location_hints[location.player]:
if location.name in world.worlds[location.player].options.start_location_hints:
precollect_hint(location)
elif location.item.name in world.start_hints[location.item.player]:
elif location.item.name in world.worlds[location.item.player].options.start_hints:
precollect_hint(location)
elif any([location.item.name in world.start_hints[player]
elif any([location.item.name in world.worlds[player].options.start_hints
for player in world.groups.get(location.item.player, {}).get("players", [])]):
precollect_hint(location)

Expand Down
88 changes: 69 additions & 19 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import abc
import logging
from copy import deepcopy
from dataclasses import dataclass
import functools
import math
import numbers
import random
Expand Down Expand Up @@ -211,6 +214,12 @@ def __gt__(self, other: typing.Union[int, NumericOption]) -> bool:
else:
return self.value > other

def __ge__(self, other: typing.Union[int, NumericOption]) -> bool:
if isinstance(other, NumericOption):
return self.value >= other.value
else:
return self.value >= other

def __bool__(self) -> bool:
return bool(self.value)

Expand Down Expand Up @@ -896,10 +905,55 @@ class ProgressionBalancing(SpecialRange):
}


common_options = {
"progression_balancing": ProgressionBalancing,
"accessibility": Accessibility
}
class OptionsMetaProperty(type):
def __new__(mcs,
name: str,
bases: typing.Tuple[type, ...],
attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty":
for attr_type in attrs.values():
assert not isinstance(attr_type, AssembleOptions),\
f"Options for {name} should be type hinted on the class, not assigned"
return super().__new__(mcs, name, bases, attrs)

@property
@functools.lru_cache(maxsize=None)
def type_hints(cls) -> typing.Dict[str, typing.Type[Option[typing.Any]]]:
"""Returns type hints of the class as a dictionary."""
return typing.get_type_hints(cls)


@dataclass
class CommonOptions(metaclass=OptionsMetaProperty):
progression_balancing: ProgressionBalancing
accessibility: Accessibility

def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, typing.Any]:
"""
Returns a dictionary of [str, Option.value]

:param option_names: names of the options to return
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
"""
option_results = {}
for option_name in option_names:
if option_name in type(self).type_hints:
if casing == "snake":
display_name = option_name
elif casing == "camel":
split_name = [name.title() for name in option_name.split("_")]
split_name[0] = split_name[0].lower()
display_name = "".join(split_name)
elif casing == "pascal":
display_name = "".join([name.title() for name in option_name.split("_")])
elif casing == "kebab":
display_name = option_name.replace("_", "-")
else:
raise ValueError(f"{casing} is invalid casing for as_dict. "
"Valid names are 'snake', 'camel', 'pascal', 'kebab'.")
option_results[display_name] = getattr(self, option_name).value
else:
raise ValueError(f"{option_name} not found in {tuple(type(self).type_hints)}")
return option_results


class LocalItems(ItemSet):
Expand Down Expand Up @@ -1020,17 +1074,16 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P
link.setdefault("link_replacement", None)


per_game_common_options = {
**common_options, # can be overwritten per-game
"local_items": LocalItems,
"non_local_items": NonLocalItems,
"start_inventory": StartInventory,
"start_hints": StartHints,
"start_location_hints": StartLocationHints,
"exclude_locations": ExcludeLocations,
"priority_locations": PriorityLocations,
"item_links": ItemLinks
}
@dataclass
class PerGameCommonOptions(CommonOptions):
local_items: LocalItems
non_local_items: NonLocalItems
start_inventory: StartInventory
start_hints: StartHints
start_location_hints: StartLocationHints
exclude_locations: ExcludeLocations
priority_locations: PriorityLocations
item_links: ItemLinks


def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
Expand Down Expand Up @@ -1071,10 +1124,7 @@ def dictify_range(option: typing.Union[Range, SpecialRange]):

for game_name, world in AutoWorldRegister.world_types.items():
if not world.hidden or generate_hidden:
all_options: typing.Dict[str, AssembleOptions] = {
**per_game_common_options,
**world.option_definitions
}
all_options: typing.Dict[str, AssembleOptions] = world.options_dataclass.type_hints

with open(local_path("data", "options.yaml")) as f:
file_data = f.read()
Expand Down
Loading
Loading