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:
		
							parent
							
								
									3f8c348a49
								
							
						
					
					
						commit
						860ab10b0b
					
				
							
								
								
									
										29
									
								
								Generate.py
								
								
								
								
							
							
						
						
									
										29
									
								
								Generate.py
								
								
								
								
							| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue