| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2021-06-08 14:15:23 +02:00
										 |  |  | import random | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | class AssembleOptions(type): | 
					
						
							| 
									
										
										
										
											2021-04-14 17:51:11 +02:00
										 |  |  |     def __new__(mcs, name, bases, attrs): | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         options = attrs["options"] = {} | 
					
						
							|  |  |  |         name_lookup = attrs["name_lookup"] = {} | 
					
						
							|  |  |  |         for base in bases: | 
					
						
							| 
									
										
										
										
											2021-06-08 21:58:11 +02:00
										 |  |  |             if hasattr(base, "options"): | 
					
						
							|  |  |  |                 options.update(base.options) | 
					
						
							|  |  |  |                 name_lookup.update(name_lookup) | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         new_options = {name[7:].lower(): option_id for name, option_id in attrs.items() if | 
					
						
							| 
									
										
										
										
											2021-03-21 00:47:17 +01:00
										 |  |  |                        name.startswith("option_")} | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         attrs["name_lookup"].update({option_id: name for name, option_id in new_options.items()}) | 
					
						
							|  |  |  |         options.update(new_options) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 00:47:17 +01:00
										 |  |  |         # apply aliases, without name_lookup | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         options.update({name[6:].lower(): option_id for name, option_id in attrs.items() if | 
					
						
							|  |  |  |                         name.startswith("alias_")}) | 
					
						
							| 
									
										
										
										
											2021-07-19 01:02:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # auto-validate schema on __init__ | 
					
						
							|  |  |  |         if "schema" in attrs.keys(): | 
					
						
							|  |  |  |             def validate_decorator(func): | 
					
						
							|  |  |  |                 def validate(self, *args, **kwargs): | 
					
						
							|  |  |  |                     func(self, *args, **kwargs) | 
					
						
							|  |  |  |                     self.value = self.schema.validate(self.value) | 
					
						
							|  |  |  |                 return validate | 
					
						
							|  |  |  |             attrs["__init__"] = validate_decorator(attrs["__init__"]) | 
					
						
							| 
									
										
										
										
											2021-04-14 17:51:11 +02:00
										 |  |  |         return super(AssembleOptions, mcs).__new__(mcs, name, bases, attrs) | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Option(metaclass=AssembleOptions): | 
					
						
							|  |  |  |     value: int | 
					
						
							|  |  |  |     name_lookup: typing.Dict[int, str] | 
					
						
							| 
									
										
										
										
											2021-04-03 14:47:49 +02:00
										 |  |  |     default = 0 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return f"{self.__class__.__name__}({self.get_option_name()})" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __hash__(self): | 
					
						
							|  |  |  |         return hash(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_option_name(self): | 
					
						
							|  |  |  |         return self.name_lookup[self.value] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __int__(self): | 
					
						
							|  |  |  |         return self.value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __bool__(self): | 
					
						
							|  |  |  |         return bool(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_any(cls, data: typing.Any): | 
					
						
							|  |  |  |         raise NotImplementedError | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Toggle(Option): | 
					
						
							|  |  |  |     option_false = 0 | 
					
						
							|  |  |  |     option_true = 1 | 
					
						
							| 
									
										
										
										
											2021-04-03 14:47:49 +02:00
										 |  |  |     default = 0 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, value: int): | 
					
						
							|  |  |  |         self.value = value | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_text(cls, text: str) -> Toggle: | 
					
						
							|  |  |  |         if text.lower() in {"off", "0", "false", "none", "null", "no"}: | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |             return cls(0) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return cls(1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_any(cls, data: typing.Any): | 
					
						
							|  |  |  |         if type(data) == str: | 
					
						
							|  |  |  |             return cls.from_text(data) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return cls(data) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |     def __eq__(self, other): | 
					
						
							|  |  |  |         if isinstance(other, Toggle): | 
					
						
							|  |  |  |             return self.value == other.value | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.value == other | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __gt__(self, other): | 
					
						
							|  |  |  |         if isinstance(other, Toggle): | 
					
						
							|  |  |  |             return self.value > other.value | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |             return self.value > other | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 00:47:17 +01:00
										 |  |  |     def __bool__(self): | 
					
						
							|  |  |  |         return bool(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __int__(self): | 
					
						
							|  |  |  |         return int(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |     def get_option_name(self): | 
					
						
							|  |  |  |         return bool(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-19 01:00:21 +02:00
										 |  |  | class DefaultOnToggle(Toggle): | 
					
						
							|  |  |  |     default = 1 | 
					
						
							| 
									
										
										
										
											2021-04-03 14:47:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  | class Choice(Option): | 
					
						
							|  |  |  |     def __init__(self, value: int): | 
					
						
							|  |  |  |         self.value: int = value | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_text(cls, text: str) -> Choice: | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         for optionname, value in cls.options.items(): | 
					
						
							|  |  |  |             if optionname == text.lower(): | 
					
						
							|  |  |  |                 return cls(value) | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |         raise KeyError( | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02: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 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2021-05-09 17:46:26 +02:00
										 |  |  |     def from_any(cls, data: typing.Any) -> Choice: | 
					
						
							| 
									
										
										
										
											2021-04-08 19:53:24 +02:00
										 |  |  |         if type(data) == int and data in cls.options.values(): | 
					
						
							|  |  |  |             return cls(data) | 
					
						
							|  |  |  |         return cls.from_text(str(data)) | 
					
						
							| 
									
										
										
										
											2021-03-14 08:38:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 21:58:11 +02:00
										 |  |  | class Range(Option, int): | 
					
						
							| 
									
										
										
										
											2021-06-08 14:15:23 +02:00
										 |  |  |     range_start = 0 | 
					
						
							|  |  |  |     range_end = 1 | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02: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 21:58:11 +02:00
										 |  |  |         self.value = value | 
					
						
							| 
									
										
										
										
											2021-06-08 14:15:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_text(cls, text: str) -> Range: | 
					
						
							| 
									
										
										
										
											2021-06-08 14:48:00 +02:00
										 |  |  |         text = text.lower() | 
					
						
							|  |  |  |         if text.startswith("random"): | 
					
						
							|  |  |  |             if text == "random-low": | 
					
						
							|  |  |  |                 return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_start), 0))) | 
					
						
							|  |  |  |             elif text == "random-high": | 
					
						
							|  |  |  |                 return cls(int(round(random.triangular(cls.range_start, cls.range_end, cls.range_end), 0))) | 
					
						
							| 
									
										
										
										
											2021-06-12 21:05:45 +02:00
										 |  |  |             elif text == "random-middle": | 
					
						
							|  |  |  |                 return cls(int(round(random.triangular(cls.range_start, cls.range_end), 0))) | 
					
						
							| 
									
										
										
										
											2021-06-08 14:48:00 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 return cls(random.randint(cls.range_start, cls.range_end)) | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02:00
										 |  |  |         return cls(int(text)) | 
					
						
							| 
									
										
										
										
											2021-06-08 14:15:23 +02: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 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 08:59:06 -05:00
										 |  |  |     def get_option_name(self): | 
					
						
							|  |  |  |         return str(self.value) | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 21:58:11 +02:00
										 |  |  |     def __str__(self): | 
					
						
							|  |  |  |         return str(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-09 17:46:26 +02:00
										 |  |  | class OptionNameSet(Option): | 
					
						
							|  |  |  |     default = frozenset() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, value: typing.Set[str]): | 
					
						
							|  |  |  |         self.value: typing.Set[str] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_text(cls, text: str) -> OptionNameSet: | 
					
						
							|  |  |  |         return cls({option.strip() for option in text.split(",")}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_any(cls, data: typing.Any) -> OptionNameSet: | 
					
						
							|  |  |  |         if type(data) == set: | 
					
						
							|  |  |  |             return cls(data) | 
					
						
							|  |  |  |         return cls.from_text(str(data)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class OptionDict(Option): | 
					
						
							|  |  |  |     default = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, value: typing.Dict[str, typing.Any]): | 
					
						
							|  |  |  |         self.value: typing.Dict[str, typing.Any] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict: | 
					
						
							|  |  |  |         if type(data) == dict: | 
					
						
							|  |  |  |             return cls(data) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-04 00:29:59 +02:00
										 |  |  |     def get_option_name(self): | 
					
						
							|  |  |  |         return str(self.value) | 
					
						
							| 
									
										
										
										
											2021-05-09 17:46:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 15:39:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 00:47:17 +01:00
										 |  |  | local_objective = Toggle  # local triforce pieces, local dungeon prizes etc. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Accessibility(Choice): | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |     option_locations = 0 | 
					
						
							|  |  |  |     option_items = 1 | 
					
						
							|  |  |  |     option_beatable = 2 | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2021-07-04 16:18:21 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     from worlds.alttp.Options import Logic | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |     import argparse | 
					
						
							| 
									
										
										
										
											2021-06-19 01:00:21 +02:00
										 |  |  |     mapshuffle = Toggle | 
					
						
							|  |  |  |     compassshuffle = Toggle | 
					
						
							|  |  |  |     keyshuffle = Toggle | 
					
						
							|  |  |  |     bigkeyshuffle = Toggle | 
					
						
							|  |  |  |     hints = Toggle | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |     test = argparse.Namespace() | 
					
						
							|  |  |  |     test.logic = Logic.from_text("no_logic") | 
					
						
							|  |  |  |     test.mapshuffle = mapshuffle.from_text("ON") | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02: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 16:15:32 +01:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |         test.logic_owg = Logic.from_text("owg") | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |     except KeyError as e: | 
					
						
							|  |  |  |         print(e) | 
					
						
							|  |  |  |     if test.mapshuffle: | 
					
						
							|  |  |  |         print("Mapshuffle is on") | 
					
						
							| 
									
										
										
										
											2020-10-24 06:43:35 +02:00
										 |  |  |     print(f"Hints are {bool(test.hints)}") | 
					
						
							| 
									
										
										
										
											2020-03-18 16:15:32 +01:00
										 |  |  |     print(test) |