Files
Grinch-AP/worlds/zillion/options.py
Aaron Wagener 7193182294 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>
2023-10-10 22:30:20 +02:00

385 lines
9.5 KiB
Python

from collections import Counter
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
class ZillionContinues(SpecialRange):
"""
number of continues before game over
game over teleports you to your ship, keeping items and open doors
"""
default = 3
range_start = 0
range_end = 21
display_name = "continues"
special_range_names = {
"vanilla": 3,
"infinity": 21
}
class ZillionFloppyReq(Range):
""" how many floppy disks are required """
range_start = 0
range_end = 8
default = 5
display_name = "floppies required"
class VBLR(Choice):
option_vanilla = 0
option_balanced = 1
option_low = 2
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):
"""
Zillion gun power for the number of Zillion power ups you pick up
For "restrictive", Champ is the only one that can get Zillion gun power level 3.
"""
display_name = "gun levels"
class ZillionJumpLevels(VBLR):
"""
jump levels for each character level
For "restrictive", Apple is the only one that can get jump level 3.
"""
display_name = "jump levels"
class ZillionRandomizeAlarms(DefaultOnToggle):
""" whether to randomize the locations of alarm sensors """
display_name = "randomize alarms"
class ZillionMaxLevel(Range):
""" the highest level you can get """
range_start = 3
range_end = 8
default = 8
display_name = "max level"
class ZillionOpasPerLevel(Range):
"""
how many Opa-Opas are required to level up
Lower makes you level up faster.
"""
range_start = 1
range_end = 5
default = 2
display_name = "Opa-Opas per level"
class ZillionStartChar(Choice):
""" which character you start with """
option_jj = 0
option_apple = 1
option_champ = 2
display_name = "start character"
default = "random"
class ZillionIDCardCount(Range):
"""
how many ID Cards are in the game
Vanilla is 63
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 42
display_name = "ID Card count"
class ZillionBreadCount(Range):
"""
how many Breads are in the game
Vanilla is 33
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 50
display_name = "Bread count"
class ZillionOpaOpaCount(Range):
"""
how many Opa-Opas are in the game
Vanilla is 26
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 26
display_name = "Opa-Opa count"
class ZillionZillionCount(Range):
"""
how many Zillion gun power ups are in the game
Vanilla is 6
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 8
display_name = "Zillion power up count"
class ZillionFloppyDiskCount(Range):
"""
how many Floppy Disks are in the game
Vanilla is 5
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 7
display_name = "Floppy Disk count"
class ZillionScopeCount(Range):
"""
how many Scopes are in the game
Vanilla is 4
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 4
display_name = "Scope count"
class ZillionRedIDCardCount(Range):
"""
how many Red ID Cards are in the game
Vanilla is 1
maximum total for all items is 144
"""
range_start = 0
range_end = 126
default = 2
display_name = "Red ID Card count"
class ZillionEarlyScope(Toggle):
""" make sure Scope is available early """
display_name = "early scope"
class ZillionSkill(Range):
""" the difficulty level of the game """
range_start = 0
range_end = 5
default = 2
class ZillionStartingCards(SpecialRange):
"""
how many ID Cards to start the game with
Refilling at the ship also ensures you have at least this many cards.
0 gives vanilla behavior.
"""
default = 2
range_start = 0
range_end = 10
display_name = "starting cards"
special_range_names = {
"vanilla": 0
}
class ZillionRoomGen(Toggle):
""" whether to generate rooms with random terrain """
display_name = "room generation"
@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:
tr: ZzItemCounts = {
ID.card: ic["ID Card"],
ID.red: ic["Red ID Card"],
ID.floppy: ic["Floppy Disk"],
ID.bread: ic["Bread"],
ID.gun: ic["Zillion"],
ID.opa: ic["Opa-Opa"],
ID.scope: ic["Scope"],
ID.empty: ic["Empty"],
}
return tr
def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]":
"""
adjusts options to make game completion possible
`options` parameter is ZillionOptions object that was put on my world by the core
"""
skill = options.skill.value
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_option = options.gun_levels.to_zz_vblr()
guns_required = char_to_gun["Champ"][gun_option].index(3)
floppy_req = options.floppy_req
item_counts = Counter({
"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,
"Bread": 0,
"Opa-Opa": required_level - 1,
"Zillion": guns_required,
"Floppy Disk": floppy_req.value,
"Scope": 0,
"Red ID Card": 1
})
for key in minimums:
item_counts[key] = max(minimums[key], item_counts[key])
max_movables = 144 - sum(minimums.values())
movables = item_counts - minimums
while sum(movables.values()) > max_movables:
# logging.warning("zillion options validate: player options item counts too high")
total = sum(movables.values())
scaler = max_movables / total
for key in movables:
movables[key] = int(movables[key] * scaler)
item_counts = movables + minimums
# now have required items, and <= 144
# now fill remaining with empty
total = sum(item_counts.values())
diff = 144 - total
if "Empty" not in item_counts:
item_counts["Empty"] = 0
item_counts["Empty"] += diff
assert sum(item_counts.values()) == 144
max_level = options.max_level
max_level.value = max(required_level, max_level.value)
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"
# )
opas_per_level.value -= 1
# that should be all of the level requirements met
name_capitalization: Dict[str, Chars] = {
"jj": "JJ",
"apple": "Apple",
"champ": "Champ",
}
start_char = options.start_char
start_char_name = name_capitalization[start_char.current_key]
assert start_char_name in chars
starting_cards = options.starting_cards
room_gen = options.room_gen
zz_item_counts = convert_item_counts(item_counts)
zz_op = ZzOptions(
zz_item_counts,
jump_option,
gun_option,
opas_per_level.value,
max_level.value,
False, # tutorial
skill,
start_char_name,
floppy_req.value,
options.continues.value,
bool(options.randomize_alarms.value),
bool(options.early_scope.value),
True, # balance defense
starting_cards.value,
bool(room_gen.value)
)
zz_validate(zz_op)
return zz_op, item_counts