320 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			320 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
| from dataclasses import dataclass, fields
 | |
| from datetime import datetime
 | |
| from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol
 | |
| 
 | |
| from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
 | |
|     ProgressionBalancing, Range, Toggle
 | |
| 
 | |
| 
 | |
| # typing boilerplate
 | |
| class FlagsProtocol(Protocol):
 | |
|     value: int
 | |
|     default: ClassVar[int]
 | |
|     flags: List[str]
 | |
| 
 | |
| 
 | |
| class FlagProtocol(Protocol):
 | |
|     value: int
 | |
|     default: ClassVar[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, Choice):
 | |
|     """
 | |
|     Off: all vanilla items in sniff spots
 | |
|     Shuffle: sniff items shuffled into random sniff spots
 | |
|     """
 | |
|     display_name = "Sniffamizer"
 | |
|     option_off = 0
 | |
|     option_shuffle = 1
 | |
|     if datetime.today().year > 2024 or datetime.today().month > 3:
 | |
|         option_everywhere = 2
 | |
|         __doc__ = __doc__ + "    Everywhere: add sniff spots to multiworld pool"
 | |
|     alias_true = 1
 | |
|     default = 1
 | |
|     flags = ['s', '', 'S']
 | |
| 
 | |
| 
 | |
| class SniffIngredients(EvermizerFlag, Choice):
 | |
|     """Select which items should be used as sniff items"""
 | |
|     display_name = "Sniff Ingredients"
 | |
|     option_vanilla_ingredients = 0
 | |
|     option_random_ingredients = 1
 | |
|     flag = 'v'
 | |
| 
 | |
| 
 | |
| 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)   # type: ignore[no-untyped-call]
 | |
|         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
 | |
|     sniff_ingredients:     SniffIngredients
 | |
|     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)):
 | |
|                 assert isinstance(option, Option)
 | |
|                 # noinspection PyUnresolvedReferences
 | |
|                 flags += option.to_flag()
 | |
|         return flags
 |