2020-03-18 15:15:32 +00:00
|
|
|
from __future__ import annotations
|
2022-04-07 17:42:30 +00:00
|
|
|
import abc
|
2023-03-07 07:44:20 +00:00
|
|
|
import logging
|
2022-10-23 16:28:09 +00:00
|
|
|
from copy import deepcopy
|
2022-04-07 17:42:30 +00:00
|
|
|
import math
|
|
|
|
import numbers
|
2020-10-24 04:43:35 +00:00
|
|
|
import typing
|
2021-06-08 12:15:23 +00:00
|
|
|
import random
|
2020-03-18 15:15:32 +00:00
|
|
|
|
2022-05-11 23:37:18 +00:00
|
|
|
from schema import Schema, And, Or, Optional
|
2022-05-09 05:18:50 +00:00
|
|
|
from Utils import get_fuzzy_results
|
2022-02-05 14:49:19 +00:00
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
from BaseClasses import PlandoOptions
|
|
|
|
from worlds.AutoWorld import World
|
2023-04-15 23:57:52 +00:00
|
|
|
import pathlib
|
2023-03-07 07:44:20 +00:00
|
|
|
|
2022-05-21 18:57:26 +00:00
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
class AssembleOptions(abc.ABCMeta):
|
2021-04-14 15:51:11 +00:00
|
|
|
def __new__(mcs, name, bases, attrs):
|
2020-10-24 04:43:35 +00:00
|
|
|
options = attrs["options"] = {}
|
|
|
|
name_lookup = attrs["name_lookup"] = {}
|
2021-08-03 17:03:41 +00:00
|
|
|
# merge parent class options
|
2020-10-24 04:43:35 +00:00
|
|
|
for base in bases:
|
2021-08-30 14:31:56 +00:00
|
|
|
if getattr(base, "options", None):
|
2021-06-08 19:58:11 +00:00
|
|
|
options.update(base.options)
|
2021-08-09 07:15:41 +00:00
|
|
|
name_lookup.update(base.name_lookup)
|
2020-10-24 04:43:35 +00:00
|
|
|
new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if
|
2021-03-20 23:47:17 +00:00
|
|
|
name.startswith("option_")}
|
2022-03-23 01:28:15 +00:00
|
|
|
|
2022-03-26 00:12:54 +00:00
|
|
|
assert "random" not in new_options, "Choice option 'random' cannot be manually assigned."
|
|
|
|
assert len(new_options) == len(set(new_options.values())), "same ID cannot be used twice. Try alias?"
|
2022-03-23 01:28:15 +00:00
|
|
|
|
2020-10-24 04:43:35 +00:00
|
|
|
attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()})
|
|
|
|
options.update(new_options)
|
2021-03-20 23:47:17 +00:00
|
|
|
# apply aliases, without name_lookup
|
2022-06-10 20:24:44 +00:00
|
|
|
aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
|
|
|
|
name.startswith("alias_")}
|
|
|
|
|
|
|
|
assert "random" not in aliases, "Choice option 'random' cannot be manually assigned."
|
|
|
|
|
2022-09-15 22:32:30 +00:00
|
|
|
# auto-alias Off and On being parsed as True and False
|
|
|
|
if "off" in options:
|
|
|
|
options["false"] = options["off"]
|
|
|
|
if "on" in options:
|
|
|
|
options["true"] = options["on"]
|
|
|
|
|
2022-06-10 20:24:44 +00:00
|
|
|
options.update(aliases)
|
2021-07-18 23:02:23 +00:00
|
|
|
|
2022-09-17 00:55:33 +00:00
|
|
|
if "verify" not in attrs:
|
|
|
|
# not overridden by class -> look up bases
|
|
|
|
verifiers = [f for f in (getattr(base, "verify", None) for base in bases) if f]
|
|
|
|
if len(verifiers) > 1: # verify multiple bases/mixins
|
|
|
|
def verify(self, *args, **kwargs) -> None:
|
|
|
|
for f in verifiers:
|
|
|
|
f(self, *args, **kwargs)
|
|
|
|
attrs["verify"] = verify
|
|
|
|
else:
|
|
|
|
assert verifiers, "class Option is supposed to implement def verify"
|
|
|
|
|
2021-07-18 23:02:23 +00:00
|
|
|
# auto-validate schema on __init__
|
|
|
|
if "schema" in attrs.keys():
|
2022-02-05 14:49:19 +00:00
|
|
|
|
|
|
|
if "__init__" in attrs:
|
|
|
|
def validate_decorator(func):
|
|
|
|
def validate(self, *args, **kwargs):
|
|
|
|
ret = func(self, *args, **kwargs)
|
|
|
|
self.value = self.schema.validate(self.value)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
return validate
|
2022-04-01 01:42:56 +00:00
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
attrs["__init__"] = validate_decorator(attrs["__init__"])
|
|
|
|
else:
|
|
|
|
# construct an __init__ that calls parent __init__
|
|
|
|
|
|
|
|
cls = super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs)
|
|
|
|
|
|
|
|
def meta__init__(self, *args, **kwargs):
|
|
|
|
super(cls, self).__init__(*args, **kwargs)
|
2021-07-18 23:02:23 +00:00
|
|
|
self.value = self.schema.validate(self.value)
|
2021-09-30 17:49:36 +00:00
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
cls.__init__ = meta__init__
|
|
|
|
return cls
|
2021-09-30 17:49:36 +00:00
|
|
|
|
2021-04-14 15:51:11 +00:00
|
|
|
return super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs)
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2022-04-01 01:42:56 +00:00
|
|
|
|
2022-03-27 23:47:47 +00:00
|
|
|
T = typing.TypeVar('T')
|
2021-08-03 17:03:41 +00:00
|
|
|
|
2022-03-27 23:47:47 +00:00
|
|
|
|
|
|
|
class Option(typing.Generic[T], metaclass=AssembleOptions):
|
|
|
|
value: T
|
2021-04-03 12:47:49 +00:00
|
|
|
default = 0
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2022-02-02 15:29:29 +00:00
|
|
|
# convert option_name_long into Name Long as display_name, otherwise name_long is the result.
|
2021-08-03 17:03:41 +00:00
|
|
|
# Handled in get_option_name()
|
2022-02-02 15:29:29 +00:00
|
|
|
auto_display_name = False
|
2021-08-03 17:03:41 +00:00
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
# can be weighted between selections
|
|
|
|
supports_weighting = True
|
|
|
|
|
2022-03-31 22:47:50 +00:00
|
|
|
# filled by AssembleOptions:
|
2023-03-07 07:44:20 +00:00
|
|
|
name_lookup: typing.Dict[T, str]
|
2022-03-31 22:47:50 +00:00
|
|
|
options: typing.Dict[str, int]
|
|
|
|
|
2021-08-03 17:03:41 +00:00
|
|
|
def __repr__(self) -> str:
|
2023-03-07 07:44:20 +00:00
|
|
|
return f"{self.__class__.__name__}({self.current_option_name})"
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
def __hash__(self) -> int:
|
2020-10-24 04:43:35 +00:00
|
|
|
return hash(self.value)
|
|
|
|
|
2021-08-09 07:15:41 +00:00
|
|
|
@property
|
|
|
|
def current_key(self) -> str:
|
|
|
|
return self.name_lookup[self.value]
|
|
|
|
|
2021-08-03 17:09:37 +00:00
|
|
|
def get_current_option_name(self) -> str:
|
2023-03-07 07:44:20 +00:00
|
|
|
"""Deprecated. use current_option_name instead. TODO remove around 0.4"""
|
|
|
|
logging.warning(DeprecationWarning(f"get_current_option_name for {self.__class__.__name__} is deprecated."
|
|
|
|
f" use current_option_name instead. Worlds should use {self}.current_key"))
|
|
|
|
return self.current_option_name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_option_name(self) -> str:
|
|
|
|
"""For display purposes. Worlds should be using current_key."""
|
2021-08-03 17:09:37 +00:00
|
|
|
return self.get_option_name(self.value)
|
|
|
|
|
2021-08-31 20:14:18 +00:00
|
|
|
@classmethod
|
2022-03-31 22:47:50 +00:00
|
|
|
def get_option_name(cls, value: T) -> str:
|
2022-02-02 15:29:29 +00:00
|
|
|
if cls.auto_display_name:
|
2021-08-31 20:14:18 +00:00
|
|
|
return cls.name_lookup[value].replace("_", " ").title()
|
2021-08-03 17:03:41 +00:00
|
|
|
else:
|
2021-08-31 20:14:18 +00:00
|
|
|
return cls.name_lookup[value]
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2022-03-31 22:47:50 +00:00
|
|
|
def __int__(self) -> T:
|
2020-10-24 04:43:35 +00:00
|
|
|
return self.value
|
|
|
|
|
2021-08-03 17:03:41 +00:00
|
|
|
def __bool__(self) -> bool:
|
2020-10-24 04:43:35 +00:00
|
|
|
return bool(self.value)
|
|
|
|
|
2021-03-14 07:38:02 +00:00
|
|
|
@classmethod
|
2023-01-31 20:26:09 +00:00
|
|
|
@abc.abstractmethod
|
2022-04-07 17:42:30 +00:00
|
|
|
def from_any(cls, data: typing.Any) -> Option[T]:
|
2023-01-31 20:26:09 +00:00
|
|
|
...
|
2021-03-14 07:38:02 +00:00
|
|
|
|
2022-09-17 00:55:33 +00:00
|
|
|
if typing.TYPE_CHECKING:
|
2023-03-07 07:44:20 +00:00
|
|
|
def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None:
|
2022-09-17 00:55:33 +00:00
|
|
|
pass
|
|
|
|
else:
|
|
|
|
def verify(self, *args, **kwargs) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
class FreeText(Option[str]):
|
2022-09-17 00:55:33 +00:00
|
|
|
"""Text option that allows users to enter strings.
|
|
|
|
Needs to be validated by the world or option definition."""
|
|
|
|
|
|
|
|
def __init__(self, value: str):
|
|
|
|
assert isinstance(value, str), "value of FreeText must be a string"
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_key(self) -> str:
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> FreeText:
|
|
|
|
return cls(text)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Any) -> FreeText:
|
|
|
|
return cls.from_text(str(data))
|
|
|
|
|
|
|
|
@classmethod
|
2023-03-07 07:44:20 +00:00
|
|
|
def get_option_name(cls, value: str) -> str:
|
2022-09-17 00:55:33 +00:00
|
|
|
return value
|
|
|
|
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2023-01-31 20:26:09 +00:00
|
|
|
class NumericOption(Option[int], numbers.Integral, abc.ABC):
|
2022-09-28 21:54:10 +00:00
|
|
|
default = 0
|
2022-04-07 17:42:30 +00:00
|
|
|
# note: some of the `typing.Any`` here is a result of unresolved issue in python standards
|
|
|
|
# `int` is not a `numbers.Integral` according to the official typestubs
|
|
|
|
# (even though isinstance(5, numbers.Integral) == True)
|
|
|
|
# https://github.com/python/typing/issues/272
|
|
|
|
# https://github.com/python/mypy/issues/3186
|
|
|
|
# https://github.com/microsoft/pyright/issues/1575
|
|
|
|
|
|
|
|
def __eq__(self, other: typing.Any) -> bool:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value == other.value
|
|
|
|
else:
|
|
|
|
return typing.cast(bool, self.value == other)
|
|
|
|
|
|
|
|
def __lt__(self, other: typing.Union[int, NumericOption]) -> bool:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value < other.value
|
|
|
|
else:
|
|
|
|
return self.value < other
|
|
|
|
|
|
|
|
def __le__(self, other: typing.Union[int, NumericOption]) -> bool:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value <= other.value
|
|
|
|
else:
|
|
|
|
return self.value <= other
|
|
|
|
|
|
|
|
def __gt__(self, other: typing.Union[int, NumericOption]) -> bool:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value > other.value
|
|
|
|
else:
|
|
|
|
return self.value > other
|
|
|
|
|
|
|
|
def __bool__(self) -> bool:
|
|
|
|
return bool(self.value)
|
|
|
|
|
|
|
|
def __int__(self) -> int:
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
def __mul__(self, other: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value * other.value
|
|
|
|
else:
|
|
|
|
return self.value * other
|
|
|
|
|
|
|
|
def __rmul__(self, other: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return other.value * self.value
|
|
|
|
else:
|
|
|
|
return other * self.value
|
|
|
|
|
|
|
|
def __sub__(self, other: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value - other.value
|
|
|
|
else:
|
|
|
|
return self.value - other
|
|
|
|
|
|
|
|
def __rsub__(self, left: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(left, NumericOption):
|
|
|
|
return left.value - self.value
|
|
|
|
else:
|
|
|
|
return left - self.value
|
|
|
|
|
|
|
|
def __add__(self, other: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value + other.value
|
|
|
|
else:
|
|
|
|
return self.value + other
|
|
|
|
|
|
|
|
def __radd__(self, left: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(left, NumericOption):
|
|
|
|
return left.value + self.value
|
|
|
|
else:
|
|
|
|
return left + self.value
|
|
|
|
|
|
|
|
def __truediv__(self, other: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(other, NumericOption):
|
|
|
|
return self.value / other.value
|
|
|
|
else:
|
|
|
|
return self.value / other
|
|
|
|
|
|
|
|
def __rtruediv__(self, left: typing.Any) -> typing.Any:
|
|
|
|
if isinstance(left, NumericOption):
|
|
|
|
return left.value / self.value
|
|
|
|
else:
|
|
|
|
return left / self.value
|
|
|
|
|
|
|
|
def __abs__(self) -> typing.Any:
|
|
|
|
return abs(self.value)
|
|
|
|
|
|
|
|
def __and__(self, other: typing.Any) -> int:
|
|
|
|
return self.value & int(other)
|
|
|
|
|
|
|
|
def __ceil__(self) -> int:
|
|
|
|
return math.ceil(self.value)
|
|
|
|
|
|
|
|
def __floor__(self) -> int:
|
|
|
|
return math.floor(self.value)
|
|
|
|
|
|
|
|
def __floordiv__(self, other: typing.Any) -> int:
|
|
|
|
return self.value // int(other)
|
|
|
|
|
|
|
|
def __invert__(self) -> int:
|
|
|
|
return ~(self.value)
|
|
|
|
|
|
|
|
def __lshift__(self, other: typing.Any) -> int:
|
|
|
|
return self.value << int(other)
|
|
|
|
|
|
|
|
def __mod__(self, other: typing.Any) -> int:
|
|
|
|
return self.value % int(other)
|
|
|
|
|
|
|
|
def __neg__(self) -> int:
|
|
|
|
return -(self.value)
|
|
|
|
|
|
|
|
def __or__(self, other: typing.Any) -> int:
|
|
|
|
return self.value | int(other)
|
|
|
|
|
|
|
|
def __pos__(self) -> int:
|
|
|
|
return +(self.value)
|
|
|
|
|
|
|
|
def __pow__(self, exponent: numbers.Complex, modulus: typing.Optional[numbers.Integral] = None) -> int:
|
|
|
|
if not (modulus is None):
|
|
|
|
assert isinstance(exponent, numbers.Integral)
|
|
|
|
return pow(self.value, exponent, modulus) # type: ignore
|
|
|
|
return self.value ** exponent # type: ignore
|
|
|
|
|
|
|
|
def __rand__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) & self.value
|
|
|
|
|
|
|
|
def __rfloordiv__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) // self.value
|
|
|
|
|
|
|
|
def __rlshift__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) << self.value
|
|
|
|
|
|
|
|
def __rmod__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) % self.value
|
|
|
|
|
|
|
|
def __ror__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) | self.value
|
|
|
|
|
|
|
|
def __round__(self, ndigits: typing.Optional[int] = None) -> int:
|
|
|
|
return round(self.value, ndigits)
|
|
|
|
|
|
|
|
def __rpow__(self, base: typing.Any) -> typing.Any:
|
|
|
|
return base ** self.value
|
|
|
|
|
|
|
|
def __rrshift__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) >> self.value
|
|
|
|
|
|
|
|
def __rshift__(self, other: typing.Any) -> int:
|
|
|
|
return self.value >> int(other)
|
|
|
|
|
|
|
|
def __rxor__(self, other: typing.Any) -> int:
|
|
|
|
return int(other) ^ self.value
|
|
|
|
|
|
|
|
def __trunc__(self) -> int:
|
|
|
|
return math.trunc(self.value)
|
|
|
|
|
|
|
|
def __xor__(self, other: typing.Any) -> int:
|
|
|
|
return self.value ^ int(other)
|
|
|
|
|
|
|
|
|
|
|
|
class Toggle(NumericOption):
|
2020-10-24 04:43:35 +00:00
|
|
|
option_false = 0
|
|
|
|
option_true = 1
|
2021-04-03 12:47:49 +00:00
|
|
|
default = 0
|
2020-10-24 04:43:35 +00:00
|
|
|
|
|
|
|
def __init__(self, value: int):
|
2022-03-26 00:12:54 +00:00
|
|
|
assert value == 0 or value == 1, "value of Toggle can only be 0 or 1"
|
2020-10-24 04:43:35 +00:00
|
|
|
self.value = value
|
2020-03-18 15:15:32 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> Toggle:
|
2022-03-31 20:09:54 +00:00
|
|
|
if text == "random":
|
|
|
|
return cls(random.choice(list(cls.name_lookup)))
|
|
|
|
elif text.lower() in {"off", "0", "false", "none", "null", "no"}:
|
2020-10-24 04:43:35 +00:00
|
|
|
return cls(0)
|
|
|
|
else:
|
|
|
|
return cls(1)
|
|
|
|
|
2021-03-14 07:38:02 +00:00
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Any):
|
|
|
|
if type(data) == str:
|
|
|
|
return cls.from_text(data)
|
|
|
|
else:
|
2022-08-27 07:21:47 +00:00
|
|
|
return cls(int(data))
|
2021-03-14 07:38:02 +00:00
|
|
|
|
2021-08-31 20:52:14 +00:00
|
|
|
@classmethod
|
|
|
|
def get_option_name(cls, value):
|
2021-08-03 17:09:37 +00:00
|
|
|
return ["No", "Yes"][int(value)]
|
2020-10-24 04:43:35 +00:00
|
|
|
|
2022-01-06 05:18:54 +00:00
|
|
|
__hash__ = Option.__hash__ # see https://docs.python.org/3/reference/datamodel.html#object.__hash__
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
|
2021-06-18 23:00:21 +00:00
|
|
|
class DefaultOnToggle(Toggle):
|
|
|
|
default = 1
|
2021-04-03 12:47:49 +00:00
|
|
|
|
2021-08-03 17:03:41 +00:00
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
class Choice(NumericOption):
|
2022-02-02 15:29:29 +00:00
|
|
|
auto_display_name = True
|
2021-08-03 17:03:41 +00:00
|
|
|
|
2020-10-24 04:43:35 +00:00
|
|
|
def __init__(self, value: int):
|
|
|
|
self.value: int = value
|
2020-03-18 15:15:32 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> Choice:
|
2021-08-09 07:15:41 +00:00
|
|
|
text = text.lower()
|
2021-09-30 11:22:25 +00:00
|
|
|
if text == "random":
|
|
|
|
return cls(random.choice(list(cls.name_lookup)))
|
2022-02-05 14:49:19 +00:00
|
|
|
for option_name, value in cls.options.items():
|
|
|
|
if option_name == text:
|
2020-10-24 04:43:35 +00:00
|
|
|
return cls(value)
|
2020-03-18 15:15:32 +00:00
|
|
|
raise KeyError(
|
2020-10-24 04:43:35 +00:00
|
|
|
f'Could not find option "{text}" for "{cls.__name__}", '
|
|
|
|
f'known options are {", ".join(f"{option}" for option in cls.name_lookup.values())}')
|
2020-03-18 15:15:32 +00:00
|
|
|
|
2021-03-14 07:38:02 +00:00
|
|
|
@classmethod
|
2021-05-09 15:46:26 +00:00
|
|
|
def from_any(cls, data: typing.Any) -> Choice:
|
2021-04-08 17:53:24 +00:00
|
|
|
if type(data) == int and data in cls.options.values():
|
|
|
|
return cls(data)
|
|
|
|
return cls.from_text(str(data))
|
2021-03-14 07:38:02 +00:00
|
|
|
|
2021-08-30 14:31:56 +00:00
|
|
|
def __eq__(self, other):
|
2021-09-04 15:53:09 +00:00
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return other.value == self.value
|
|
|
|
elif isinstance(other, str):
|
2022-03-31 01:29:45 +00:00
|
|
|
assert other in self.options, f"compared against a str that could never be equal. {self} == {other}"
|
2021-08-30 14:31:56 +00:00
|
|
|
return other == self.current_key
|
2021-08-30 21:07:19 +00:00
|
|
|
elif isinstance(other, int):
|
2022-03-31 01:29:45 +00:00
|
|
|
assert other in self.name_lookup, f"compared against an int that could never be equal. {self} == {other}"
|
2021-08-30 14:31:56 +00:00
|
|
|
return other == self.value
|
2021-09-16 22:17:54 +00:00
|
|
|
elif isinstance(other, bool):
|
2021-08-30 14:31:56 +00:00
|
|
|
return other == bool(self.value)
|
|
|
|
else:
|
|
|
|
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
|
|
|
|
2021-08-30 21:07:19 +00:00
|
|
|
def __ne__(self, other):
|
2021-09-04 15:53:09 +00:00
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return other.value != self.value
|
|
|
|
elif isinstance(other, str):
|
2022-04-01 01:42:56 +00:00
|
|
|
assert other in self.options, f"compared against a str that could never be equal. {self} != {other}"
|
2021-08-30 21:07:19 +00:00
|
|
|
return other != self.current_key
|
|
|
|
elif isinstance(other, int):
|
2022-03-31 01:29:45 +00:00
|
|
|
assert other in self.name_lookup, f"compared against am int that could never be equal. {self} != {other}"
|
2021-08-30 21:07:19 +00:00
|
|
|
return other != self.value
|
|
|
|
elif isinstance(other, bool):
|
|
|
|
return other != bool(self.value)
|
2021-10-14 17:42:13 +00:00
|
|
|
elif other is None:
|
|
|
|
return False
|
2021-08-30 21:07:19 +00:00
|
|
|
else:
|
|
|
|
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
2021-06-08 13:39:34 +00:00
|
|
|
|
2022-01-06 16:03:47 +00:00
|
|
|
__hash__ = Option.__hash__ # see https://docs.python.org/3/reference/datamodel.html#object.__hash__
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
|
2022-09-17 00:55:33 +00:00
|
|
|
class TextChoice(Choice):
|
|
|
|
"""Allows custom string input and offers choices. Choices will resolve to int and text will resolve to string"""
|
2023-03-07 07:44:20 +00:00
|
|
|
value: typing.Union[str, int]
|
2022-09-17 00:55:33 +00:00
|
|
|
|
|
|
|
def __init__(self, value: typing.Union[str, int]):
|
|
|
|
assert isinstance(value, str) or isinstance(value, int), \
|
|
|
|
f"{value} is not a valid option for {self.__class__.__name__}"
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def current_key(self) -> str:
|
|
|
|
if isinstance(self.value, str):
|
|
|
|
return self.value
|
2023-03-07 07:44:20 +00:00
|
|
|
return super().current_key
|
2022-09-17 00:55:33 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> TextChoice:
|
|
|
|
if text.lower() == "random": # chooses a random defined option but won't use any free text options
|
|
|
|
return cls(random.choice(list(cls.name_lookup)))
|
|
|
|
for option_name, value in cls.options.items():
|
|
|
|
if option_name.lower() == text.lower():
|
|
|
|
return cls(value)
|
|
|
|
return cls(text)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_option_name(cls, value: T) -> str:
|
|
|
|
if isinstance(value, str):
|
|
|
|
return value
|
2023-03-07 07:44:20 +00:00
|
|
|
return super().get_option_name(value)
|
2022-09-17 00:55:33 +00:00
|
|
|
|
|
|
|
def __eq__(self, other: typing.Any):
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
return other.value == self.value
|
|
|
|
elif isinstance(other, str):
|
|
|
|
if other in self.options:
|
|
|
|
return other == self.current_key
|
|
|
|
return other == self.value
|
|
|
|
elif isinstance(other, int):
|
|
|
|
assert other in self.name_lookup, f"compared against an int that could never be equal. {self} == {other}"
|
|
|
|
return other == self.value
|
|
|
|
elif isinstance(other, bool):
|
|
|
|
return other == bool(self.value)
|
|
|
|
else:
|
|
|
|
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
|
|
|
|
|
|
|
|
2022-10-12 18:28:32 +00:00
|
|
|
class BossMeta(AssembleOptions):
|
|
|
|
def __new__(mcs, name, bases, attrs):
|
|
|
|
if name != "PlandoBosses":
|
|
|
|
assert "bosses" in attrs, f"Please define valid bosses for {name}"
|
|
|
|
attrs["bosses"] = frozenset((boss.lower() for boss in attrs["bosses"]))
|
|
|
|
assert "locations" in attrs, f"Please define valid locations for {name}"
|
|
|
|
attrs["locations"] = frozenset((location.lower() for location in attrs["locations"]))
|
|
|
|
cls = super().__new__(mcs, name, bases, attrs)
|
|
|
|
assert not cls.duplicate_bosses or "singularity" in cls.options, f"Please define option_singularity for {name}"
|
|
|
|
return cls
|
|
|
|
|
|
|
|
|
|
|
|
class PlandoBosses(TextChoice, metaclass=BossMeta):
|
|
|
|
"""Generic boss shuffle option that supports plando. Format expected is
|
|
|
|
'location1-boss1;location2-boss2;shuffle_mode'.
|
|
|
|
If shuffle_mode is not provided in the string, this will be the default shuffle mode. Must override can_place_boss,
|
|
|
|
which passes a plando boss and location. Check if the placement is valid for your game here."""
|
|
|
|
bosses: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
|
|
|
|
locations: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
|
|
|
|
|
|
|
|
duplicate_bosses: bool = False
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str):
|
|
|
|
# set all of our text to lower case for name checking
|
|
|
|
text = text.lower()
|
|
|
|
if text == "random":
|
|
|
|
return cls(random.choice(list(cls.options.values())))
|
|
|
|
for option_name, value in cls.options.items():
|
|
|
|
if option_name == text:
|
|
|
|
return cls(value)
|
|
|
|
options = text.split(";")
|
|
|
|
|
|
|
|
# since plando exists in the option verify the plando values given are valid
|
|
|
|
cls.validate_plando_bosses(options)
|
|
|
|
return cls.get_shuffle_mode(options)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_shuffle_mode(cls, option_list: typing.List[str]):
|
|
|
|
# find out what mode of boss shuffle we should use for placing bosses after plando
|
|
|
|
# and add as a string to look nice in the spoiler
|
|
|
|
if "random" in option_list:
|
|
|
|
shuffle = random.choice(list(cls.options))
|
|
|
|
option_list.remove("random")
|
|
|
|
options = ";".join(option_list) + f";{shuffle}"
|
|
|
|
boss_class = cls(options)
|
|
|
|
else:
|
|
|
|
for option in option_list:
|
|
|
|
if option in cls.options:
|
|
|
|
options = ";".join(option_list)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if cls.duplicate_bosses and len(option_list) == 1:
|
|
|
|
if cls.valid_boss_name(option_list[0]):
|
|
|
|
# this doesn't exist in this class but it's a forced option for classes where this is called
|
|
|
|
options = option_list[0] + ";singularity"
|
|
|
|
else:
|
|
|
|
options = option_list[0] + f";{cls.name_lookup[cls.default]}"
|
|
|
|
else:
|
|
|
|
options = ";".join(option_list) + f";{cls.name_lookup[cls.default]}"
|
|
|
|
boss_class = cls(options)
|
|
|
|
return boss_class
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def validate_plando_bosses(cls, options: typing.List[str]) -> None:
|
|
|
|
used_locations = []
|
|
|
|
used_bosses = []
|
|
|
|
for option in options:
|
|
|
|
# check if a shuffle mode was provided in the incorrect location
|
|
|
|
if option == "random" or option in cls.options:
|
|
|
|
if option != options[-1]:
|
|
|
|
raise ValueError(f"{option} option must be at the end of the boss_shuffle options!")
|
|
|
|
elif "-" in option:
|
|
|
|
location, boss = option.split("-")
|
|
|
|
if location in used_locations:
|
|
|
|
raise ValueError(f"Duplicate Boss Location {location} not allowed.")
|
|
|
|
if not cls.duplicate_bosses and boss in used_bosses:
|
|
|
|
raise ValueError(f"Duplicate Boss {boss} not allowed.")
|
|
|
|
used_locations.append(location)
|
|
|
|
used_bosses.append(boss)
|
|
|
|
if not cls.valid_boss_name(boss):
|
|
|
|
raise ValueError(f"{boss.title()} is not a valid boss name.")
|
|
|
|
if not cls.valid_location_name(location):
|
|
|
|
raise ValueError(f"{location.title()} is not a valid boss location name.")
|
|
|
|
if not cls.can_place_boss(boss, location):
|
|
|
|
raise ValueError(f"{location.title()} is not a valid location for {boss.title()} to be placed.")
|
|
|
|
else:
|
|
|
|
if cls.duplicate_bosses:
|
|
|
|
if not cls.valid_boss_name(option):
|
|
|
|
raise ValueError(f"{option} is not a valid boss name.")
|
|
|
|
else:
|
|
|
|
raise ValueError(f"{option.title()} is not formatted correctly.")
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def can_place_boss(cls, boss: str, location: str) -> bool:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def valid_boss_name(cls, value: str) -> bool:
|
|
|
|
return value in cls.bosses
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def valid_location_name(cls, value: str) -> bool:
|
|
|
|
return value in cls.locations
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
2022-10-12 18:28:32 +00:00
|
|
|
if isinstance(self.value, int):
|
|
|
|
return
|
2023-03-07 07:44:20 +00:00
|
|
|
from BaseClasses import PlandoOptions
|
2023-01-17 16:25:59 +00:00
|
|
|
if not(PlandoOptions.bosses & plando_options):
|
2022-10-12 18:28:32 +00:00
|
|
|
# plando is disabled but plando options were given so pull the option and change it to an int
|
|
|
|
option = self.value.split(";")[-1]
|
|
|
|
self.value = self.options[option]
|
|
|
|
logging.warning(f"The plando bosses module is turned off, so {self.name_lookup[self.value].title()} "
|
|
|
|
f"boss shuffle will be used for player {player_name}.")
|
|
|
|
|
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
class Range(NumericOption):
|
2021-06-08 12:15:23 +00:00
|
|
|
range_start = 0
|
|
|
|
range_end = 1
|
2021-06-08 13:39:34 +00:00
|
|
|
|
|
|
|
def __init__(self, value: int):
|
|
|
|
if value < self.range_start:
|
|
|
|
raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}")
|
|
|
|
elif value > self.range_end:
|
|
|
|
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}")
|
2021-06-08 19:58:11 +00:00
|
|
|
self.value = value
|
2021-06-08 12:15:23 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> Range:
|
2021-06-08 12:48:00 +00:00
|
|
|
text = text.lower()
|
|
|
|
if text.startswith("random"):
|
2022-06-12 21:33:14 +00:00
|
|
|
return cls.weighted_range(text)
|
2022-04-07 17:42:30 +00:00
|
|
|
elif text == "default" and hasattr(cls, "default"):
|
2022-09-19 20:40:15 +00:00
|
|
|
return cls.from_any(cls.default)
|
2022-05-14 23:50:36 +00:00
|
|
|
elif text == "high":
|
2022-04-07 17:42:30 +00:00
|
|
|
return cls(cls.range_end)
|
2022-05-14 23:50:36 +00:00
|
|
|
elif text == "low":
|
2022-04-07 17:42:30 +00:00
|
|
|
return cls(cls.range_start)
|
2022-05-14 23:50:36 +00:00
|
|
|
elif cls.range_start == 0 \
|
|
|
|
and hasattr(cls, "default") \
|
|
|
|
and cls.default != 0 \
|
|
|
|
and text in ("true", "false"):
|
|
|
|
# these are the conditions where "true" and "false" make sense
|
|
|
|
if text == "true":
|
2022-09-19 20:40:15 +00:00
|
|
|
return cls.from_any(cls.default)
|
2022-05-14 23:50:36 +00:00
|
|
|
else: # "false"
|
|
|
|
return cls(0)
|
2021-06-08 13:39:34 +00:00
|
|
|
return cls(int(text))
|
2021-06-08 12:15:23 +00:00
|
|
|
|
2022-06-12 21:33:14 +00:00
|
|
|
@classmethod
|
|
|
|
def weighted_range(cls, text) -> Range:
|
|
|
|
if text == "random-low":
|
|
|
|
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_start))
|
|
|
|
elif text == "random-high":
|
|
|
|
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_end))
|
|
|
|
elif text == "random-middle":
|
|
|
|
return cls(cls.triangular(cls.range_start, cls.range_end))
|
|
|
|
elif text.startswith("random-range-"):
|
|
|
|
return cls.custom_range(text)
|
|
|
|
elif text == "random":
|
|
|
|
return cls(random.randint(cls.range_start, cls.range_end))
|
|
|
|
else:
|
|
|
|
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. "
|
|
|
|
f"Acceptable values are: random, random-high, random-middle, random-low, "
|
|
|
|
f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, "
|
|
|
|
f"random-range-high-<min>-<max>, or random-range-<min>-<max>.")
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def custom_range(cls, text) -> Range:
|
|
|
|
textsplit = text.split("-")
|
|
|
|
try:
|
|
|
|
random_range = [int(textsplit[len(textsplit) - 2]), int(textsplit[len(textsplit) - 1])]
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError(f"Invalid random range {text} for option {cls.__name__}")
|
|
|
|
random_range.sort()
|
|
|
|
if random_range[0] < cls.range_start or random_range[1] > cls.range_end:
|
|
|
|
raise Exception(
|
|
|
|
f"{random_range[0]}-{random_range[1]} is outside allowed range "
|
|
|
|
f"{cls.range_start}-{cls.range_end} for option {cls.__name__}")
|
|
|
|
if text.startswith("random-range-low"):
|
|
|
|
return cls(cls.triangular(random_range[0], random_range[1], random_range[0]))
|
|
|
|
elif text.startswith("random-range-middle"):
|
|
|
|
return cls(cls.triangular(random_range[0], random_range[1]))
|
|
|
|
elif text.startswith("random-range-high"):
|
|
|
|
return cls(cls.triangular(random_range[0], random_range[1], random_range[1]))
|
|
|
|
else:
|
|
|
|
return cls(random.randint(random_range[0], random_range[1]))
|
|
|
|
|
2021-06-08 12:15:23 +00:00
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Any) -> Range:
|
|
|
|
if type(data) == int:
|
|
|
|
return cls(data)
|
|
|
|
return cls.from_text(str(data))
|
2020-03-18 15:15:32 +00:00
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
@classmethod
|
|
|
|
def get_option_name(cls, value: int) -> str:
|
2021-10-14 17:42:13 +00:00
|
|
|
return str(value)
|
2021-06-08 13:39:34 +00:00
|
|
|
|
2022-04-07 17:42:30 +00:00
|
|
|
def __str__(self) -> str:
|
2021-06-08 19:58:11 +00:00
|
|
|
return str(self.value)
|
|
|
|
|
2022-06-12 21:33:14 +00:00
|
|
|
@staticmethod
|
|
|
|
def triangular(lower: int, end: int, tri: typing.Optional[int] = None) -> int:
|
|
|
|
return int(round(random.triangular(lower, end, tri), 0))
|
|
|
|
|
|
|
|
|
|
|
|
class SpecialRange(Range):
|
|
|
|
special_range_cutoff = 0
|
|
|
|
special_range_names: typing.Dict[str, int] = {}
|
2022-06-14 01:52:21 +00:00
|
|
|
"""Special Range names have to be all lowercase as matching is done with text.lower()"""
|
2022-06-12 21:33:14 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str) -> Range:
|
|
|
|
text = text.lower()
|
|
|
|
if text in cls.special_range_names:
|
|
|
|
return cls(cls.special_range_names[text])
|
|
|
|
return super().from_text(text)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def weighted_range(cls, text) -> Range:
|
|
|
|
if text == "random-low":
|
|
|
|
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.special_range_cutoff))
|
|
|
|
elif text == "random-high":
|
|
|
|
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end, cls.range_end))
|
|
|
|
elif text == "random-middle":
|
|
|
|
return cls(cls.triangular(cls.special_range_cutoff, cls.range_end))
|
|
|
|
elif text.startswith("random-range-"):
|
|
|
|
return cls.custom_range(text)
|
|
|
|
elif text == "random":
|
|
|
|
return cls(random.randint(cls.special_range_cutoff, cls.range_end))
|
|
|
|
else:
|
|
|
|
raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. "
|
|
|
|
f"Acceptable values are: random, random-high, random-middle, random-low, "
|
|
|
|
f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, "
|
|
|
|
f"random-range-high-<min>-<max>, or random-range-<min>-<max>.")
|
|
|
|
|
2021-06-08 13:39:34 +00:00
|
|
|
|
2023-04-10 21:54:56 +00:00
|
|
|
class FreezeValidKeys(AssembleOptions):
|
|
|
|
def __new__(mcs, name, bases, attrs):
|
|
|
|
if "valid_keys" in attrs:
|
|
|
|
attrs["_valid_keys"] = frozenset(attrs["valid_keys"])
|
|
|
|
return super(FreezeValidKeys, mcs).__new__(mcs, name, bases, attrs)
|
|
|
|
|
|
|
|
|
|
|
|
class VerifyKeys(metaclass=FreezeValidKeys):
|
|
|
|
valid_keys: typing.Iterable = []
|
|
|
|
_valid_keys: frozenset # gets created by AssembleOptions from valid_keys
|
2022-01-11 21:01:54 +00:00
|
|
|
valid_keys_casefold: bool = False
|
2022-03-21 19:49:54 +00:00
|
|
|
convert_name_groups: bool = False
|
|
|
|
verify_item_name: bool = False
|
|
|
|
verify_location_name: bool = False
|
2022-02-06 15:37:21 +00:00
|
|
|
value: typing.Any
|
2022-01-11 21:01:54 +00:00
|
|
|
|
|
|
|
@classmethod
|
2023-03-07 07:44:20 +00:00
|
|
|
def verify_keys(cls, data: typing.List[str]):
|
2022-01-11 21:01:54 +00:00
|
|
|
if cls.valid_keys:
|
2022-01-15 20:20:26 +00:00
|
|
|
data = set(data)
|
2022-01-11 21:01:54 +00:00
|
|
|
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
|
2023-04-10 21:54:56 +00:00
|
|
|
extra = dataset - cls._valid_keys
|
2022-01-11 21:01:54 +00:00
|
|
|
if extra:
|
|
|
|
raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. "
|
2023-04-10 21:54:56 +00:00
|
|
|
f"Allowed keys: {cls._valid_keys}.")
|
2022-01-11 21:01:54 +00:00
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
2022-03-21 19:49:54 +00:00
|
|
|
if self.convert_name_groups and self.verify_item_name:
|
|
|
|
new_value = type(self.value)() # empty container of whatever value is
|
|
|
|
for item_name in self.value:
|
|
|
|
new_value |= world.item_name_groups.get(item_name, {item_name})
|
|
|
|
self.value = new_value
|
2023-03-08 21:15:28 +00:00
|
|
|
elif self.convert_name_groups and self.verify_location_name:
|
|
|
|
new_value = type(self.value)()
|
|
|
|
for loc_name in self.value:
|
|
|
|
new_value |= world.location_name_groups.get(loc_name, {loc_name})
|
|
|
|
self.value = new_value
|
2022-02-06 15:37:21 +00:00
|
|
|
if self.verify_item_name:
|
|
|
|
for item_name in self.value:
|
|
|
|
if item_name not in world.item_names:
|
2022-05-09 15:03:16 +00:00
|
|
|
picks = get_fuzzy_results(item_name, world.item_names, limit=1)
|
2022-02-06 15:37:21 +00:00
|
|
|
raise Exception(f"Item {item_name} from option {self} "
|
2022-04-26 09:28:43 +00:00
|
|
|
f"is not a valid item name from {world.game}. "
|
|
|
|
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)")
|
2022-02-06 15:37:21 +00:00
|
|
|
elif self.verify_location_name:
|
|
|
|
for location_name in self.value:
|
2022-03-21 19:49:54 +00:00
|
|
|
if location_name not in world.location_names:
|
2022-05-09 15:03:16 +00:00
|
|
|
picks = get_fuzzy_results(location_name, world.location_names, limit=1)
|
2022-02-06 15:37:21 +00:00
|
|
|
raise Exception(f"Location {location_name} from option {self} "
|
2022-04-26 09:28:43 +00:00
|
|
|
f"is not a valid location name from {world.game}. "
|
|
|
|
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure)")
|
2022-02-06 15:37:21 +00:00
|
|
|
|
2022-01-11 21:01:54 +00:00
|
|
|
|
2022-03-27 23:47:47 +00:00
|
|
|
class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys):
|
2022-09-28 21:54:10 +00:00
|
|
|
default: typing.Dict[str, typing.Any] = {}
|
2021-09-16 22:17:54 +00:00
|
|
|
supports_weighting = False
|
2021-05-09 15:46:26 +00:00
|
|
|
|
|
|
|
def __init__(self, value: typing.Dict[str, typing.Any]):
|
2022-10-23 16:28:09 +00:00
|
|
|
self.value = deepcopy(value)
|
2021-05-09 15:46:26 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict:
|
|
|
|
if type(data) == dict:
|
2022-01-15 20:20:26 +00:00
|
|
|
cls.verify_keys(data)
|
2021-05-09 15:46:26 +00:00
|
|
|
return cls(data)
|
|
|
|
else:
|
|
|
|
raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}")
|
|
|
|
|
2021-08-03 17:09:37 +00:00
|
|
|
def get_option_name(self, value):
|
2021-10-14 17:42:13 +00:00
|
|
|
return ", ".join(f"{key}: {v}" for key, v in value.items())
|
2021-05-09 15:46:26 +00:00
|
|
|
|
2021-09-30 17:49:36 +00:00
|
|
|
def __contains__(self, item):
|
|
|
|
return item in self.value
|
2021-06-08 13:39:34 +00:00
|
|
|
|
2021-09-30 17:49:36 +00:00
|
|
|
|
2021-10-25 02:13:25 +00:00
|
|
|
class ItemDict(OptionDict):
|
|
|
|
verify_item_name = True
|
|
|
|
|
|
|
|
def __init__(self, value: typing.Dict[str, int]):
|
|
|
|
if any(item_count < 1 for item_count in value.values()):
|
|
|
|
raise Exception("Cannot have non-positive item counts.")
|
|
|
|
super(ItemDict, self).__init__(value)
|
|
|
|
|
|
|
|
|
2022-03-27 23:47:47 +00:00
|
|
|
class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
2023-04-10 21:54:56 +00:00
|
|
|
# Supports duplicate entries and ordering.
|
|
|
|
# If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead.
|
|
|
|
# Not a docstring so it doesn't get grabbed by the options system.
|
|
|
|
|
2022-09-28 21:54:10 +00:00
|
|
|
default: typing.List[typing.Any] = []
|
2021-09-16 22:17:54 +00:00
|
|
|
supports_weighting = False
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
|
MC: 1.17 support (#120)
* MC: add death_link option
* Minecraft: 1.17 advancements and logic support
* Update Minecraft tracker to 1.17
* Minecraft: add tests for new advancements
* removed jdk/forge download install out of iss and into MinecraftClient.py using flag --install
* Add required_bosses option
choices are none, ender_dragon, wither, both
postgame advancements are set according to the required boss for completion
* fix docstring for PostgameAdvancements
* Minecraft: add starting_items
List of dicts: item, amount, nbt
* Update descriptions for AdvancementGoal and EggShardsRequired
* Minecraft: fix tests for required_bosses attribute
* Minecraft: updated logic for various dragon-related advancements
Split the logic into can_respawn and can_kill dragon
Free the End, Monsters Hunted, The End Again still require both respawn and kill, since the player needs to kill and be credited with the kill
You Need a Mint and Is It a Plane now require only respawn, since the dragon need only be alive; if killed out of logic, it's ok
The Next Generation only requires kill, since the egg spawns regardless of whether the player was credited with the kill or not
* Minecraft client: ignore prereleases unless --prerelease flag is on
* explicitly state all defaults
change structure shuffle and structure compass defaults to true
update install tutorial to point to player-settings page, as well as removing instructions for manual install
* Minecraft client: add Minecraft version check
Adds a minecraft_version field in the apmc, and downloads only mods which contain that version in the name of the .jar file.
This ensures that the client remains compatible even if new mods are released for later versions, since they won't download a mod for a later version than the apmc says.
Co-authored-by: Kono Tyran <Kono.Tyran@gmail.com>
2021-12-01 01:37:11 +00:00
|
|
|
def __init__(self, value: typing.List[typing.Any]):
|
2022-10-23 16:28:09 +00:00
|
|
|
self.value = deepcopy(value)
|
2021-09-16 22:17:54 +00:00
|
|
|
super(OptionList, self).__init__()
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str):
|
|
|
|
return cls([option.strip() for option in text.split(",")])
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Any):
|
|
|
|
if type(data) == list:
|
2022-01-15 20:20:26 +00:00
|
|
|
cls.verify_keys(data)
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
return cls(data)
|
|
|
|
return cls.from_text(str(data))
|
|
|
|
|
|
|
|
def get_option_name(self, value):
|
2021-12-02 03:53:52 +00:00
|
|
|
return ", ".join(map(str, value))
|
2021-09-16 22:17:54 +00:00
|
|
|
|
2021-09-30 17:49:36 +00:00
|
|
|
def __contains__(self, item):
|
|
|
|
return item in self.value
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
|
2022-03-27 23:47:47 +00:00
|
|
|
class OptionSet(Option[typing.Set[str]], VerifyKeys):
|
2022-10-23 16:28:09 +00:00
|
|
|
default: typing.Union[typing.Set[str], typing.FrozenSet[str]] = frozenset()
|
2021-09-16 22:17:54 +00:00
|
|
|
supports_weighting = False
|
|
|
|
|
2022-10-23 16:28:09 +00:00
|
|
|
def __init__(self, value: typing.Iterable[str]):
|
|
|
|
self.value = set(deepcopy(value))
|
2021-09-16 22:17:54 +00:00
|
|
|
super(OptionSet, self).__init__()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_text(cls, text: str):
|
|
|
|
return cls([option.strip() for option in text.split(",")])
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_any(cls, data: typing.Any):
|
2022-09-19 20:40:15 +00:00
|
|
|
if isinstance(data, (list, set, frozenset)):
|
2022-02-06 15:37:21 +00:00
|
|
|
cls.verify_keys(data)
|
2021-09-16 22:17:54 +00:00
|
|
|
return cls(data)
|
|
|
|
return cls.from_text(str(data))
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
def get_option_name(self, value):
|
2022-03-22 14:25:34 +00:00
|
|
|
return ", ".join(sorted(value))
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
|
2021-09-30 17:49:36 +00:00
|
|
|
def __contains__(self, item):
|
|
|
|
return item in self.value
|
|
|
|
|
Ocarina of Time (#64)
* first commit (not including OoT data files yet)
* added some basic options
* rule parser works now at least
* make sure to commit everything this time
* temporary change to BaseClasses for oot
* overworld location graph builds mostly correctly
* adding oot data files
* commenting out world options until later since they only existed to make the RuleParser work
* conversion functions between AP ids and OOT ids
* world graph outputs
* set scrub prices
* itempool generates, entrances connected, way too many options added
* fixed set_rules and set_shop_rules
* temp baseclasses changes
* Reaches the fill step now, old event-based system retained in case the new way breaks
* Song placements and misc fixes everywhere
* temporary changes to make oot work
* changed root exits for AP fill framework
* prevent infinite recursion due to OoT sharing usage of the address field
* age reachability works hopefully, songs are broken again
* working spoiler log generation on beatable-only
* Logic tricks implemented
* need this for logic tricks
* fixed map/compass being placed on Serenade location
* kill unreachable events before filling the world
* add a bunch of utility functions to prepare for rom patching
* move OptionList into generic options
* fixed some silly bugs with OptionList
* properly seed all random behavior (so far)
* ROM generation working
* fix hints trying to get alttp dungeon hint texts
* continue fixing hints
* add oot to network data package
* change item and location IDs to 66000 and 67000 range respectively
* push removed items to precollected items
* fixed various issues with cross-contamination with multiple world generation
* reenable glitched logic (hopefully)
* glitched world files age-check fix
* cleaned up some get_locations calls
* added token shuffle and scrub shuffle, modified some options slightly to make the parsing work
* reenable MQ dungeons
* fix forest mq exception
* made targeting style an option for now, will be cosmetic later
* reminder to move targeting to cosmetics
* some oot option maintenance
* enabled starting time of day
* fixed issue breaking shop slots in multiworld generation
* added "off" option for text shuffle and hints
* shopsanity functionality restored
* change patch file extension
* remove unnecessary utility functions + imports
* update MIT license
* change option to "patch_uncompressed_rom" instead of "compress_rom"
* compliance with new AutoWorld systems
* Kill only internal events, remove non-internal big poe event in code
* re-add the big poe event and handle it correctly
* remove extra method in Range option
* fix typo
* Starting items, starting with consumables option
* do not remove nonexistent item
* move set_shop_rules to after shop items are placed
* some cleanup
* add retries for song placement
* flagged Skull Mask and Mask of Truth as advancement items
* update OoT to use LogicMixin
* Fixed trying to assign starting items from the wrong players
* fixed song retry step
* improved option handling, comments, and starting item replacements
* DefaultOnToggle writes Yes or No to spoiler
* enable compression of output if Compress executable is present
* clean up compression
* check whether (de)compressor exists before running the process
* allow specification of rom path in host.yaml
* check if decompressed file already exists before decompressing again
* fix triforce hunt generation
* rename all the oot state functions with prefix
* OoT: mark triforce pieces as completion goal for triforce hunt
* added overworld and any-dungeon shuffle for dungeon items
* Hide most unshuffled locations and events from the list of locations in spoiler
* build oot option ranges with a generic function instead of defining each separately
* move oot output-type control to host.yaml instead of individual yamls
* implement dungeon song shuffle
* minor improvements to overworld dungeon item shuffle
* remove random ice trap names in shops, mostly to avoid maintaining a massive censor list
* always output patch file to folder, remove option to generate ROM in preparation for removal
* re-add the fix for infinite recursion due to not being light or dark world
* change AP-sendable to Ocarina of Time model, since the triforce piece has some extra code apparently
* oot: remove item_names and location_names
* oot: minor fixes
* oot: comment out ROM patching
* oot: only add CollectionState objects on creation if actually needed
* main entrance shuffle method and entrances-based rules
* fix entrances based rules
* disable master quest and big poe count options for client compatibility
* use get_player_name instead of get_player_names
* fix OptionList
* fix oot options for new option system
* new coop section in oot rom: expand player names to 16 bytes, write AP_PLAYER_NAME at end of PLAYER_NAMES
* fill AP player name in oot rom with 0 instead of 0xDF
* encode player name with ASCII for fixed-width
* revert oot player name array to 8 bytes per name
* remove Pierre location if fast scarecrow is on
* check player name length
* "free_scarecrow" not "fast_scarecrow"
* OoT locations now properly store the AP ID instead of the oot internal ID
* oot __version__ updates in lockstep with AP version
* pull in unmodified oot cosmetic files
* also grab JSONDump since it's needed apparently
* gather extra needed methods, modify imports
* delete cosmetics log, replace all instances of SettingsList with OOTWorld
* cosmetic options working, except for sound effects (due to ear-safe issues)
* SFX, Music, and Fanfare randomization reenabled
* move OoT data files into the worlds folder
* move Compress and Decompress into oot data folder
* Replace get_all_state with custom method to avoid the cache
* OoT ROM: increment item counter before setting incoming item/player values to 0, preventing desync issues
* set data_version to 0
* make Kokiri Sword shuffle off by default
* reenable "Random Choice" for various cosmetic options
* kill Ruto's Letter turnin if open fountain
also fix for shopsanity
* place Buy Goron/Zora Tunic first in shop shuffle
* make ice traps appear as other items instead of breaking generation
* managed to break ice traps on non-major-only
* only handle ice traps if they are on
* fix shopsanity for non-oot games, and write player name instead of player number
* light arrows hint uses player name instead of player number
* Reenable "skip child zelda" option
* fix entrances_based_rules
* fix ganondorf hint if starting with light arrows
* fix dungeonitem shuffle and shopsanity interaction
* remove has_all_of, has_any_of, count_of in BaseClasses, replace usage with has_all, has_any, has_group
* force local giveable item on ZL if skip_child_zelda and shuffle_song_items is any
* keep bosses and bombchu bowling chus out of data package
* revert workaround for infinite recursion and fix it properly
* fix shared shop id caches during patching process
* fix shop text box overflows, as much as possible
* add default oot host.yaml option
* add .apz5, .n64, .z64 to gitignore
* Properly document and name all (functioning) OOT options
* clean up some imports
* remove unnecessary files from oot's data
* fix typo in gitignore
* readd the Compress and Decompress utilities, since they are needed for generation
* cleanup of imports and some minor optimizations
* increase shop offset for item IDs to 0xCB
* remove shop item AP ids entirely
* prevent triforce pieces for other players from being received by yourself
* add "excluded" property to Location
* Hint system adapted and reenabled; hints still unseeded
* make hints deterministic with lists instead of sets
* do not allow hints to point to Light Arrows on non-vanilla bridge
* foreign locations hint as their full name in OoT rather than their region
* checkedLocations now stores hint names by player ID, so that the same location in different worlds can have hints associated
* consolidate versioning in Utils
* ice traps appear as major items rather than any progression item
* set prescription and claim check as defaults for adult trade item settings
* add oot options to playerSettings
* allow case-insensitive logic tricks in yaml
* fix oot shopsanity option formatting
* Write OoT override info even if local item, enabling local checks to show up immediately in the client
* implement CollectionState.can_live_dmg for oot glitched logic
* filter item names for invalid characters when patching shops
* make ice traps appear according to the settings of the world they are shuffled into, rather than the original world
* set hidden-spoiler items and locations with Shop items to events
* make GF carpenters, Gerudo Card, Malon, ZL, and Impa events if the relevant settings are enabled, preventing them from appearing in the client on game start
* Fix oot Glitched and No Logic generation
* fix indenting
* Greatly reduce displayed cosmetic options
* Change oot data version to 1
* add apz5 distribution to webhost
* print player name if an ALttP dungeon contains a good item for OoT world
* delete unneeded commented code
* remove OcarinaSongs import to satisfy lint
2021-09-02 12:35:05 +00:00
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
class ItemSet(OptionSet):
|
|
|
|
verify_item_name = True
|
|
|
|
convert_name_groups = True
|
2021-03-20 23:47:17 +00:00
|
|
|
|
2020-03-18 15:15:32 +00:00
|
|
|
|
|
|
|
class Accessibility(Choice):
|
2021-09-16 22:17:54 +00:00
|
|
|
"""Set rules for reachability of your items/locations.
|
|
|
|
Locations: ensure everything can be reached and acquired.
|
|
|
|
Items: ensure all logically relevant items can be acquired.
|
|
|
|
Minimal: ensure what is needed to reach your goal can be acquired."""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Accessibility"
|
2020-10-24 04:43:35 +00:00
|
|
|
option_locations = 0
|
|
|
|
option_items = 1
|
2021-09-16 22:17:54 +00:00
|
|
|
option_minimal = 2
|
|
|
|
alias_none = 2
|
|
|
|
default = 1
|
|
|
|
|
|
|
|
|
2022-06-12 21:33:14 +00:00
|
|
|
class ProgressionBalancing(SpecialRange):
|
2022-05-11 07:13:21 +00:00
|
|
|
"""A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
2022-11-01 23:07:56 +00:00
|
|
|
A lower setting means more getting stuck. A higher setting means less getting stuck."""
|
2022-05-11 07:13:21 +00:00
|
|
|
default = 50
|
|
|
|
range_start = 0
|
|
|
|
range_end = 99
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Progression Balancing"
|
2022-06-12 21:33:14 +00:00
|
|
|
special_range_names = {
|
2022-06-14 01:52:21 +00:00
|
|
|
"disabled": 0,
|
|
|
|
"normal": 50,
|
|
|
|
"extreme": 99,
|
2022-06-12 21:33:14 +00:00
|
|
|
}
|
2021-09-16 22:17:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
common_options = {
|
|
|
|
"progression_balancing": ProgressionBalancing,
|
|
|
|
"accessibility": Accessibility
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LocalItems(ItemSet):
|
|
|
|
"""Forces these items to be in their native world."""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Local Items"
|
2020-03-18 15:15:32 +00:00
|
|
|
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
class NonLocalItems(ItemSet):
|
|
|
|
"""Forces these items to be outside their native world."""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Not Local Items"
|
2021-09-16 22:17:54 +00:00
|
|
|
|
|
|
|
|
2021-10-25 02:13:25 +00:00
|
|
|
class StartInventory(ItemDict):
|
2021-09-16 22:17:54 +00:00
|
|
|
"""Start with these items."""
|
|
|
|
verify_item_name = True
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Start Inventory"
|
|
|
|
|
|
|
|
|
2023-04-10 19:13:33 +00:00
|
|
|
class StartInventoryPool(StartInventory):
|
|
|
|
"""Start with these items and don't place them in the world.
|
|
|
|
The game decides what the replacement items will be."""
|
|
|
|
verify_item_name = True
|
|
|
|
display_name = "Start Inventory from Pool"
|
|
|
|
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
class StartHints(ItemSet):
|
|
|
|
"""Start with these item's locations prefilled into the !hint command."""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Start Hints"
|
2021-09-16 22:17:54 +00:00
|
|
|
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
class LocationSet(OptionSet):
|
|
|
|
verify_location_name = True
|
2023-04-04 17:29:20 +00:00
|
|
|
convert_name_groups = True
|
2023-03-07 07:44:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StartLocationHints(LocationSet):
|
2021-11-17 15:44:20 +00:00
|
|
|
"""Start with these locations and their item prefilled into the !hint command"""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Start Location Hints"
|
2021-10-03 12:40:25 +00:00
|
|
|
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
class ExcludeLocations(LocationSet):
|
2021-09-16 22:17:54 +00:00
|
|
|
"""Prevent these locations from having an important item"""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Excluded Locations"
|
2021-09-16 22:17:54 +00:00
|
|
|
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
class PriorityLocations(LocationSet):
|
2022-02-01 15:36:14 +00:00
|
|
|
"""Prevent these locations from having an unimportant item"""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Priority Locations"
|
2022-02-01 15:36:14 +00:00
|
|
|
|
|
|
|
|
2021-11-01 18:37:47 +00:00
|
|
|
class DeathLink(Toggle):
|
|
|
|
"""When you die, everyone dies. Of course the reverse is true too."""
|
2022-02-02 15:29:29 +00:00
|
|
|
display_name = "Death Link"
|
2021-11-01 18:37:47 +00:00
|
|
|
|
|
|
|
|
2022-02-05 14:49:19 +00:00
|
|
|
class ItemLinks(OptionList):
|
|
|
|
"""Share part of your item pool with other players."""
|
|
|
|
default = []
|
|
|
|
schema = Schema([
|
|
|
|
{
|
|
|
|
"name": And(str, len),
|
|
|
|
"item_pool": [And(str, len)],
|
2022-05-11 23:37:18 +00:00
|
|
|
Optional("exclude"): [And(str, len)],
|
2022-05-15 14:41:11 +00:00
|
|
|
"replacement_item": Or(And(str, len), None),
|
|
|
|
Optional("local_items"): [And(str, len)],
|
2022-12-07 05:37:47 +00:00
|
|
|
Optional("non_local_items"): [And(str, len)],
|
|
|
|
Optional("link_replacement"): Or(None, bool),
|
2022-02-05 14:49:19 +00:00
|
|
|
}
|
|
|
|
])
|
|
|
|
|
2022-05-15 14:41:11 +00:00
|
|
|
@staticmethod
|
|
|
|
def verify_items(items: typing.List[str], item_link: str, pool_name: str, world, allow_item_groups: bool = True) -> typing.Set:
|
|
|
|
pool = set()
|
|
|
|
for item_name in items:
|
|
|
|
if item_name not in world.item_names and (not allow_item_groups or item_name not in world.item_name_groups):
|
|
|
|
picks = get_fuzzy_results(item_name, world.item_names, limit=1)
|
|
|
|
picks_group = get_fuzzy_results(item_name, world.item_name_groups.keys(), limit=1)
|
|
|
|
picks_group = f" or '{picks_group[0][0]}' ({picks_group[0][1]}% sure)" if allow_item_groups else ""
|
|
|
|
|
|
|
|
raise Exception(f"Item {item_name} from item link {item_link} "
|
|
|
|
f"is not a valid item from {world.game} for {pool_name}. "
|
|
|
|
f"Did you mean '{picks[0][0]}' ({picks[0][1]}% sure){picks_group}")
|
|
|
|
if allow_item_groups:
|
|
|
|
pool |= world.item_name_groups.get(item_name, {item_name})
|
|
|
|
else:
|
|
|
|
pool |= {item_name}
|
|
|
|
return pool
|
|
|
|
|
2023-03-07 07:44:20 +00:00
|
|
|
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
2022-12-07 05:37:47 +00:00
|
|
|
link: dict
|
2022-09-17 00:55:33 +00:00
|
|
|
super(ItemLinks, self).verify(world, player_name, plando_options)
|
2022-02-23 23:17:24 +00:00
|
|
|
existing_links = set()
|
2022-02-06 15:37:21 +00:00
|
|
|
for link in self.value:
|
2022-02-23 23:17:24 +00:00
|
|
|
if link["name"] in existing_links:
|
|
|
|
raise Exception(f"You cannot have more than one link named {link['name']}.")
|
|
|
|
existing_links.add(link["name"])
|
2022-05-15 14:41:11 +00:00
|
|
|
|
|
|
|
pool = self.verify_items(link["item_pool"], link["name"], "item_pool", world)
|
|
|
|
local_items = set()
|
|
|
|
non_local_items = set()
|
|
|
|
|
2022-05-11 23:37:18 +00:00
|
|
|
if "exclude" in link:
|
2022-05-15 14:41:11 +00:00
|
|
|
pool -= self.verify_items(link["exclude"], link["name"], "exclude", world)
|
|
|
|
if link["replacement_item"]:
|
|
|
|
self.verify_items([link["replacement_item"]], link["name"], "replacement_item", world, False)
|
|
|
|
if "local_items" in link:
|
|
|
|
local_items = self.verify_items(link["local_items"], link["name"], "local_items", world)
|
|
|
|
local_items &= pool
|
|
|
|
if "non_local_items" in link:
|
|
|
|
non_local_items = self.verify_items(link["non_local_items"], link["name"], "non_local_items", world)
|
|
|
|
non_local_items &= pool
|
|
|
|
|
|
|
|
intersection = local_items.intersection(non_local_items)
|
|
|
|
if intersection:
|
2022-12-07 05:37:47 +00:00
|
|
|
raise Exception(f"item_link {link['name']} has {intersection} "
|
|
|
|
f"items in both its local_items and non_local_items pool.")
|
|
|
|
link.setdefault("link_replacement", None)
|
2022-05-15 14:41:11 +00:00
|
|
|
|
|
|
|
|
2021-09-16 22:17:54 +00:00
|
|
|
per_game_common_options = {
|
2021-12-28 17:43:52 +00:00
|
|
|
**common_options, # can be overwritten per-game
|
2021-09-16 22:17:54 +00:00
|
|
|
"local_items": LocalItems,
|
|
|
|
"non_local_items": NonLocalItems,
|
|
|
|
"start_inventory": StartInventory,
|
|
|
|
"start_hints": StartHints,
|
2021-10-03 12:40:25 +00:00
|
|
|
"start_location_hints": StartLocationHints,
|
2022-02-01 15:36:14 +00:00
|
|
|
"exclude_locations": ExcludeLocations,
|
|
|
|
"priority_locations": PriorityLocations,
|
2022-02-05 14:49:19 +00:00
|
|
|
"item_links": ItemLinks
|
2021-09-16 22:17:54 +00:00
|
|
|
}
|
|
|
|
|
2023-04-15 23:57:52 +00:00
|
|
|
|
|
|
|
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
|
|
|
|
import os
|
|
|
|
|
|
|
|
import yaml
|
|
|
|
from jinja2 import Template
|
|
|
|
|
|
|
|
from worlds import AutoWorldRegister
|
|
|
|
from Utils import local_path, __version__
|
|
|
|
|
|
|
|
full_path: str
|
|
|
|
|
|
|
|
os.makedirs(target_folder, exist_ok=True)
|
|
|
|
|
|
|
|
# clean out old
|
|
|
|
for file in os.listdir(target_folder):
|
|
|
|
full_path = os.path.join(target_folder, file)
|
|
|
|
if os.path.isfile(full_path) and full_path.endswith(".yaml"):
|
|
|
|
os.unlink(full_path)
|
|
|
|
|
|
|
|
def dictify_range(option: typing.Union[Range, SpecialRange]):
|
|
|
|
data = {option.default: 50}
|
|
|
|
for sub_option in ["random", "random-low", "random-high"]:
|
|
|
|
if sub_option != option.default:
|
|
|
|
data[sub_option] = 0
|
|
|
|
|
|
|
|
notes = {}
|
|
|
|
for name, number in getattr(option, "special_range_names", {}).items():
|
|
|
|
notes[name] = f"equivalent to {number}"
|
|
|
|
if number in data:
|
|
|
|
data[name] = data[number]
|
|
|
|
del data[number]
|
|
|
|
else:
|
|
|
|
data[name] = 0
|
|
|
|
|
|
|
|
return data, notes
|
|
|
|
|
|
|
|
for game_name, world in AutoWorldRegister.world_types.items():
|
|
|
|
if not world.hidden or generate_hidden:
|
|
|
|
all_options: typing.Dict[str, AssembleOptions] = {
|
|
|
|
**per_game_common_options,
|
|
|
|
**world.option_definitions
|
|
|
|
}
|
|
|
|
|
|
|
|
with open(local_path("data", "options.yaml")) as f:
|
|
|
|
file_data = f.read()
|
|
|
|
res = Template(file_data).render(
|
|
|
|
options=all_options,
|
|
|
|
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
|
|
|
|
dictify_range=dictify_range,
|
|
|
|
)
|
|
|
|
|
|
|
|
del file_data
|
|
|
|
|
|
|
|
with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
|
|
|
|
f.write(res)
|
|
|
|
|
|
|
|
|
2020-03-18 15:15:32 +00:00
|
|
|
if __name__ == "__main__":
|
2021-07-04 14:18:21 +00:00
|
|
|
|
|
|
|
from worlds.alttp.Options import Logic
|
2020-03-18 15:15:32 +00:00
|
|
|
import argparse
|
2021-09-16 22:17:54 +00:00
|
|
|
|
2021-08-30 16:00:39 +00:00
|
|
|
map_shuffle = Toggle
|
|
|
|
compass_shuffle = Toggle
|
2022-02-05 14:49:19 +00:00
|
|
|
key_shuffle = Toggle
|
|
|
|
big_key_shuffle = Toggle
|
2021-06-18 23:00:21 +00:00
|
|
|
hints = Toggle
|
2020-03-18 15:15:32 +00:00
|
|
|
test = argparse.Namespace()
|
|
|
|
test.logic = Logic.from_text("no_logic")
|
2021-08-30 16:00:39 +00:00
|
|
|
test.map_shuffle = map_shuffle.from_text("ON")
|
2020-10-24 04:43:35 +00:00
|
|
|
test.hints = hints.from_text('OFF')
|
|
|
|
try:
|
|
|
|
test.logic = Logic.from_text("overworld_glitches_typo")
|
|
|
|
except KeyError as e:
|
|
|
|
print(e)
|
2020-03-18 15:15:32 +00:00
|
|
|
try:
|
2020-10-24 04:43:35 +00:00
|
|
|
test.logic_owg = Logic.from_text("owg")
|
2020-03-18 15:15:32 +00:00
|
|
|
except KeyError as e:
|
|
|
|
print(e)
|
2021-08-30 16:00:39 +00:00
|
|
|
if test.map_shuffle:
|
|
|
|
print("map_shuffle is on")
|
2020-10-24 04:43:35 +00:00
|
|
|
print(f"Hints are {bool(test.hints)}")
|
2020-03-18 15:15:32 +00:00
|
|
|
print(test)
|