Generate: remove tag "-" (#3036)

* Generate: introduce Remove, similar to Merge

* make + dict behave as + for each value


---------

Co-authored-by: Zach Parks <zach@alliware.com>
This commit is contained in:
Fabian Dill 2024-05-23 15:03:21 +02:00 committed by GitHub
parent 3f8c348a49
commit 860ab10b0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 20 deletions

View File

@ -9,6 +9,7 @@ import urllib.parse
import urllib.request
from collections import Counter
from typing import Any, Dict, Tuple, Union
from itertools import chain
import ModuleUpdate
@ -319,18 +320,34 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
logging.debug(f'Applying {new_weights}')
cleaned_weights = {}
for option in new_weights:
option_name = option.lstrip("+")
option_name = option.lstrip("+-")
if option.startswith("+") and option_name in weights:
cleaned_value = weights[option_name]
new_value = new_weights[option]
if isinstance(new_value, (set, dict)):
if isinstance(new_value, set):
cleaned_value.update(new_value)
elif isinstance(new_value, list):
cleaned_value.extend(new_value)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
else:
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value
elif option.startswith("-") and option_name in weights:
cleaned_value = weights[option_name]
new_value = new_weights[option]
if isinstance(new_value, set):
cleaned_value.difference_update(new_value)
elif isinstance(new_value, list):
for element in new_value:
cleaned_value.remove(element)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) - Counter(new_value))
else:
raise Exception(f"Cannot apply remove to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value
else:
cleaned_weights[option_name] = new_weights[option]
new_options = set(cleaned_weights) - set(weights)
@ -466,9 +483,11 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
world_type = AutoWorldRegister.world_types[ret.game]
game_weights = weights[ret.game]
if any(weight.startswith("+") for weight in game_weights) or \
any(weight.startswith("+") for weight in weights):
raise Exception(f"Merge tag cannot be used outside of trigger contexts.")
for weight in chain(game_weights, weights):
if weight.startswith("+"):
raise Exception(f"Merge tag cannot be used outside of trigger contexts. Found {weight}")
if weight.startswith("-"):
raise Exception(f"Remove tag cannot be used outside of trigger contexts. Found {weight}")
if "triggers" in game_weights:
weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names)

View File

@ -31,7 +31,7 @@ class TestPlayerOptions(unittest.TestCase):
self.assertEqual(new_weights["list_2"], ["string_3"])
self.assertEqual(new_weights["list_1"], ["string", "string_2"])
self.assertEqual(new_weights["dict_1"]["option_a"], 50)
self.assertEqual(new_weights["dict_1"]["option_b"], 0)
self.assertEqual(new_weights["dict_1"]["option_b"], 50)
self.assertEqual(new_weights["dict_1"]["option_c"], 50)
self.assertNotIn("option_f", new_weights["dict_2"])
self.assertEqual(new_weights["dict_2"]["option_g"], 50)

View File

@ -123,10 +123,21 @@ again using the new options `normal`, `pupdunk_hard`, and `pupdunk_mystery`, and
new weights for 150 and 200. This allows for two more triggers that will only be used for the new `pupdunk_hard`
and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery".
Options that define a list, set, or dict can additionally have the character `+` added to the start of their name, which applies the contents of
the activated trigger to the already present equivalents in the game options.
## Adding or Removing from a List, Set, or Dict Option
List, set, and dict options can additionally have values added to or removed from itself without overriding the existing
option value by prefixing the option name in the trigger block with `+` (add) or `-` (remove). The exact behavior for
each will depend on the option type.
- For sets, `+` will add the value(s) to the set and `-` will remove any value(s) of the set. Sets do not allow
duplicates.
- For lists, `+` will add new values(s) to the list and `-` will remove the first matching values(s) it comes across.
Lists allow duplicate values.
- For dicts, `+` will add the value(s) to the given key(s) inside the dict if it exists, or add it otherwise. `-` is the
inverse operation of addition (and negative values are allowed).
For example:
```yaml
Super Metroid:
start_location:
@ -134,18 +145,21 @@ Super Metroid:
aqueduct: 50
start_hints:
- Morph Ball
triggers:
- option_category: Super Metroid
option_name: start_location
option_result: aqueduct
options:
Super Metroid:
+start_hints:
- Gravity Suit
start_inventory:
Power Bombs: 1
triggers:
- option_category: Super Metroid
option_name: start_location
option_result: aqueduct
options:
Super Metroid:
+start_hints:
- Gravity Suit
```
In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be created.
If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph Ball.
In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be
created. If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph
Ball.
Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key will
replace that value within the dict.
Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key
will replace that value within the dict.