mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00

also give warnings about deprecated LttP options also fix FactorioClient.py getting stuck if send an unknown item id also fix !missing having an extra newline after each entry also default to no webui
294 lines
7.6 KiB
Python
294 lines
7.6 KiB
Python
from __future__ import annotations
|
|
import typing
|
|
|
|
|
|
class AssembleOptions(type):
|
|
def __new__(cls, name, bases, attrs):
|
|
options = attrs["options"] = {}
|
|
name_lookup = attrs["name_lookup"] = {}
|
|
for base in bases:
|
|
options.update(base.options)
|
|
name_lookup.update(name_lookup)
|
|
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
|
name.startswith("option_")}
|
|
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
|
options.update(new_options)
|
|
|
|
# apply aliases, without name_lookup
|
|
options.update({name[6:].lower(): option_id for name, option_id in attrs.items() if
|
|
name.startswith("alias_")})
|
|
return super(AssembleOptions, cls).__new__(cls, name, bases, attrs)
|
|
|
|
|
|
class Option(metaclass=AssembleOptions):
|
|
value: int
|
|
name_lookup: typing.Dict[int, str]
|
|
default = 0
|
|
|
|
def __repr__(self):
|
|
return f"{self.__class__.__name__}({self.get_option_name()})"
|
|
|
|
def __hash__(self):
|
|
return hash(self.value)
|
|
|
|
def get_option_name(self):
|
|
return self.name_lookup[self.value]
|
|
|
|
def __int__(self):
|
|
return self.value
|
|
|
|
def __bool__(self):
|
|
return bool(self.value)
|
|
|
|
@classmethod
|
|
def from_any(cls, data: typing.Any):
|
|
raise NotImplementedError
|
|
|
|
|
|
class Toggle(Option):
|
|
option_false = 0
|
|
option_true = 1
|
|
default = 0
|
|
|
|
def __init__(self, value: int):
|
|
self.value = value
|
|
|
|
@classmethod
|
|
def from_text(cls, text: str) -> Toggle:
|
|
if text.lower() in {"off", "0", "false", "none", "null", "no"}:
|
|
return cls(0)
|
|
else:
|
|
return cls(1)
|
|
|
|
@classmethod
|
|
def from_any(cls, data: typing.Any):
|
|
if type(data) == str:
|
|
return cls.from_text(data)
|
|
else:
|
|
return cls(data)
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, Toggle):
|
|
return self.value == other.value
|
|
else:
|
|
return self.value == other
|
|
|
|
def __gt__(self, other):
|
|
if isinstance(other, Toggle):
|
|
return self.value > other.value
|
|
else:
|
|
return self.value > other
|
|
|
|
def __bool__(self):
|
|
return bool(self.value)
|
|
|
|
def __int__(self):
|
|
return int(self.value)
|
|
|
|
def get_option_name(self):
|
|
return bool(self.value)
|
|
|
|
|
|
class Choice(Option):
|
|
def __init__(self, value: int):
|
|
self.value: int = value
|
|
|
|
@classmethod
|
|
def from_text(cls, text: str) -> Choice:
|
|
for optionname, value in cls.options.items():
|
|
if optionname == text.lower():
|
|
return cls(value)
|
|
raise KeyError(
|
|
f'Could not find option "{text}" for "{cls.__name__}", '
|
|
f'known options are {", ".join(f"{option}" for option in cls.name_lookup.values())}')
|
|
|
|
@classmethod
|
|
def from_any(cls, data: typing.Any):
|
|
return cls.from_text(data)
|
|
|
|
|
|
class Logic(Choice):
|
|
option_no_glitches = 0
|
|
option_minor_glitches = 1
|
|
option_overworld_glitches = 2
|
|
option_no_logic = 4
|
|
alias_owg = 2
|
|
|
|
|
|
class Objective(Choice):
|
|
option_crystals = 0
|
|
# option_pendants = 1
|
|
option_triforce_pieces = 2
|
|
option_pedestal = 3
|
|
option_bingo = 4
|
|
|
|
|
|
local_objective = Toggle # local triforce pieces, local dungeon prizes etc.
|
|
|
|
|
|
class Goal(Choice):
|
|
option_kill_ganon = 0
|
|
option_kill_ganon_and_gt_agahnim = 1
|
|
option_hand_in = 2
|
|
|
|
|
|
class Accessibility(Choice):
|
|
option_locations = 0
|
|
option_items = 1
|
|
option_beatable = 2
|
|
|
|
|
|
class Crystals(Choice):
|
|
# can't use IntEnum since there's also random
|
|
option_0 = 0
|
|
option_1 = 1
|
|
option_2 = 2
|
|
option_3 = 3
|
|
option_4 = 4
|
|
option_5 = 5
|
|
option_6 = 6
|
|
option_7 = 7
|
|
option_random = -1
|
|
|
|
|
|
class WorldState(Choice):
|
|
option_standard = 1
|
|
option_open = 0
|
|
option_inverted = 2
|
|
|
|
|
|
class Bosses(Choice):
|
|
option_vanilla = 0
|
|
option_simple = 1
|
|
option_full = 2
|
|
option_chaos = 3
|
|
option_singularity = 4
|
|
|
|
|
|
class Enemies(Choice):
|
|
option_vanilla = 0
|
|
option_shuffled = 1
|
|
option_chaos = 2
|
|
|
|
|
|
mapshuffle = Toggle
|
|
compassshuffle = Toggle
|
|
keyshuffle = Toggle
|
|
bigkeyshuffle = Toggle
|
|
hints = Toggle
|
|
|
|
RandomizeDreamers = Toggle
|
|
RandomizeSkills = Toggle
|
|
RandomizeCharms = Toggle
|
|
RandomizeKeys = Toggle
|
|
RandomizeGeoChests = Toggle
|
|
RandomizeMaskShards = Toggle
|
|
RandomizeVesselFragments = Toggle
|
|
RandomizeCharmNotches = Toggle
|
|
RandomizePaleOre = Toggle
|
|
RandomizeRancidEggs = Toggle
|
|
RandomizeRelics = Toggle
|
|
RandomizeMaps = Toggle
|
|
RandomizeStags = Toggle
|
|
RandomizeGrubs = Toggle
|
|
RandomizeWhisperingRoots = Toggle
|
|
RandomizeRocks = Toggle
|
|
RandomizeSoulTotems = Toggle
|
|
RandomizePalaceTotems = Toggle
|
|
RandomizeLoreTablets = Toggle
|
|
RandomizeLifebloodCocoons = Toggle
|
|
RandomizeFlames = Toggle
|
|
|
|
hollow_knight_randomize_options: typing.Dict[str, Option] = {
|
|
"RandomizeDreamers": RandomizeDreamers,
|
|
"RandomizeSkills": RandomizeSkills,
|
|
"RandomizeCharms": RandomizeCharms,
|
|
"RandomizeKeys": RandomizeKeys,
|
|
"RandomizeGeoChests": RandomizeGeoChests,
|
|
"RandomizeMaskShards": RandomizeMaskShards,
|
|
"RandomizeVesselFragments": RandomizeVesselFragments,
|
|
"RandomizeCharmNotches": RandomizeCharmNotches,
|
|
"RandomizePaleOre": RandomizePaleOre,
|
|
"RandomizeRancidEggs": RandomizeRancidEggs,
|
|
"RandomizeRelics": RandomizeRelics,
|
|
"RandomizeMaps": RandomizeMaps,
|
|
"RandomizeStags": RandomizeStags,
|
|
"RandomizeGrubs": RandomizeGrubs,
|
|
"RandomizeWhisperingRoots": RandomizeWhisperingRoots,
|
|
"RandomizeRocks": RandomizeRocks,
|
|
"RandomizeSoulTotems": RandomizeSoulTotems,
|
|
"RandomizePalaceTotems": RandomizePalaceTotems,
|
|
"RandomizeLoreTablets": RandomizeLoreTablets,
|
|
"RandomizeLifebloodCocoons": RandomizeLifebloodCocoons,
|
|
"RandomizeFlames": RandomizeFlames
|
|
}
|
|
|
|
hollow_knight_skip_options: typing.Dict[str, type(Option)] = {
|
|
"MILDSKIPS": Toggle,
|
|
"SPICYSKIPS": Toggle,
|
|
"FIREBALLSKIPS": Toggle,
|
|
"ACIDSKIPS": Toggle,
|
|
"SPIKETUNNELS": Toggle,
|
|
"DARKROOMS": Toggle,
|
|
"CURSED": Toggle,
|
|
"SHADESKIPS": Toggle,
|
|
}
|
|
|
|
hollow_knight_options: typing.Dict[str, Option] = {**hollow_knight_randomize_options, **hollow_knight_skip_options}
|
|
|
|
|
|
class MaxSciencePack(Choice):
|
|
option_automation_science_pack = 0
|
|
option_logistic_science_pack = 1
|
|
option_military_science_pack = 2
|
|
option_chemical_science_pack = 3
|
|
option_production_science_pack = 4
|
|
option_utility_science_pack = 5
|
|
option_space_science_pack = 6
|
|
default = 6
|
|
|
|
def get_allowed_packs(self):
|
|
return {option.replace("_", "-") for option, value in self.options.items()
|
|
if value <= self.value}
|
|
|
|
|
|
class TechCost(Choice):
|
|
option_very_easy = 0
|
|
option_easy = 1
|
|
option_kind = 2
|
|
option_normal = 3
|
|
option_hard = 4
|
|
option_very_hard = 5
|
|
option_insane = 6
|
|
default = 3
|
|
|
|
|
|
class TechTreeLayout(Choice):
|
|
option_single = 0
|
|
default = 0
|
|
|
|
|
|
factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxSciencePack,
|
|
"tech_tree_layout": TechTreeLayout,
|
|
"tech_cost": TechCost}
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
|
|
test = argparse.Namespace()
|
|
test.logic = Logic.from_text("no_logic")
|
|
test.mapshuffle = mapshuffle.from_text("ON")
|
|
test.hints = hints.from_text('OFF')
|
|
try:
|
|
test.logic = Logic.from_text("overworld_glitches_typo")
|
|
except KeyError as e:
|
|
print(e)
|
|
try:
|
|
test.logic_owg = Logic.from_text("owg")
|
|
except KeyError as e:
|
|
print(e)
|
|
if test.mapshuffle:
|
|
print("Mapshuffle is on")
|
|
print(f"Hints are {bool(test.hints)}")
|
|
print(test)
|