Core: Minor Options cleanup (#1182)

* Options.py cleanup

* TextChoice cleanup

* make Option.current_option_name a property

* title the TextChoce option names

* satisfy the linter

* a little more options cleanup

* move the typing import

* typing should be PlandoSettings

* fix incorrect conflict merging

* make imports local

* the tests seem to want me to import these twice though i hate it.

* changes from review. Make the various Location verifying Options `LocationSet`

* remove unnecessary fluff

* begrudgingly support get_current_option_name. Leave a comment that worlds shouldn't be touching this

* log a deprecation warning and return the property for `get_current_option_name()`

---------

Co-authored-by: beauxq <beauxq@yahoo.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
alwaysintreble 2023-03-07 01:44:20 -06:00 committed by GitHub
parent e6109394ad
commit 414166f6a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 37 deletions

View File

@ -1261,7 +1261,7 @@ class Spoiler():
res = getattr(self.multiworld, option_key)[player] res = getattr(self.multiworld, option_key)[player]
display_name = getattr(option_obj, "display_name", option_key) display_name = getattr(option_obj, "display_name", option_key)
try: try:
outfile.write(f'{display_name + ":":33}{res.get_current_option_name()}\n') outfile.write(f'{display_name + ":":33}{res.current_option_name}\n')
except: except:
raise Exception raise Exception

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import abc import abc
import logging
from copy import deepcopy from copy import deepcopy
import math import math
import numbers import numbers
@ -9,6 +10,10 @@ import random
from schema import Schema, And, Or, Optional from schema import Schema, And, Or, Optional
from Utils import get_fuzzy_results from Utils import get_fuzzy_results
if typing.TYPE_CHECKING:
from BaseClasses import PlandoOptions
from worlds.AutoWorld import World
class AssembleOptions(abc.ABCMeta): class AssembleOptions(abc.ABCMeta):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
@ -95,11 +100,11 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
supports_weighting = True supports_weighting = True
# filled by AssembleOptions: # filled by AssembleOptions:
name_lookup: typing.Dict[int, str] name_lookup: typing.Dict[T, str]
options: typing.Dict[str, int] options: typing.Dict[str, int]
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.get_current_option_name()})" return f"{self.__class__.__name__}({self.current_option_name})"
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self.value) return hash(self.value)
@ -109,7 +114,14 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
return self.name_lookup[self.value] return self.name_lookup[self.value]
def get_current_option_name(self) -> str: def get_current_option_name(self) -> str:
"""For display purposes.""" """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."""
return self.get_option_name(self.value) return self.get_option_name(self.value)
@classmethod @classmethod
@ -131,17 +143,14 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
... ...
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from Generate import PlandoOptions def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None:
from worlds.AutoWorld import World
def verify(self, world: World, player_name: str, plando_options: PlandoOptions) -> None:
pass pass
else: else:
def verify(self, *args, **kwargs) -> None: def verify(self, *args, **kwargs) -> None:
pass pass
class FreeText(Option): class FreeText(Option[str]):
"""Text option that allows users to enter strings. """Text option that allows users to enter strings.
Needs to be validated by the world or option definition.""" Needs to be validated by the world or option definition."""
@ -162,7 +171,7 @@ class FreeText(Option):
return cls.from_text(str(data)) return cls.from_text(str(data))
@classmethod @classmethod
def get_option_name(cls, value: T) -> str: def get_option_name(cls, value: str) -> str:
return value return value
@ -424,6 +433,7 @@ class Choice(NumericOption):
class TextChoice(Choice): class TextChoice(Choice):
"""Allows custom string input and offers choices. Choices will resolve to int and text will resolve to string""" """Allows custom string input and offers choices. Choices will resolve to int and text will resolve to string"""
value: typing.Union[str, int]
def __init__(self, value: typing.Union[str, int]): def __init__(self, value: typing.Union[str, int]):
assert isinstance(value, str) or isinstance(value, int), \ assert isinstance(value, str) or isinstance(value, int), \
@ -434,8 +444,7 @@ class TextChoice(Choice):
def current_key(self) -> str: def current_key(self) -> str:
if isinstance(self.value, str): if isinstance(self.value, str):
return self.value return self.value
else: return super().current_key
return self.name_lookup[self.value]
@classmethod @classmethod
def from_text(cls, text: str) -> TextChoice: def from_text(cls, text: str) -> TextChoice:
@ -450,7 +459,7 @@ class TextChoice(Choice):
def get_option_name(cls, value: T) -> str: def get_option_name(cls, value: T) -> str:
if isinstance(value, str): if isinstance(value, str):
return value return value
return cls.name_lookup[value] return super().get_option_name(value)
def __eq__(self, other: typing.Any): def __eq__(self, other: typing.Any):
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
@ -573,12 +582,11 @@ class PlandoBosses(TextChoice, metaclass=BossMeta):
def valid_location_name(cls, value: str) -> bool: def valid_location_name(cls, value: str) -> bool:
return value in cls.locations return value in cls.locations
def verify(self, world, player_name: str, plando_options) -> None: def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
if isinstance(self.value, int): if isinstance(self.value, int):
return return
from Generate import PlandoOptions from BaseClasses import PlandoOptions
if not(PlandoOptions.bosses & plando_options): if not(PlandoOptions.bosses & plando_options):
import logging
# plando is disabled but plando options were given so pull the option and change it to an int # plando is disabled but plando options were given so pull the option and change it to an int
option = self.value.split(";")[-1] option = self.value.split(";")[-1]
self.value = self.options[option] self.value = self.options[option]
@ -716,7 +724,7 @@ class VerifyKeys:
value: typing.Any value: typing.Any
@classmethod @classmethod
def verify_keys(cls, data): def verify_keys(cls, data: typing.List[str]):
if cls.valid_keys: if cls.valid_keys:
data = set(data) data = set(data)
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
@ -725,7 +733,7 @@ class VerifyKeys:
raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. " raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. "
f"Allowed keys: {cls.valid_keys}.") f"Allowed keys: {cls.valid_keys}.")
def verify(self, world, player_name: str, plando_options) -> None: def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
if self.convert_name_groups and self.verify_item_name: if self.convert_name_groups and self.verify_item_name:
new_value = type(self.value)() # empty container of whatever value is new_value = type(self.value)() # empty container of whatever value is
for item_name in self.value: for item_name in self.value:
@ -830,7 +838,9 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
return item in self.value return item in self.value
local_objective = Toggle # local triforce pieces, local dungeon prizes etc. class ItemSet(OptionSet):
verify_item_name = True
convert_name_groups = True
class Accessibility(Choice): class Accessibility(Choice):
@ -866,11 +876,6 @@ common_options = {
} }
class ItemSet(OptionSet):
verify_item_name = True
convert_name_groups = True
class LocalItems(ItemSet): class LocalItems(ItemSet):
"""Forces these items to be in their native world.""" """Forces these items to be in their native world."""
display_name = "Local Items" display_name = "Local Items"
@ -892,22 +897,23 @@ class StartHints(ItemSet):
display_name = "Start Hints" display_name = "Start Hints"
class StartLocationHints(OptionSet): class LocationSet(OptionSet):
verify_location_name = True
class StartLocationHints(LocationSet):
"""Start with these locations and their item prefilled into the !hint command""" """Start with these locations and their item prefilled into the !hint command"""
display_name = "Start Location Hints" display_name = "Start Location Hints"
verify_location_name = True
class ExcludeLocations(OptionSet): class ExcludeLocations(LocationSet):
"""Prevent these locations from having an important item""" """Prevent these locations from having an important item"""
display_name = "Excluded Locations" display_name = "Excluded Locations"
verify_location_name = True
class PriorityLocations(OptionSet): class PriorityLocations(LocationSet):
"""Prevent these locations from having an unimportant item""" """Prevent these locations from having an unimportant item"""
display_name = "Priority Locations" display_name = "Priority Locations"
verify_location_name = True
class DeathLink(Toggle): class DeathLink(Toggle):
@ -948,7 +954,7 @@ class ItemLinks(OptionList):
pool |= {item_name} pool |= {item_name}
return pool return pool
def verify(self, world, player_name: str, plando_options) -> None: def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
link: dict link: dict
super(ItemLinks, self).verify(world, player_name, plando_options) super(ItemLinks, self).verify(world, player_name, plando_options)
existing_links = set() existing_links = set()

View File

@ -1,5 +1,5 @@
import unittest import unittest
import Generate from BaseClasses import PlandoOptions
from Options import PlandoBosses from Options import PlandoBosses
@ -123,14 +123,14 @@ class TestPlandoBosses(unittest.TestCase):
regular = MultiBosses.from_any(regular_string) regular = MultiBosses.from_any(regular_string)
# plando should work with boss plando # plando should work with boss plando
plandoed.verify(None, "Player", Generate.PlandoOptions.bosses) plandoed.verify(None, "Player", PlandoOptions.bosses)
self.assertTrue(plandoed.value.startswith(plandoed_string)) self.assertTrue(plandoed.value.startswith(plandoed_string))
# plando should fall back to default without boss plando # plando should fall back to default without boss plando
plandoed.verify(None, "Player", Generate.PlandoOptions.items) plandoed.verify(None, "Player", PlandoOptions.items)
self.assertEqual(plandoed, MultiBosses.option_vanilla) self.assertEqual(plandoed, MultiBosses.option_vanilla)
# mixed should fall back to mode # mixed should fall back to mode
mixed.verify(None, "Player", Generate.PlandoOptions.items) # should produce a warning and still work mixed.verify(None, "Player", PlandoOptions.items) # should produce a warning and still work
self.assertEqual(mixed, MultiBosses.option_shuffle) self.assertEqual(mixed, MultiBosses.option_shuffle)
# mode stuff should just work # mode stuff should just work
regular.verify(None, "Player", Generate.PlandoOptions.items) regular.verify(None, "Player", PlandoOptions.items)
self.assertEqual(regular, MultiBosses.option_shuffle) self.assertEqual(regular, MultiBosses.option_shuffle)

View File

@ -255,7 +255,7 @@ class ZillionWorld(World):
group_players = group["players"] group_players = group["players"]
start_chars = cast(Dict[int, ZillionStartChar], getattr(multiworld, "start_char")) start_chars = cast(Dict[int, ZillionStartChar], getattr(multiworld, "start_char"))
players_start_chars = [ players_start_chars = [
(player, start_chars[player].get_current_option_name()) (player, start_chars[player].current_option_name)
for player in group_players for player in group_players
] ]
start_char_counts = Counter(sc for _, sc in players_start_chars) start_char_counts = Counter(sc for _, sc in players_start_chars)