mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
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:
@@ -12,7 +12,7 @@ from BaseClasses import ItemClassification, LocationProgressType, \
|
||||
MultiWorld, Item, CollectionState, Entrance, Tutorial
|
||||
from .logic import cs_to_zz_locs
|
||||
from .region import ZillionLocation, ZillionRegion
|
||||
from .options import ZillionStartChar, zillion_options, validate
|
||||
from .options import ZillionOptions, ZillionStartChar, validate
|
||||
from .id_maps import item_name_to_id as _item_name_to_id, \
|
||||
loc_name_to_id as _loc_name_to_id, make_id_to_others, \
|
||||
zz_reg_name_to_reg_name, base_id
|
||||
@@ -70,7 +70,9 @@ class ZillionWorld(World):
|
||||
game = "Zillion"
|
||||
web = ZillionWebWorld()
|
||||
|
||||
option_definitions = zillion_options
|
||||
options_dataclass = ZillionOptions
|
||||
options: ZillionOptions
|
||||
|
||||
settings: typing.ClassVar[ZillionSettings]
|
||||
topology_present = True # indicate if world type has any meaningful layout/pathing
|
||||
|
||||
@@ -142,7 +144,10 @@ class ZillionWorld(World):
|
||||
if not hasattr(self.multiworld, "zillion_logic_cache"):
|
||||
setattr(self.multiworld, "zillion_logic_cache", {})
|
||||
|
||||
zz_op, item_counts = validate(self.multiworld, self.player)
|
||||
zz_op, item_counts = validate(self.options)
|
||||
|
||||
if zz_op.early_scope:
|
||||
self.multiworld.early_items[self.player]["Scope"] = 1
|
||||
|
||||
self._item_counts = item_counts
|
||||
|
||||
@@ -299,7 +304,8 @@ class ZillionWorld(World):
|
||||
elif start_char_counts["Champ"] > start_char_counts["Apple"]:
|
||||
to_stay = "Champ"
|
||||
else: # equal
|
||||
to_stay = multiworld.random.choice(("Apple", "Champ"))
|
||||
choices: Tuple[Literal['Apple', 'Champ', 'JJ'], ...] = ("Apple", "Champ")
|
||||
to_stay = multiworld.random.choice(choices)
|
||||
|
||||
for p, sc in players_start_chars:
|
||||
if sc != to_stay:
|
||||
|
@@ -1,13 +1,14 @@
|
||||
from collections import Counter
|
||||
# import logging
|
||||
from typing import TYPE_CHECKING, Any, Dict, Tuple, cast
|
||||
from Options import AssembleOptions, DefaultOnToggle, Range, SpecialRange, Toggle, Choice
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Tuple
|
||||
from typing_extensions import TypeGuard # remove when Python >= 3.10
|
||||
|
||||
from Options import DefaultOnToggle, PerGameCommonOptions, Range, SpecialRange, Toggle, Choice
|
||||
|
||||
from zilliandomizer.options import \
|
||||
Options as ZzOptions, char_to_gun, char_to_jump, ID, \
|
||||
VBLR as ZzVBLR, chars, Chars, ItemCounts as ZzItemCounts
|
||||
from zilliandomizer.options.parsing import validate as zz_validate
|
||||
if TYPE_CHECKING:
|
||||
from BaseClasses import MultiWorld
|
||||
|
||||
|
||||
class ZillionContinues(SpecialRange):
|
||||
@@ -41,6 +42,19 @@ class VBLR(Choice):
|
||||
option_restrictive = 3
|
||||
default = 1
|
||||
|
||||
def to_zz_vblr(self) -> ZzVBLR:
|
||||
def is_vblr(o: str) -> TypeGuard[ZzVBLR]:
|
||||
"""
|
||||
This function is because mypy doesn't support narrowing with `in`,
|
||||
https://github.com/python/mypy/issues/12535
|
||||
so this is the only way I see to get type narrowing to `Literal`.
|
||||
"""
|
||||
return o in ("vanilla", "balanced", "low", "restrictive")
|
||||
|
||||
key = self.current_key
|
||||
assert is_vblr(key), f"{key=}"
|
||||
return key
|
||||
|
||||
|
||||
class ZillionGunLevels(VBLR):
|
||||
"""
|
||||
@@ -225,27 +239,27 @@ class ZillionRoomGen(Toggle):
|
||||
display_name = "room generation"
|
||||
|
||||
|
||||
zillion_options: Dict[str, AssembleOptions] = {
|
||||
"continues": ZillionContinues,
|
||||
"floppy_req": ZillionFloppyReq,
|
||||
"gun_levels": ZillionGunLevels,
|
||||
"jump_levels": ZillionJumpLevels,
|
||||
"randomize_alarms": ZillionRandomizeAlarms,
|
||||
"max_level": ZillionMaxLevel,
|
||||
"start_char": ZillionStartChar,
|
||||
"opas_per_level": ZillionOpasPerLevel,
|
||||
"id_card_count": ZillionIDCardCount,
|
||||
"bread_count": ZillionBreadCount,
|
||||
"opa_opa_count": ZillionOpaOpaCount,
|
||||
"zillion_count": ZillionZillionCount,
|
||||
"floppy_disk_count": ZillionFloppyDiskCount,
|
||||
"scope_count": ZillionScopeCount,
|
||||
"red_id_card_count": ZillionRedIDCardCount,
|
||||
"early_scope": ZillionEarlyScope,
|
||||
"skill": ZillionSkill,
|
||||
"starting_cards": ZillionStartingCards,
|
||||
"room_gen": ZillionRoomGen,
|
||||
}
|
||||
@dataclass
|
||||
class ZillionOptions(PerGameCommonOptions):
|
||||
continues: ZillionContinues
|
||||
floppy_req: ZillionFloppyReq
|
||||
gun_levels: ZillionGunLevels
|
||||
jump_levels: ZillionJumpLevels
|
||||
randomize_alarms: ZillionRandomizeAlarms
|
||||
max_level: ZillionMaxLevel
|
||||
start_char: ZillionStartChar
|
||||
opas_per_level: ZillionOpasPerLevel
|
||||
id_card_count: ZillionIDCardCount
|
||||
bread_count: ZillionBreadCount
|
||||
opa_opa_count: ZillionOpaOpaCount
|
||||
zillion_count: ZillionZillionCount
|
||||
floppy_disk_count: ZillionFloppyDiskCount
|
||||
scope_count: ZillionScopeCount
|
||||
red_id_card_count: ZillionRedIDCardCount
|
||||
early_scope: ZillionEarlyScope
|
||||
skill: ZillionSkill
|
||||
starting_cards: ZillionStartingCards
|
||||
room_gen: ZillionRoomGen
|
||||
|
||||
|
||||
def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
|
||||
@@ -262,47 +276,34 @@ def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts:
|
||||
return tr
|
||||
|
||||
|
||||
def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
"""
|
||||
adjusts options to make game completion possible
|
||||
|
||||
`world` parameter is MultiWorld object that has my options on it
|
||||
`p` is my player id
|
||||
`options` parameter is ZillionOptions object that was put on my world by the core
|
||||
"""
|
||||
for option_name in zillion_options:
|
||||
assert hasattr(world, option_name), f"Zillion option {option_name} didn't get put in world object"
|
||||
wo = cast(Any, world) # so I don't need getattr on all the options
|
||||
|
||||
skill = wo.skill[p].value
|
||||
skill = options.skill.value
|
||||
|
||||
jump_levels = cast(ZillionJumpLevels, wo.jump_levels[p])
|
||||
jump_option = jump_levels.current_key
|
||||
required_level = char_to_jump["Apple"][cast(ZzVBLR, jump_option)].index(3) + 1
|
||||
jump_option = options.jump_levels.to_zz_vblr()
|
||||
required_level = char_to_jump["Apple"][jump_option].index(3) + 1
|
||||
if skill == 0:
|
||||
# because of hp logic on final boss
|
||||
required_level = 8
|
||||
|
||||
gun_levels = cast(ZillionGunLevels, wo.gun_levels[p])
|
||||
gun_option = gun_levels.current_key
|
||||
guns_required = char_to_gun["Champ"][cast(ZzVBLR, gun_option)].index(3)
|
||||
gun_option = options.gun_levels.to_zz_vblr()
|
||||
guns_required = char_to_gun["Champ"][gun_option].index(3)
|
||||
|
||||
floppy_req = cast(ZillionFloppyReq, wo.floppy_req[p])
|
||||
floppy_req = options.floppy_req
|
||||
|
||||
card = cast(ZillionIDCardCount, wo.id_card_count[p])
|
||||
bread = cast(ZillionBreadCount, wo.bread_count[p])
|
||||
opa = cast(ZillionOpaOpaCount, wo.opa_opa_count[p])
|
||||
gun = cast(ZillionZillionCount, wo.zillion_count[p])
|
||||
floppy = cast(ZillionFloppyDiskCount, wo.floppy_disk_count[p])
|
||||
scope = cast(ZillionScopeCount, wo.scope_count[p])
|
||||
red = cast(ZillionRedIDCardCount, wo.red_id_card_count[p])
|
||||
item_counts = Counter({
|
||||
"ID Card": card,
|
||||
"Bread": bread,
|
||||
"Opa-Opa": opa,
|
||||
"Zillion": gun,
|
||||
"Floppy Disk": floppy,
|
||||
"Scope": scope,
|
||||
"Red ID Card": red
|
||||
"ID Card": options.id_card_count,
|
||||
"Bread": options.bread_count,
|
||||
"Opa-Opa": options.opa_opa_count,
|
||||
"Zillion": options.zillion_count,
|
||||
"Floppy Disk": options.floppy_disk_count,
|
||||
"Scope": options.scope_count,
|
||||
"Red ID Card": options.red_id_card_count
|
||||
})
|
||||
minimums = Counter({
|
||||
"ID Card": 0,
|
||||
@@ -335,10 +336,10 @@ def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
item_counts["Empty"] += diff
|
||||
assert sum(item_counts.values()) == 144
|
||||
|
||||
max_level = cast(ZillionMaxLevel, wo.max_level[p])
|
||||
max_level = options.max_level
|
||||
max_level.value = max(required_level, max_level.value)
|
||||
|
||||
opas_per_level = cast(ZillionOpasPerLevel, wo.opas_per_level[p])
|
||||
opas_per_level = options.opas_per_level
|
||||
while (opas_per_level.value > 1) and (1 + item_counts["Opa-Opa"] // opas_per_level.value < max_level.value):
|
||||
# logging.warning(
|
||||
# "zillion options validate: option opas_per_level incompatible with options max_level and opa_opa_count"
|
||||
@@ -347,39 +348,34 @@ def validate(world: "MultiWorld", p: int) -> "Tuple[ZzOptions, Counter[str]]":
|
||||
|
||||
# that should be all of the level requirements met
|
||||
|
||||
name_capitalization = {
|
||||
name_capitalization: Dict[str, Chars] = {
|
||||
"jj": "JJ",
|
||||
"apple": "Apple",
|
||||
"champ": "Champ",
|
||||
}
|
||||
|
||||
start_char = cast(ZillionStartChar, wo.start_char[p])
|
||||
start_char = options.start_char
|
||||
start_char_name = name_capitalization[start_char.current_key]
|
||||
assert start_char_name in chars
|
||||
start_char_name = cast(Chars, start_char_name)
|
||||
|
||||
starting_cards = cast(ZillionStartingCards, wo.starting_cards[p])
|
||||
starting_cards = options.starting_cards
|
||||
|
||||
room_gen = cast(ZillionRoomGen, wo.room_gen[p])
|
||||
|
||||
early_scope = cast(ZillionEarlyScope, wo.early_scope[p])
|
||||
if early_scope:
|
||||
world.early_items[p]["Scope"] = 1
|
||||
room_gen = options.room_gen
|
||||
|
||||
zz_item_counts = convert_item_counts(item_counts)
|
||||
zz_op = ZzOptions(
|
||||
zz_item_counts,
|
||||
cast(ZzVBLR, jump_option),
|
||||
cast(ZzVBLR, gun_option),
|
||||
jump_option,
|
||||
gun_option,
|
||||
opas_per_level.value,
|
||||
max_level.value,
|
||||
False, # tutorial
|
||||
skill,
|
||||
start_char_name,
|
||||
floppy_req.value,
|
||||
wo.continues[p].value,
|
||||
wo.randomize_alarms[p].value,
|
||||
False, # early scope is done with AP early_items API
|
||||
options.continues.value,
|
||||
bool(options.randomize_alarms.value),
|
||||
bool(options.early_scope.value),
|
||||
True, # balance defense
|
||||
starting_cards.value,
|
||||
bool(room_gen.value)
|
||||
|
@@ -1 +1,2 @@
|
||||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@d7122bcbeda40da5db26d60fad06246a1331706f#0.5.4
|
||||
typing-extensions>=4.7, <5
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from . import ZillionTestBase
|
||||
|
||||
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, validate
|
||||
from worlds.zillion.options import ZillionJumpLevels, ZillionGunLevels, ZillionOptions, validate
|
||||
from zilliandomizer.options import VBLR_CHOICES
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ class OptionsTest(ZillionTestBase):
|
||||
|
||||
def test_validate_default(self) -> None:
|
||||
self.world_setup()
|
||||
validate(self.multiworld, 1)
|
||||
options = self.multiworld.worlds[1].options
|
||||
assert isinstance(options, ZillionOptions)
|
||||
validate(options)
|
||||
|
||||
def test_vblr_ap_to_zz(self) -> None:
|
||||
""" all of the valid values for the AP options map to valid values for ZZ options """
|
||||
@@ -20,7 +22,9 @@ class OptionsTest(ZillionTestBase):
|
||||
for value in vblr_class.name_lookup.values():
|
||||
self.options = {option_name: value}
|
||||
self.world_setup()
|
||||
zz_options, _item_counts = validate(self.multiworld, 1)
|
||||
options = self.multiworld.worlds[1].options
|
||||
assert isinstance(options, ZillionOptions)
|
||||
zz_options, _item_counts = validate(options)
|
||||
assert getattr(zz_options, option_name) in VBLR_CHOICES
|
||||
|
||||
# TODO: test validate with invalid combinations of options
|
||||
|
Reference in New Issue
Block a user