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 import urllib.request
from collections import Counter from collections import Counter
from typing import Any, Dict, Tuple, Union from typing import Any, Dict, Tuple, Union
from itertools import chain
import ModuleUpdate 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}') logging.debug(f'Applying {new_weights}')
cleaned_weights = {} cleaned_weights = {}
for option in new_weights: for option in new_weights:
option_name = option.lstrip("+") option_name = option.lstrip("+-")
if option.startswith("+") and option_name in weights: if option.startswith("+") and option_name in weights:
cleaned_value = weights[option_name] cleaned_value = weights[option_name]
new_value = new_weights[option] new_value = new_weights[option]
if isinstance(new_value, (set, dict)): if isinstance(new_value, set):
cleaned_value.update(new_value) cleaned_value.update(new_value)
elif isinstance(new_value, list): elif isinstance(new_value, list):
cleaned_value.extend(new_value) cleaned_value.extend(new_value)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
else: else:
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name}," raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.") f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value 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: else:
cleaned_weights[option_name] = new_weights[option] cleaned_weights[option_name] = new_weights[option]
new_options = set(cleaned_weights) - set(weights) 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] world_type = AutoWorldRegister.world_types[ret.game]
game_weights = weights[ret.game] game_weights = weights[ret.game]
if any(weight.startswith("+") for weight in game_weights) or \ for weight in chain(game_weights, weights):
any(weight.startswith("+") for weight in weights): if weight.startswith("+"):
raise Exception(f"Merge tag cannot be used outside of trigger contexts.") 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: if "triggers" in game_weights:
weights = roll_triggers(weights, game_weights["triggers"], valid_trigger_names) 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_2"], ["string_3"])
self.assertEqual(new_weights["list_1"], ["string", "string_2"]) 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_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.assertEqual(new_weights["dict_1"]["option_c"], 50)
self.assertNotIn("option_f", new_weights["dict_2"]) self.assertNotIn("option_f", new_weights["dict_2"])
self.assertEqual(new_weights["dict_2"]["option_g"], 50) 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` 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". 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 ## Adding or Removing from a List, Set, or Dict Option
the activated trigger to the already present equivalents in the game options.
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: For example:
```yaml ```yaml
Super Metroid: Super Metroid:
start_location: start_location:
@ -134,18 +145,21 @@ Super Metroid:
aqueduct: 50 aqueduct: 50
start_hints: start_hints:
- Morph Ball - Morph Ball
triggers: start_inventory:
- option_category: Super Metroid Power Bombs: 1
option_name: start_location triggers:
option_result: aqueduct - option_category: Super Metroid
options: option_name: start_location
Super Metroid: option_result: aqueduct
+start_hints: options:
- Gravity Suit 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. In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be
If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph Ball. 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 Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key
replace that value within the dict. will replace that value within the dict.