mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
SoE: use new AP API and naming and make APworld (#2701)
* SoE: new file naming also fixes test base deprecation * SoE: use options_dataclass * SoE: moar typing * SoE: no more multiworld.random * SoE: replace LogicMixin by SoEPlayerLogic object * SoE: add test that rocket parts always exist * SoE: Even moar typing * SoE: can haz apworld now * SoE: pep up test naming * SoE: use self.options for trap chances * SoE: remove unused import with outdated comment * SoE: move flag and trap extraction to dataclass as suggested by beauxq * SoE: test trap option parsing and item generation
This commit is contained in:
298
worlds/soe/options.py
Normal file
298
worlds/soe/options.py
Normal file
@@ -0,0 +1,298 @@
|
||||
from dataclasses import dataclass, fields
|
||||
from typing import Any, cast, Dict, Iterator, List, Tuple, Protocol
|
||||
|
||||
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, PerGameCommonOptions, ProgressionBalancing, \
|
||||
Range, Toggle
|
||||
|
||||
|
||||
# typing boilerplate
|
||||
class FlagsProtocol(Protocol):
|
||||
value: int
|
||||
default: int
|
||||
flags: List[str]
|
||||
|
||||
|
||||
class FlagProtocol(Protocol):
|
||||
value: int
|
||||
default: int
|
||||
flag: str
|
||||
|
||||
|
||||
# meta options
|
||||
class EvermizerFlags:
|
||||
flags: List[str]
|
||||
|
||||
def to_flag(self: FlagsProtocol) -> str:
|
||||
return self.flags[self.value]
|
||||
|
||||
|
||||
class EvermizerFlag:
|
||||
flag: str
|
||||
|
||||
def to_flag(self: FlagProtocol) -> str:
|
||||
return self.flag if self.value != self.default else ''
|
||||
|
||||
|
||||
class OffOnFullChoice(Choice):
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
option_full = 2
|
||||
alias_chaos = 2
|
||||
|
||||
|
||||
class OffOnLogicChoice(Choice):
|
||||
option_off = 0
|
||||
option_on = 1
|
||||
option_logic = 2
|
||||
|
||||
|
||||
# actual options
|
||||
class Difficulty(EvermizerFlags, Choice):
|
||||
"""Changes relative spell cost and stuff"""
|
||||
display_name = "Difficulty"
|
||||
option_easy = 0
|
||||
option_normal = 1
|
||||
option_hard = 2
|
||||
option_mystery = 3 # 'random' is reserved
|
||||
alias_chaos = 3
|
||||
default = 1
|
||||
flags = ['e', 'n', 'h', 'x']
|
||||
|
||||
|
||||
class EnergyCore(EvermizerFlags, Choice):
|
||||
"""How to obtain the Energy Core"""
|
||||
display_name = "Energy Core"
|
||||
option_vanilla = 0
|
||||
option_shuffle = 1
|
||||
option_fragments = 2
|
||||
default = 1
|
||||
flags = ['z', '', 'Z']
|
||||
|
||||
|
||||
class RequiredFragments(Range):
|
||||
"""Required fragment count for Energy Core = Fragments"""
|
||||
display_name = "Required Fragments"
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
default = 10
|
||||
|
||||
|
||||
class AvailableFragments(Range):
|
||||
"""Placed fragment count for Energy Core = Fragments"""
|
||||
display_name = "Available Fragments"
|
||||
range_start = 1
|
||||
range_end = 99
|
||||
default = 11
|
||||
|
||||
|
||||
class MoneyModifier(Range):
|
||||
"""Money multiplier in %"""
|
||||
display_name = "Money Modifier"
|
||||
range_start = 1
|
||||
range_end = 2500
|
||||
default = 200
|
||||
|
||||
|
||||
class ExpModifier(Range):
|
||||
"""EXP multiplier for Weapons, Characters and Spells in %"""
|
||||
display_name = "Exp Modifier"
|
||||
range_start = 1
|
||||
range_end = 2500
|
||||
default = 200
|
||||
|
||||
|
||||
class SequenceBreaks(EvermizerFlags, OffOnLogicChoice):
|
||||
"""Disable, enable some sequence breaks or put them in logic"""
|
||||
display_name = "Sequence Breaks"
|
||||
default = 0
|
||||
flags = ['', 'j', 'J']
|
||||
|
||||
|
||||
class OutOfBounds(EvermizerFlags, OffOnLogicChoice):
|
||||
"""Disable, enable the out-of-bounds glitch or put it in logic"""
|
||||
display_name = "Out Of Bounds"
|
||||
default = 0
|
||||
flags = ['', 'u', 'U']
|
||||
|
||||
|
||||
class FixCheats(EvermizerFlag, DefaultOnToggle):
|
||||
"""Fix cheats left in by the devs (not desert skip)"""
|
||||
display_name = "Fix Cheats"
|
||||
flag = '2'
|
||||
|
||||
|
||||
class FixInfiniteAmmo(EvermizerFlag, Toggle):
|
||||
"""Fix infinite ammo glitch"""
|
||||
display_name = "Fix Infinite Ammo"
|
||||
flag = '5'
|
||||
|
||||
|
||||
class FixAtlasGlitch(EvermizerFlag, Toggle):
|
||||
"""Fix atlas underflowing stats"""
|
||||
display_name = "Fix Atlas Glitch"
|
||||
flag = '6'
|
||||
|
||||
|
||||
class FixWingsGlitch(EvermizerFlag, Toggle):
|
||||
"""Fix wings making you invincible in some areas"""
|
||||
display_name = "Fix Wings Glitch"
|
||||
flag = '7'
|
||||
|
||||
|
||||
class ShorterDialogs(EvermizerFlag, DefaultOnToggle):
|
||||
"""Cuts some dialogs"""
|
||||
display_name = "Shorter Dialogs"
|
||||
flag = '9'
|
||||
|
||||
|
||||
class ShortBossRush(EvermizerFlag, DefaultOnToggle):
|
||||
"""Start boss rush at Metal Magmar, cut enemy HP in half"""
|
||||
display_name = "Short Boss Rush"
|
||||
flag = 'f'
|
||||
|
||||
|
||||
class Ingredienizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles, Full randomizes spell ingredients"""
|
||||
display_name = "Ingredienizer"
|
||||
default = 1
|
||||
flags = ['i', '', 'I']
|
||||
|
||||
|
||||
class Sniffamizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles, Full randomizes drops in sniff locations"""
|
||||
display_name = "Sniffamizer"
|
||||
default = 1
|
||||
flags = ['s', '', 'S']
|
||||
|
||||
|
||||
class Callbeadamizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On Shuffles call bead characters, Full shuffles individual spells"""
|
||||
display_name = "Callbeadamizer"
|
||||
default = 1
|
||||
flags = ['c', '', 'C']
|
||||
|
||||
|
||||
class Musicmizer(EvermizerFlag, Toggle):
|
||||
"""Randomize music for some rooms"""
|
||||
display_name = "Musicmizer"
|
||||
flag = 'm'
|
||||
|
||||
|
||||
class Doggomizer(EvermizerFlags, OffOnFullChoice):
|
||||
"""On shuffles dog per act, Full randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
|
||||
display_name = "Doggomizer"
|
||||
option_pupdunk = 3
|
||||
default = 0
|
||||
flags = ['', 'd', 'D', 'p']
|
||||
|
||||
|
||||
class TurdoMode(EvermizerFlag, Toggle):
|
||||
"""Replace offensive spells by Turd Balls with varying strength and make weapons weak"""
|
||||
display_name = "Turdo Mode"
|
||||
flag = 't'
|
||||
|
||||
|
||||
class TrapCount(Range):
|
||||
"""Replace some filler items with traps"""
|
||||
display_name = "Trap Count"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 0
|
||||
|
||||
|
||||
# more meta options
|
||||
class ItemChanceMeta(AssembleOptions):
|
||||
def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "ItemChanceMeta":
|
||||
if 'item_name' in attrs:
|
||||
attrs["display_name"] = f"{attrs['item_name']} Chance"
|
||||
attrs["range_start"] = 0
|
||||
attrs["range_end"] = 100
|
||||
cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs)
|
||||
return cast(ItemChanceMeta, cls)
|
||||
|
||||
|
||||
class TrapChance(Range, metaclass=ItemChanceMeta):
|
||||
item_name: str
|
||||
default = 20
|
||||
|
||||
|
||||
# more actual options
|
||||
class TrapChanceQuake(TrapChance):
|
||||
"""Sets the chance/ratio of quake traps"""
|
||||
item_name = "Quake Trap"
|
||||
|
||||
|
||||
class TrapChancePoison(TrapChance):
|
||||
"""Sets the chance/ratio of poison effect traps"""
|
||||
item_name = "Poison Trap"
|
||||
|
||||
|
||||
class TrapChanceConfound(TrapChance):
|
||||
"""Sets the chance/ratio of confound effect traps"""
|
||||
item_name = "Confound Trap"
|
||||
|
||||
|
||||
class TrapChanceHUD(TrapChance):
|
||||
"""Sets the chance/ratio of HUD visibility traps"""
|
||||
item_name = "HUD Trap"
|
||||
|
||||
|
||||
class TrapChanceOHKO(TrapChance):
|
||||
"""Sets the chance/ratio of OHKO (1HP left) traps"""
|
||||
item_name = "OHKO Trap"
|
||||
|
||||
|
||||
class SoEProgressionBalancing(ProgressionBalancing):
|
||||
default = 30
|
||||
__doc__ = ProgressionBalancing.__doc__.replace(f"default {ProgressionBalancing.default}", f"default {default}") \
|
||||
if ProgressionBalancing.__doc__ else None
|
||||
special_range_names = {**ProgressionBalancing.special_range_names, "normal": default}
|
||||
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
@dataclass
|
||||
class SoEOptions(PerGameCommonOptions):
|
||||
difficulty: Difficulty
|
||||
energy_core: EnergyCore
|
||||
required_fragments: RequiredFragments
|
||||
available_fragments: AvailableFragments
|
||||
money_modifier: MoneyModifier
|
||||
exp_modifier: ExpModifier
|
||||
sequence_breaks: SequenceBreaks
|
||||
out_of_bounds: OutOfBounds
|
||||
fix_cheats: FixCheats
|
||||
fix_infinite_ammo: FixInfiniteAmmo
|
||||
fix_atlas_glitch: FixAtlasGlitch
|
||||
fix_wings_glitch: FixWingsGlitch
|
||||
shorter_dialogs: ShorterDialogs
|
||||
short_boss_rush: ShortBossRush
|
||||
ingredienizer: Ingredienizer
|
||||
sniffamizer: Sniffamizer
|
||||
callbeadamizer: Callbeadamizer
|
||||
musicmizer: Musicmizer
|
||||
doggomizer: Doggomizer
|
||||
turdo_mode: TurdoMode
|
||||
death_link: DeathLink
|
||||
trap_count: TrapCount
|
||||
trap_chance_quake: TrapChanceQuake
|
||||
trap_chance_poison: TrapChancePoison
|
||||
trap_chance_confound: TrapChanceConfound
|
||||
trap_chance_hud: TrapChanceHUD
|
||||
trap_chance_ohko: TrapChanceOHKO
|
||||
progression_balancing: SoEProgressionBalancing
|
||||
|
||||
@property
|
||||
def trap_chances(self) -> Iterator[TrapChance]:
|
||||
for field in fields(self):
|
||||
option = getattr(self, field.name)
|
||||
if isinstance(option, TrapChance):
|
||||
yield option
|
||||
|
||||
@property
|
||||
def flags(self) -> str:
|
||||
flags = ''
|
||||
for field in fields(self):
|
||||
option = getattr(self, field.name)
|
||||
if isinstance(option, (EvermizerFlag, EvermizerFlags)):
|
||||
flags += getattr(self, field.name).to_flag()
|
||||
return flags
|
||||
Reference in New Issue
Block a user