From af277a3c9391674fd00d8ad10d369c8fd1a23b17 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:29:32 +0200 Subject: [PATCH 01/18] CounterOption --- Options.py | 13 +++++----- Utils.py | 3 +++ WebHostLib/options.py | 4 +-- .../templates/playerOptions/macros.html | 2 +- .../playerOptions/playerOptions.html | 8 +++--- .../templates/weightedOptions/macros.html | 2 +- .../weightedOptions/weightedOptions.html | 4 +-- worlds/witness/options.py | 26 +++++++++++-------- 8 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Options.py b/Options.py index d040828509d1..37f9b7942fe0 100644 --- a/Options.py +++ b/Options.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import collections import functools import logging import math @@ -855,13 +856,13 @@ def __len__(self) -> int: return self.value.__len__() -class ItemDict(OptionDict): - verify_item_name = True - +class CounterOption(OptionDict): 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) + super(CounterOption, self).__init__(collections.Counter(value)) + + +class ItemDict(CounterOption): + verify_item_name = True class OptionList(Option[typing.List[typing.Any]], VerifyKeys): diff --git a/Utils.py b/Utils.py index f89330cf7c65..d3d910072061 100644 --- a/Utils.py +++ b/Utils.py @@ -419,6 +419,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def find_class(self, module: str, name: str) -> type: if module == "builtins" and name in safe_builtins: return getattr(builtins, name) + # used by CounterOption + if module == "collections" and name == "Counter": + return collections.Counter # used by MultiServer -> savegame/multidata if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}: return getattr(self.net_utils_module, name) diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 15b7bd61ceee..e91d0f3f707c 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -108,7 +108,7 @@ def option_presets(game: str) -> Response: f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}." presets[preset_name][preset_option_name] = option.value - elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.ItemDict)): + elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.CounterOption)): presets[preset_name][preset_option_name] = option.value elif isinstance(preset_option, str): # Ensure the option value is valid for Choice and Toggle options @@ -216,7 +216,7 @@ def generate_yaml(game: str): for key, val in options.copy().items(): key_parts = key.rsplit("||", 2) - # Detect and build ItemDict options from their name pattern + # Detect and build CounterOption options from their name pattern if key_parts[-1] == "qty": if key_parts[0] not in options: options[key_parts[0]] = {} diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index 30a4fc78dff3..f035b74ceb6c 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -111,7 +111,7 @@ {% endmacro %} -{% macro ItemDict(option_name, option) %} +{% macro CounterOption(option_name, option) %} {{ OptionTitle(option_name, option) }}
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index 73de5d56eb20..3b1da1b2e762 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -93,8 +93,8 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} - {{ inputs.ItemDict(option_name, option) }} + {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} + {{ inputs.CounterOption(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} @@ -133,8 +133,8 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} - {{ inputs.ItemDict(option_name, option) }} + {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} + {{ inputs.CounterOption(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html index a1d319697154..0d31433f3106 100644 --- a/WebHostLib/templates/weightedOptions/macros.html +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -113,7 +113,7 @@ {{ TextChoice(option_name, option) }} {% endmacro %} -{% macro ItemDict(option_name, option, world) %} +{% macro CounterOption(option_name, option, world) %}
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
diff --git a/WebHostLib/templates/weightedOptions/weightedOptions.html b/WebHostLib/templates/weightedOptions/weightedOptions.html index b3aefd483535..d96595ebccf1 100644 --- a/WebHostLib/templates/weightedOptions/weightedOptions.html +++ b/WebHostLib/templates/weightedOptions/weightedOptions.html @@ -83,8 +83,8 @@

{{ option.display_name|default(option_name) }}

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} - {{ inputs.ItemDict(option_name, option, world) }} + {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} + {{ inputs.CounterOption(option_name, option, world) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 4855fc715933..6513e9cc30a9 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,8 +1,9 @@ from dataclasses import dataclass -from schema import And, Schema +from schema import And, Schema, Optional -from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility +from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility, \ + CounterOption from .data import static_logic as static_witness_logic from .data.item_definition_classes import ItemCategory, WeightedItemDefinition @@ -234,23 +235,26 @@ class TrapPercentage(Range): default = 20 -class TrapWeights(OptionDict): +_default_trap_weights = { + trap_name: item_definition.weight + for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() + if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP +} + + +class TrapWeights(CounterOption): """ Specify the weights determining how many copies of each trap item will be in your itempool. If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option. """ + valid_keys = _default_trap_weights.keys() + display_name = "Trap Weights" schema = Schema({ - trap_name: And(int, lambda n: n >= 0) - for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() - if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP + Optional(trap_name): And(int, lambda n: n >= 0) for trap_name in _default_trap_weights }) - default = { - trap_name: item_definition.weight - for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() - if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP - } + default = _default_trap_weights class PuzzleSkipAmount(Range): From b67fe486899b1f15e783847d1508a4bb07dfc9cf Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:33:04 +0200 Subject: [PATCH 02/18] bring back the negative exception for ItemDict --- Options.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Options.py b/Options.py index 37f9b7942fe0..e06a429a4aaa 100644 --- a/Options.py +++ b/Options.py @@ -864,6 +864,12 @@ def __init__(self, value: typing.Dict[str, int]): class ItemDict(CounterOption): verify_item_name = True + def __init__(self, value: typing.Dict[str, int]): + if any(item_count < 0 for item_count in value.values()): + raise Exception("Cannot have negative item counts.") + + super(ItemDict, self).__init__(value) + class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # Supports duplicate entries and ordering. From 271142c50a46c2023dee5886f1685da85f9bf932 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:40:54 +0200 Subject: [PATCH 03/18] Backwards compatibility --- Options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Options.py b/Options.py index e06a429a4aaa..23eb1b44fc31 100644 --- a/Options.py +++ b/Options.py @@ -868,6 +868,9 @@ def __init__(self, value: typing.Dict[str, int]): if any(item_count < 0 for item_count in value.values()): raise Exception("Cannot have negative item counts.") + # Backwards compatibility: Cull 0s + value = {item_name: amount for item_name, amount in value.items() if amount != 0} + super(ItemDict, self).__init__(value) From 28e99b753d5ef9be2c08caa2b22bb52331a66440 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:44:19 +0200 Subject: [PATCH 04/18] ruff on witness --- worlds/witness/options.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 6513e9cc30a9..70d265e9c5ee 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,9 +1,17 @@ from dataclasses import dataclass -from schema import And, Schema, Optional - -from Options import Choice, DefaultOnToggle, OptionDict, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility, \ - CounterOption +from schema import And, Optional, Schema + +from Options import ( + Choice, + CounterOption, + DefaultOnToggle, + OptionGroup, + PerGameCommonOptions, + Range, + Toggle, + Visibility, +) from .data import static_logic as static_witness_logic from .data.item_definition_classes import ItemCategory, WeightedItemDefinition From 2ad0f62f1a5f1275f91c6f25a767b178fb4f828c Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:59:25 +0200 Subject: [PATCH 05/18] fix in calls --- Options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Options.py b/Options.py index 23eb1b44fc31..d21b39ae20c4 100644 --- a/Options.py +++ b/Options.py @@ -873,6 +873,9 @@ def __init__(self, value: typing.Dict[str, int]): super(ItemDict, self).__init__(value) + def __contains__(self, item): + return item in self.value + class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # Supports duplicate entries and ordering. From c9f17230ef53fffb9af86dd3c54dab818978d16d Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:02:49 +0200 Subject: [PATCH 06/18] move the contains --- Options.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Options.py b/Options.py index d21b39ae20c4..bd3d141357e2 100644 --- a/Options.py +++ b/Options.py @@ -855,6 +855,10 @@ def __iter__(self) -> typing.Iterator[str]: def __len__(self) -> int: return self.value.__len__() + # __getitem__ fallback fails for Counters, so we define this explicitly + def __contains__(self, item): + return item in self.value + class CounterOption(OptionDict): def __init__(self, value: typing.Dict[str, int]): @@ -873,9 +877,6 @@ def __init__(self, value: typing.Dict[str, int]): super(ItemDict, self).__init__(value) - def __contains__(self, item): - return item in self.value - class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # Supports duplicate entries and ordering. From 51e5226a2f0a0887e0ecf7f085b260d48c9f88bb Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:04:50 +0200 Subject: [PATCH 07/18] comment --- Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Options.py b/Options.py index bd3d141357e2..575994981c42 100644 --- a/Options.py +++ b/Options.py @@ -872,7 +872,7 @@ def __init__(self, value: typing.Dict[str, int]): if any(item_count < 0 for item_count in value.values()): raise Exception("Cannot have negative item counts.") - # Backwards compatibility: Cull 0s + # Backwards compatibility: Cull 0s to make "in" checks behave the same way as before value = {item_name: amount for item_name, amount in value.items() if amount != 0} super(ItemDict, self).__init__(value) From 34c99310a2a457b7aba62585db8144d1a4864feb Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:08:40 +0200 Subject: [PATCH 08/18] comment --- Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Options.py b/Options.py index 575994981c42..6d1b13d781c9 100644 --- a/Options.py +++ b/Options.py @@ -872,7 +872,7 @@ def __init__(self, value: typing.Dict[str, int]): if any(item_count < 0 for item_count in value.values()): raise Exception("Cannot have negative item counts.") - # Backwards compatibility: Cull 0s to make "in" checks behave the same way as before + # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a CounterOption value = {item_name: amount for item_name, amount in value.items() if amount != 0} super(ItemDict, self).__init__(value) From 45474ad0e0957336233f5801556fbd9f2e7f3f42 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:49:33 +0200 Subject: [PATCH 09/18] Add option min and max values for CounterOption --- Options.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Options.py b/Options.py index 6d1b13d781c9..b846675a2ea1 100644 --- a/Options.py +++ b/Options.py @@ -861,17 +861,42 @@ def __contains__(self, item): class CounterOption(OptionDict): + min: typing.Optional[int] = None + max: typing.Optional[int] = None + def __init__(self, value: typing.Dict[str, int]): super(CounterOption, self).__init__(collections.Counter(value)) + def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None: + super(CounterOption, self).verify(world, player_name, plando_options) + + range_errors = [] + + if self.max is not None: + range_errors += [ + f"\"{key}: {value}\" is higher than maximum allowed value {self.max}." + for key, value in self.value.items() if value > self.max + ] + + if self.min is not None: + range_errors += [ + f"\"{key}: {value}\" is lower than minimum allowed value {self.min}." + for key, value in self.value.items() if value < self.min + ] + + if len(range_errors) == 1: + raise OptionError(range_errors[0][:-1] + f" for option {getattr(self, 'display_name', self)}.") + elif range_errors: + range_errors = [f"For option {getattr(self, 'display_name', self)}:"] + range_errors + raise OptionError("\n".join(range_errors)) + class ItemDict(CounterOption): verify_item_name = True - def __init__(self, value: typing.Dict[str, int]): - if any(item_count < 0 for item_count in value.values()): - raise Exception("Cannot have negative item counts.") + min = 0 + def __init__(self, value: typing.Dict[str, int]): # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a CounterOption value = {item_name: amount for item_name, amount in value.items() if amount != 0} From 6a4b27483582e211b1757e94e68de4a66bef82ac Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:50:10 +0200 Subject: [PATCH 10/18] Use min 0 for TrapWeights --- worlds/witness/options.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 70d265e9c5ee..bb19da0d6b52 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -256,12 +256,11 @@ class TrapWeights(CounterOption): If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option. """ + display_name = "Trap Weights" valid_keys = _default_trap_weights.keys() - display_name = "Trap Weights" - schema = Schema({ - Optional(trap_name): And(int, lambda n: n >= 0) for trap_name in _default_trap_weights - }) + min = 0 + default = _default_trap_weights From f4b18cd651faca61c8f8a5dea991899269d0904a Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:50:47 +0200 Subject: [PATCH 11/18] This is safe now --- worlds/witness/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index bb19da0d6b52..3f88148d5168 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -253,7 +253,7 @@ class TrapPercentage(Range): class TrapWeights(CounterOption): """ Specify the weights determining how many copies of each trap item will be in your itempool. - If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). + If you don't want a specific type of trap, you can set the weight for it to 0. If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option. """ display_name = "Trap Weights" From ba5ffe949419f45d3da426878b8406b6e684c1da Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:51:02 +0200 Subject: [PATCH 12/18] ruff --- worlds/witness/options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 3f88148d5168..2566ea68ad5e 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,7 +1,5 @@ from dataclasses import dataclass -from schema import And, Optional, Schema - from Options import ( Choice, CounterOption, From a3228ee8de31513a5209686b2605b7dd88768a16 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 10 Aug 2024 14:51:58 +0200 Subject: [PATCH 13/18] This fits on one line again now --- worlds/witness/options.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 2566ea68ad5e..3d867acc1b97 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,15 +1,6 @@ from dataclasses import dataclass -from Options import ( - Choice, - CounterOption, - DefaultOnToggle, - OptionGroup, - PerGameCommonOptions, - Range, - Toggle, - Visibility, -) +from Options import Choice, CounterOption, DefaultOnToggle, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility from .data import static_logic as static_witness_logic from .data.item_definition_classes import ItemCategory, WeightedItemDefinition From 5c2e8bc3527383b8ab4c01ffa5adbd4f76c61685 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 16 Aug 2024 04:10:19 +0200 Subject: [PATCH 14/18] OptionCounter --- Options.py | 10 +++++----- Utils.py | 2 +- WebHostLib/options.py | 4 ++-- WebHostLib/templates/playerOptions/macros.html | 2 +- WebHostLib/templates/playerOptions/playerOptions.html | 8 ++++---- WebHostLib/templates/weightedOptions/macros.html | 2 +- .../templates/weightedOptions/weightedOptions.html | 4 ++-- worlds/witness/options.py | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Options.py b/Options.py index b846675a2ea1..0887ba6b85b8 100644 --- a/Options.py +++ b/Options.py @@ -860,15 +860,15 @@ def __contains__(self, item): return item in self.value -class CounterOption(OptionDict): +class OptionCounter(OptionDict): min: typing.Optional[int] = None max: typing.Optional[int] = None def __init__(self, value: typing.Dict[str, int]): - super(CounterOption, self).__init__(collections.Counter(value)) + super(OptionCounter, self).__init__(collections.Counter(value)) def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None: - super(CounterOption, self).verify(world, player_name, plando_options) + super(OptionCounter, self).verify(world, player_name, plando_options) range_errors = [] @@ -891,13 +891,13 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: Pl raise OptionError("\n".join(range_errors)) -class ItemDict(CounterOption): +class ItemDict(OptionCounter): verify_item_name = True min = 0 def __init__(self, value: typing.Dict[str, int]): - # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a CounterOption + # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter value = {item_name: amount for item_name, amount in value.items() if amount != 0} super(ItemDict, self).__init__(value) diff --git a/Utils.py b/Utils.py index d3d910072061..e599e98eb5e5 100644 --- a/Utils.py +++ b/Utils.py @@ -419,7 +419,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def find_class(self, module: str, name: str) -> type: if module == "builtins" and name in safe_builtins: return getattr(builtins, name) - # used by CounterOption + # used by OptionCounter if module == "collections" and name == "Counter": return collections.Counter # used by MultiServer -> savegame/multidata diff --git a/WebHostLib/options.py b/WebHostLib/options.py index e91d0f3f707c..630f0210c381 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -108,7 +108,7 @@ def option_presets(game: str) -> Response: f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}." presets[preset_name][preset_option_name] = option.value - elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.CounterOption)): + elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.OptionCounter)): presets[preset_name][preset_option_name] = option.value elif isinstance(preset_option, str): # Ensure the option value is valid for Choice and Toggle options @@ -216,7 +216,7 @@ def generate_yaml(game: str): for key, val in options.copy().items(): key_parts = key.rsplit("||", 2) - # Detect and build CounterOption options from their name pattern + # Detect and build OptionCounter options from their name pattern if key_parts[-1] == "qty": if key_parts[0] not in options: options[key_parts[0]] = {} diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index f035b74ceb6c..38a3602422cc 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -111,7 +111,7 @@
{% endmacro %} -{% macro CounterOption(option_name, option) %} +{% macro OptionCounter(option_name, option) %} {{ OptionTitle(option_name, option) }}
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index 3b1da1b2e762..85467b91f046 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -93,8 +93,8 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} - {{ inputs.CounterOption(option_name, option) }} + {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} @@ -133,8 +133,8 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} - {{ inputs.CounterOption(option_name, option) }} + {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html index 0d31433f3106..018474099693 100644 --- a/WebHostLib/templates/weightedOptions/macros.html +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -113,7 +113,7 @@ {{ TextChoice(option_name, option) }} {% endmacro %} -{% macro CounterOption(option_name, option, world) %} +{% macro OptionCounter(option_name, option, world) %}
{% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %}
diff --git a/WebHostLib/templates/weightedOptions/weightedOptions.html b/WebHostLib/templates/weightedOptions/weightedOptions.html index d96595ebccf1..327823e19c34 100644 --- a/WebHostLib/templates/weightedOptions/weightedOptions.html +++ b/WebHostLib/templates/weightedOptions/weightedOptions.html @@ -83,8 +83,8 @@

{{ option.display_name|default(option_name) }}

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.CounterOption) and option.valid_keys %} - {{ inputs.CounterOption(option_name, option, world) }} + {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {{ inputs.OptionCounter(option_name, option, world) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 3d867acc1b97..3b8372a1249a 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from Options import Choice, CounterOption, DefaultOnToggle, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility +from Options import Choice, OptionCounter, DefaultOnToggle, OptionGroup, PerGameCommonOptions, Range, Toggle, Visibility from .data import static_logic as static_witness_logic from .data.item_definition_classes import ItemCategory, WeightedItemDefinition @@ -239,7 +239,7 @@ class TrapPercentage(Range): } -class TrapWeights(CounterOption): +class TrapWeights(OptionCounter): """ Specify the weights determining how many copies of each trap item will be in your itempool. If you don't want a specific type of trap, you can set the weight for it to 0. From 67448def86a9dcc3cf926b221bb20ea559424774 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 27 Nov 2024 04:38:37 +0100 Subject: [PATCH 15/18] Update Options.py --- Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Options.py b/Options.py index 0887ba6b85b8..15a8dadd2820 100644 --- a/Options.py +++ b/Options.py @@ -856,7 +856,7 @@ def __len__(self) -> int: return self.value.__len__() # __getitem__ fallback fails for Counters, so we define this explicitly - def __contains__(self, item): + def __contains__(self, item) -> bool: return item in self.value From 54ed306814ccd4326d9ea0072df12d6706a0dcd5 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 27 Nov 2024 04:40:48 +0100 Subject: [PATCH 16/18] Couple more typing things --- Options.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Options.py b/Options.py index 15a8dadd2820..c5902931ffa9 100644 --- a/Options.py +++ b/Options.py @@ -861,13 +861,13 @@ def __contains__(self, item) -> bool: class OptionCounter(OptionDict): - min: typing.Optional[int] = None - max: typing.Optional[int] = None + min: int | None = None + max: int | None = None - def __init__(self, value: typing.Dict[str, int]): + def __init__(self, value: dict[str, int]) -> None: super(OptionCounter, self).__init__(collections.Counter(value)) - def verify(self, world: typing.Type[World], player_name: str, plando_options: PlandoOptions) -> None: + def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None: super(OptionCounter, self).verify(world, player_name, plando_options) range_errors = [] @@ -896,7 +896,7 @@ class ItemDict(OptionCounter): min = 0 - def __init__(self, value: typing.Dict[str, int]): + def __init__(self, value: dict[str, int]) -> None: # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter value = {item_name: amount for item_name, amount in value.items() if amount != 0} From 8e2bc08abf085f193cc14571347627672819f0d1 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 7 Dec 2024 08:02:38 +0100 Subject: [PATCH 17/18] Update Options.py --- Options.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Options.py b/Options.py index a692b6f5ea3c..0b0768a171e9 100644 --- a/Options.py +++ b/Options.py @@ -885,9 +885,7 @@ def verify(self, world: type[World], player_name: str, plando_options: PlandoOpt for key, value in self.value.items() if value < self.min ] - if len(range_errors) == 1: - raise OptionError(range_errors[0][:-1] + f" for option {getattr(self, 'display_name', self)}.") - elif range_errors: + if range_errors: range_errors = [f"For option {getattr(self, 'display_name', self)}:"] + range_errors raise OptionError("\n".join(range_errors)) From 7f7def4ad422d43d00f8c64d074ae5f40cabe5fc Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 7 Dec 2024 09:09:18 +0100 Subject: [PATCH 18/18] Make StartInventory work again, also make LocationCounter theoretically work --- WebHostLib/templates/playerOptions/macros.html | 11 ++++++++++- WebHostLib/templates/playerOptions/playerOptions.html | 8 ++++++-- WebHostLib/templates/weightedOptions/macros.html | 11 ++++++++++- .../templates/weightedOptions/weightedOptions.html | 4 +++- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index 9744ed986ec0..8a44f1a2726d 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -112,9 +112,18 @@ {% endmacro %} {% macro OptionCounter(option_name, option) %} + {% set relevant_keys = option.valid_keys %} + {% if not relevant_keys %} + {% if option.verify_item_name %} + {% set relevant_keys = world.item_names %} + {% elif option.verify_location_name %} + {% set relevant_keys = world.location_names %} + {% endif %} + {% endif %} + {{ OptionTitle(option_name, option) }}
- {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} + {% for item_name in relevant_keys|sort %}
diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index ae92fc08aa64..5e82342126f7 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -93,7 +93,9 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {% elif issubclass(option, Options.OptionCounter) and ( + option.valid_keys or option.verify_item_name or option.verify_location_name + ) %} {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} @@ -133,7 +135,9 @@

Player Options

{% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {% elif issubclass(option, Options.OptionCounter) and ( + option.valid_keys or option.verify_item_name or option.verify_location_name + ) %} {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html index a9054cadc8ad..0a6ad46611d6 100644 --- a/WebHostLib/templates/weightedOptions/macros.html +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -114,8 +114,17 @@ {% endmacro %} {% macro OptionCounter(option_name, option, world) %} + {% set relevant_keys = option.valid_keys %} + {% if not relevant_keys %} + {% if option.verify_item_name %} + {% set relevant_keys = world.item_names %} + {% elif option.verify_location_name %} + {% set relevant_keys = world.location_names %} + {% endif %} + {% endif %} +
- {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} + {% for item_name in relevant_keys|sort %}
{{ option.display_name|default(option_name) }} {% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.OptionCounter) and option.valid_keys %} + {% elif issubclass(option, Options.OptionCounter) and ( + option.valid_keys or option.verify_item_name or option.verify_location_name + ) %} {{ inputs.OptionCounter(option_name, option, world) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %}