From 2db9630a9a98650a4ff8c6990a75a77b28cea831 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Wed, 1 Jan 2025 20:31:56 -0800 Subject: [PATCH 01/19] Allow items & locations to specify yaml options --- src/Helpers.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index cf268ae1..86353b20 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -62,7 +62,8 @@ def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) return hook_result category_data = category_table.get(category_name, {}) - return resolve_yaml_option(multiworld, player, category_data) + resolve_option = resolve_yaml_option(multiworld, player, category_data) + return resolve_option or resolve_option is None def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: if "yaml_option" in data: @@ -75,7 +76,8 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool option_name = format_to_valid_identifier(option_name) if is_option_enabled(multiworld, player, option_name) != required: return False - return True + return True + return None def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> bool: """Check if an item named 'item_name' has been disabled by a yaml option.""" @@ -86,12 +88,17 @@ def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> return is_item_enabled(multiworld, player, item) def is_item_enabled(multiworld: MultiWorld, player: int, item: "ManualItem") -> bool: + from .Data import item_table """Check if an item has been disabled by a yaml option.""" hook_result = before_is_item_enabled(multiworld, player, item) if hook_result is not None: return hook_result - - return _is_manualobject_enabled(multiworld, player, item) + + try_resolve = resolve_yaml_option(multiworld, player, item_table.get(item.name, {})) + if try_resolve is None: + return _is_manualobject_enabled(multiworld, player, item) + else: + return try_resolve def is_location_name_enabled(multiworld: MultiWorld, player: int, location_name: str) -> bool: """Check if a location named 'location_name' has been disabled by a yaml option.""" @@ -102,24 +109,28 @@ def is_location_name_enabled(multiworld: MultiWorld, player: int, location_name: return is_location_enabled(multiworld, player, location) def is_location_enabled(multiworld: MultiWorld, player: int, location: "ManualLocation") -> bool: + from .Data import location_table """Check if a location has been disabled by a yaml option.""" hook_result = before_is_location_enabled(multiworld, player, location) if hook_result is not None: return hook_result - - return _is_manualobject_enabled(multiworld, player, location) + + try_resolve = resolve_yaml_option(multiworld, player, location_table.get(location.name, {})) + if try_resolve is None: + return _is_manualobject_enabled(multiworld, player, location) + else: + return try_resolve def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: any) -> bool: """Internal method: Check if a Manual Object has any category disabled by a yaml option. \nPlease use the proper is_'item/location'_enabled or is_'item/location'_name_enabled methods instead. """ - enabled = True for category in object.get("category", []): - if not is_category_enabled(multiworld, player, category): - enabled = False - break - - return enabled + resolve = is_category_enabled(multiworld, player, category) + if not resolve and resolve is not None: + return False + + return True def get_items_for_player(multiworld: MultiWorld, player: int, includePrecollected: bool = False) -> List[Item]: """Return list of items of a player including placed items""" From 49da261d0894c8e00661378c84ecb71fc2ce87c2 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Wed, 1 Jan 2025 21:02:04 -0800 Subject: [PATCH 02/19] Implement different option types --- src/Helpers.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 86353b20..2405a60d 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -68,13 +68,26 @@ def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: if "yaml_option" in data: for option_name in data["yaml_option"]: - required = True + eval_f = lambda x: x.value + if "<" in option_name: + option_name, target = option_name.split("<") + eval_f = lambda x: x < int(target) + elif ">" in option_name: + option_name, target = option_name.split(">") + eval_f = lambda x: x > int(target) + elif "=" in option_name: + option_name, target = option_name.split("=") + eval_f = lambda x: x == int(target) + elif ":" in option_name: + option_name, target = option_name.split(":") + eval_f = lambda x: x == target if option_name.startswith("!"): option_name = option_name[1:] - required = False - + eval_f = lambda x: not eval_f(x) + option_name = format_to_valid_identifier(option_name) - if is_option_enabled(multiworld, player, option_name) != required: + option = getattr(multiworld.worlds[player].options, option_name, None) + if not eval_f(option): return False return True return None From 3f183a1adf28c74ef708ec7a7635ee1b049d69a6 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Wed, 1 Jan 2025 21:12:55 -0800 Subject: [PATCH 03/19] Fix option management --- src/Helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 2405a60d..c335c484 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -71,16 +71,16 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool eval_f = lambda x: x.value if "<" in option_name: option_name, target = option_name.split("<") - eval_f = lambda x: x < int(target) + eval_f = lambda x: x.value < int(target) elif ">" in option_name: option_name, target = option_name.split(">") - eval_f = lambda x: x > int(target) + eval_f = lambda x: x.value > int(target) elif "=" in option_name: option_name, target = option_name.split("=") - eval_f = lambda x: x == int(target) + eval_f = lambda x: x.value == int(target) elif ":" in option_name: option_name, target = option_name.split(":") - eval_f = lambda x: x == target + eval_f = lambda x: x.current_key == target if option_name.startswith("!"): option_name = option_name[1:] eval_f = lambda x: not eval_f(x) From a41877963e5a271f43da7a726e78bf58c603eba9 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Wed, 1 Jan 2025 21:29:05 -0800 Subject: [PATCH 04/19] Limit options made automatically --- src/Options.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Options.py b/src/Options.py index 836cc5a5..3d38fce2 100644 --- a/src/Options.py +++ b/src/Options.py @@ -203,6 +203,9 @@ def addOptionToGroup(option_name: str, group: str): for category in category_table: for option_name in category_table[category].get("yaml_option", []): + for c in "><=:": # Range and Choice options must be defined using Options.json + if c in option_name: + continue if option_name[0] == "!": option_name = option_name[1:] option_name = format_to_valid_identifier(option_name) @@ -214,6 +217,9 @@ def addOptionToGroup(option_name: str, group: str): for starting_items in starting_items: if starting_items.get("yaml_option"): for option_name in starting_items["yaml_option"]: + for c in "><=:": # Range and Choice options must be defined using Options.json + if c in option_name: + continue if option_name[0] == "!": option_name = option_name[1:] option_name = format_to_valid_identifier(option_name) From 9f7d0ec83abd3c653bfa10a60269f92cfd7606cb Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Wed, 1 Jan 2025 21:37:05 -0800 Subject: [PATCH 05/19] Thanks nico --- src/Helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helpers.py b/src/Helpers.py index c335c484..65018157 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -140,7 +140,7 @@ def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: any) - """ for category in object.get("category", []): resolve = is_category_enabled(multiworld, player, category) - if not resolve and resolve is not None: + if resolve == False: return False return True From 43e4b1fe32251a7924433f26605187c074ed2a8f Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 20:50:46 -0800 Subject: [PATCH 06/19] Incorporated feedback Normalized ":" equality for all option types Removed imports from data and now reference multiworld/world data directly --- src/Helpers.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 65018157..eea80733 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -55,39 +55,40 @@ def clamp(value, min, max): return value def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) -> bool: - from .Data import category_table """Check if a category has been disabled by a yaml option.""" hook_result = before_is_category_enabled(multiworld, player, category_name) if hook_result is not None: return hook_result - category_data = category_table.get(category_name, {}) + category_data = multiworld.worlds[player].category_table.get(category_name, {}) resolve_option = resolve_yaml_option(multiworld, player, category_data) return resolve_option or resolve_option is None def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: if "yaml_option" in data: for option_name in data["yaml_option"]: - eval_f = lambda x: x.value + eval_f = lambda x, t: x.value + target = 1 if "<" in option_name: option_name, target = option_name.split("<") - eval_f = lambda x: x.value < int(target) + eval_f = lambda x, t: x.value < t elif ">" in option_name: option_name, target = option_name.split(">") - eval_f = lambda x: x.value > int(target) + eval_f = lambda x, t: x.value > t elif "=" in option_name: - option_name, target = option_name.split("=") - eval_f = lambda x: x.value == int(target) - elif ":" in option_name: option_name, target = option_name.split(":") - eval_f = lambda x: x.current_key == target + eval_f = lambda x, t: x.value == t if option_name.startswith("!"): option_name = option_name[1:] - eval_f = lambda x: not eval_f(x) + eval_f = lambda x, t: not eval_f(x, t) option_name = format_to_valid_identifier(option_name) option = getattr(multiworld.worlds[player].options, option_name, None) - if not eval_f(option): + try: + t_eval = int(target) + except ValueError: + t_eval = option.options[target] + if not eval_f(option, t_eval): return False return True return None @@ -101,13 +102,12 @@ def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> return is_item_enabled(multiworld, player, item) def is_item_enabled(multiworld: MultiWorld, player: int, item: "ManualItem") -> bool: - from .Data import item_table """Check if an item has been disabled by a yaml option.""" hook_result = before_is_item_enabled(multiworld, player, item) if hook_result is not None: return hook_result - try_resolve = resolve_yaml_option(multiworld, player, item_table.get(item.name, {})) + try_resolve = resolve_yaml_option(multiworld, player, multiworld.worlds[player].item_table.get(item.name, {})) if try_resolve is None: return _is_manualobject_enabled(multiworld, player, item) else: @@ -122,13 +122,12 @@ def is_location_name_enabled(multiworld: MultiWorld, player: int, location_name: return is_location_enabled(multiworld, player, location) def is_location_enabled(multiworld: MultiWorld, player: int, location: "ManualLocation") -> bool: - from .Data import location_table """Check if a location has been disabled by a yaml option.""" hook_result = before_is_location_enabled(multiworld, player, location) if hook_result is not None: return hook_result - try_resolve = resolve_yaml_option(multiworld, player, location_table.get(location.name, {})) + try_resolve = resolve_yaml_option(multiworld, player, multiworld.worlds[player].location_table.get(location.name, {})) if try_resolve is None: return _is_manualobject_enabled(multiworld, player, location) else: From e5f27a8f6489ee0fc4211cb6f0a2a4c122c60c87 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 20:58:33 -0800 Subject: [PATCH 07/19] Fixed options to throw an error if not defined --- src/Options.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Options.py b/src/Options.py index 3d38fce2..9725519a 100644 --- a/src/Options.py +++ b/src/Options.py @@ -203,9 +203,15 @@ def addOptionToGroup(option_name: str, group: str): for category in category_table: for option_name in category_table[category].get("yaml_option", []): - for c in "><=:": # Range and Choice options must be defined using Options.json + skip = False + for c in "><:": # Range and Choice options must be defined using Options.json if c in option_name: - continue + skip = True + option_base = option_name.split(c)[0].strip("!") + if option_base not in manual_options: + raise Exception(f"Option {option_base} was referenced in Category.json, but Range and Choice type options must be defined in Options.json first") + if skip: + continue if option_name[0] == "!": option_name = option_name[1:] option_name = format_to_valid_identifier(option_name) @@ -217,9 +223,15 @@ def addOptionToGroup(option_name: str, group: str): for starting_items in starting_items: if starting_items.get("yaml_option"): for option_name in starting_items["yaml_option"]: - for c in "><=:": # Range and Choice options must be defined using Options.json + skip = False + for c in "><:": # Range and Choice options must be defined using Options.json if c in option_name: - continue + skip = True + option_base = option_name.split(c)[0].strip("!") + if option_base not in manual_options: + raise Exception(f"Option {option_base} was referenced in starting items, but Range and Choice type options must be defined in Options.json first") + if skip: + continue if option_name[0] == "!": option_name = option_name[1:] option_name = format_to_valid_identifier(option_name) From 3b1145f70df133da52bd2d7842834a52d7f81dff Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 21:07:54 -0800 Subject: [PATCH 08/19] Update documentation --- schemas/Manual.items.schema.json | 9 +++++++++ schemas/Manual.locations.schema.json | 9 +++++++++ src/data/categories.json | 2 +- src/data/items.json | 15 +++++++++------ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/schemas/Manual.items.schema.json b/schemas/Manual.items.schema.json index a4cee1df..94bab3a6 100644 --- a/schemas/Manual.items.schema.json +++ b/schemas/Manual.items.schema.json @@ -99,6 +99,15 @@ "description": "(Optional) Skips the item ID forward to the given value.\nThis can be used to provide buffer space for future items.", "type": "integer" }, + "yaml_option": { + "description": "(Optional) Array of Options that will decide if this item is enabled", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, "_comment": {"$ref": "#/definitions/comment"} }, "required": ["name"] diff --git a/schemas/Manual.locations.schema.json b/schemas/Manual.locations.schema.json index 012d007e..d7e59c75 100644 --- a/schemas/Manual.locations.schema.json +++ b/schemas/Manual.locations.schema.json @@ -100,6 +100,15 @@ "description": "(Optional) Skips the item ID forward to the given value.\nThis can be used to provide buffer space for future items.", "type": "integer" }, + "yaml_option": { + "description": "(Optional) Array of Options that will decide if this location is enabled", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, "_comment": {"$ref": "#/definitions/comment"} } }, diff --git a/src/data/categories.json b/src/data/categories.json index bb0dbf14..cbbadf70 100644 --- a/src/data/categories.json +++ b/src/data/categories.json @@ -4,6 +4,6 @@ "hidden": true }, "Example Yaml-option category": { - "yaml_option": ["DLC_enabled"] + "yaml_option": ["DLC_enabled", "Example_Choice:test", "Example_Range<5"] } } diff --git a/src/data/items.json b/src/data/items.json index 57e5bafc..8f4063e6 100644 --- a/src/data/items.json +++ b/src/data/items.json @@ -26,7 +26,7 @@ "Left Side" ], "value": {"coins": 7}, - "progression": true + "progression": true }, { "name": "Firebrand", @@ -42,7 +42,8 @@ "Characters", "Left Side" ], - "progression": true + "progression": true, + "yaml_option": ["Example_Toggle"] }, { "name": "Phoenix Wright", @@ -50,7 +51,8 @@ "Characters", "Left Side" ], - "progression": true + "progression": true, + "yaml_option": ["Example_Choice:test"] }, { "name": "Nova", @@ -58,7 +60,8 @@ "Characters", "Right Side" ], - "progression": true + "progression": true, + "yaml_option": ["Example_Range<5"] }, { "name": "Ghost Rider", @@ -404,6 +407,6 @@ "Right Side" ], "progression": true - } ] - + } + ] } \ No newline at end of file From bacb3f9db5c6efd44376896dc5be2fe7bb78b256 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 21:46:42 -0800 Subject: [PATCH 09/19] Some changes to make the default game generate properly --- src/data/categories.json | 2 +- src/data/items.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/categories.json b/src/data/categories.json index cbbadf70..bb0dbf14 100644 --- a/src/data/categories.json +++ b/src/data/categories.json @@ -4,6 +4,6 @@ "hidden": true }, "Example Yaml-option category": { - "yaml_option": ["DLC_enabled", "Example_Choice:test", "Example_Range<5"] + "yaml_option": ["DLC_enabled"] } } diff --git a/src/data/items.json b/src/data/items.json index 22cf5645..2bb14562 100644 --- a/src/data/items.json +++ b/src/data/items.json @@ -43,7 +43,7 @@ "Left Side" ], "progression": true, - "yaml_option": ["Example_Toggle"] + "yaml_option": ["!Example_Toggle"] }, { "name": "Phoenix Wright", @@ -52,7 +52,7 @@ "Left Side" ], "progression": true, - "yaml_option": ["Example_Choice:test"] + "yaml_option": ["Example_Choice:start"] }, { "name": "Nova", From 563a782edb15dec5467a91d42d96e89c93eebbb7 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 21:47:56 -0800 Subject: [PATCH 10/19] Fix some helper function issues is_location_enabled and is_item_enabled function input types were wrong and confusing Split lambdas into two stages to prevent infinite recursion --- src/Helpers.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index eea80733..32487713 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -2,6 +2,7 @@ import os import pkgutil import json +from copy import deepcopy from BaseClasses import MultiWorld, Item from typing import Optional, List, TYPE_CHECKING, Union, get_args, get_origin @@ -67,28 +68,31 @@ def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: if "yaml_option" in data: for option_name in data["yaml_option"]: - eval_f = lambda x, t: x.value + eval_1 = lambda x, t: x.value target = 1 if "<" in option_name: option_name, target = option_name.split("<") - eval_f = lambda x, t: x.value < t + eval_1 = lambda x, t: x.value < t elif ">" in option_name: option_name, target = option_name.split(">") - eval_f = lambda x, t: x.value > t - elif "=" in option_name: + eval_1 = lambda x, t: x.value > t + elif ":" in option_name: option_name, target = option_name.split(":") - eval_f = lambda x, t: x.value == t + eval_1 = lambda x, t: x.value == t if option_name.startswith("!"): option_name = option_name[1:] - eval_f = lambda x, t: not eval_f(x, t) + eval_2 = lambda x, t: not eval_1(x, t) + else: + eval_2 = eval_1 option_name = format_to_valid_identifier(option_name) option = getattr(multiworld.worlds[player].options, option_name, None) + print(option_name, option) try: - t_eval = int(target) + target_eval = int(target) except ValueError: - t_eval = option.options[target] - if not eval_f(option, t_eval): + target_eval = option.options[target] + if not eval_2(option, target_eval): return False return True return None @@ -101,13 +105,13 @@ def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> return is_item_enabled(multiworld, player, item) -def is_item_enabled(multiworld: MultiWorld, player: int, item: "ManualItem") -> bool: +def is_item_enabled(multiworld: MultiWorld, player: int, item: dict) -> bool: """Check if an item has been disabled by a yaml option.""" hook_result = before_is_item_enabled(multiworld, player, item) if hook_result is not None: return hook_result - try_resolve = resolve_yaml_option(multiworld, player, multiworld.worlds[player].item_table.get(item.name, {})) + try_resolve = resolve_yaml_option(multiworld, player, item) if try_resolve is None: return _is_manualobject_enabled(multiworld, player, item) else: @@ -121,13 +125,13 @@ def is_location_name_enabled(multiworld: MultiWorld, player: int, location_name: return is_location_enabled(multiworld, player, location) -def is_location_enabled(multiworld: MultiWorld, player: int, location: "ManualLocation") -> bool: +def is_location_enabled(multiworld: MultiWorld, player: int, location: dict) -> bool: """Check if a location has been disabled by a yaml option.""" hook_result = before_is_location_enabled(multiworld, player, location) if hook_result is not None: return hook_result - try_resolve = resolve_yaml_option(multiworld, player, multiworld.worlds[player].location_table.get(location.name, {})) + try_resolve = resolve_yaml_option(multiworld, player, location) if try_resolve is None: return _is_manualobject_enabled(multiworld, player, location) else: From a9372a6e7e7e38573fd92992c8a2eaba71574f78 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sat, 18 Jan 2025 21:48:42 -0800 Subject: [PATCH 11/19] Forgot to get rid of a print --- src/Helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Helpers.py b/src/Helpers.py index 32487713..2f3dd2fe 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -87,7 +87,6 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool option_name = format_to_valid_identifier(option_name) option = getattr(multiworld.worlds[player].options, option_name, None) - print(option_name, option) try: target_eval = int(target) except ValueError: From 5bb3551e7eda32735d5c501d0fb00503189fd8a3 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sun, 19 Jan 2025 08:51:23 -0800 Subject: [PATCH 12/19] Swapped formatting to `=` instead of `:` --- src/Helpers.py | 4 ++-- src/Options.py | 4 ++-- src/data/items.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 2f3dd2fe..37165645 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -76,8 +76,8 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool elif ">" in option_name: option_name, target = option_name.split(">") eval_1 = lambda x, t: x.value > t - elif ":" in option_name: - option_name, target = option_name.split(":") + elif "=" in option_name: + option_name, target = option_name.split("=") eval_1 = lambda x, t: x.value == t if option_name.startswith("!"): option_name = option_name[1:] diff --git a/src/Options.py b/src/Options.py index 9725519a..5e326c3b 100644 --- a/src/Options.py +++ b/src/Options.py @@ -204,7 +204,7 @@ def addOptionToGroup(option_name: str, group: str): for category in category_table: for option_name in category_table[category].get("yaml_option", []): skip = False - for c in "><:": # Range and Choice options must be defined using Options.json + for c in "><=": # Range and Choice options must be defined using Options.json if c in option_name: skip = True option_base = option_name.split(c)[0].strip("!") @@ -224,7 +224,7 @@ def addOptionToGroup(option_name: str, group: str): if starting_items.get("yaml_option"): for option_name in starting_items["yaml_option"]: skip = False - for c in "><:": # Range and Choice options must be defined using Options.json + for c in "><=": # Range and Choice options must be defined using Options.json if c in option_name: skip = True option_base = option_name.split(c)[0].strip("!") diff --git a/src/data/items.json b/src/data/items.json index 2bb14562..8fcf0f16 100644 --- a/src/data/items.json +++ b/src/data/items.json @@ -52,7 +52,7 @@ "Left Side" ], "progression": true, - "yaml_option": ["Example_Choice:start"] + "yaml_option": ["Example_Choice=start"] }, { "name": "Nova", From 4437028a5e46e7dda60856a967ea76a2dbba6182 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Sun, 19 Jan 2025 08:53:05 -0800 Subject: [PATCH 13/19] Add support for `<=` and `>=` --- src/Helpers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Helpers.py b/src/Helpers.py index 37165645..090a5e77 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -70,7 +70,13 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool for option_name in data["yaml_option"]: eval_1 = lambda x, t: x.value target = 1 - if "<" in option_name: + if "<=" in option_name: + option_name, target = option_name.split("<=") + eval_1 = lambda x, t: x.value <= t + elif ">=" in option_name: + option_name, target = option_name.split(">=") + eval_1 = lambda x, t: x.value >= t + elif "<" in option_name: option_name, target = option_name.split("<") eval_1 = lambda x, t: x.value < t elif ">" in option_name: From 54233ad86148dc24f8c98044b3e695caea456215 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Mon, 20 Jan 2025 20:44:08 -0800 Subject: [PATCH 14/19] Add support for `!=` --- src/Helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Helpers.py b/src/Helpers.py index 090a5e77..668f0493 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -76,6 +76,9 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool elif ">=" in option_name: option_name, target = option_name.split(">=") eval_1 = lambda x, t: x.value >= t + elif "!=" in option_name: + option_name, target = option_name.split("!=") + eval_1 = lambda x, t: x.value != t elif "<" in option_name: option_name, target = option_name.split("<") eval_1 = lambda x, t: x.value < t From 84492041b0576319239359ec1f058ea1c08b4ac9 Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Fri, 4 Apr 2025 11:23:18 -0700 Subject: [PATCH 15/19] Fixes Fixed hook type hints Added `==` as valid option to match #130 syntax --- src/Helpers.py | 3 +++ src/hooks/Helpers.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 668f0493..af2f164d 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -79,6 +79,9 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool elif "!=" in option_name: option_name, target = option_name.split("!=") eval_1 = lambda x, t: x.value != t + elif "==" in option_name: + option_name, target = option_name.split("==") + eval_1 = lambda x, t: x.value == t elif "<" in option_name: option_name, target = option_name.split("<") eval_1 = lambda x, t: x.value < t diff --git a/src/hooks/Helpers.py b/src/hooks/Helpers.py index 5826fd83..540791e3 100644 --- a/src/hooks/Helpers.py +++ b/src/hooks/Helpers.py @@ -12,10 +12,10 @@ def before_is_category_enabled(multiworld: MultiWorld, player: int, category_nam # Use this if you want to override the default behavior of is_option_enabled # Return True to enable the item, False to disable it, or None to use the default behavior -def before_is_item_enabled(multiworld: MultiWorld, player: int, item: "ManualItem") -> Optional[bool]: +def before_is_item_enabled(multiworld: MultiWorld, player: int, item: dict) -> Optional[bool]: return None # Use this if you want to override the default behavior of is_option_enabled # Return True to enable the location, False to disable it, or None to use the default behavior -def before_is_location_enabled(multiworld: MultiWorld, player: int, location: "ManualLocation") -> Optional[bool]: +def before_is_location_enabled(multiworld: MultiWorld, player: int, location: dict) -> Optional[bool]: return None From d7ccc93e9da9578aa48bb7d9524355ae2625fa4b Mon Sep 17 00:00:00 2001 From: Sam Zimmerman Date: Fri, 4 Apr 2025 11:29:05 -0700 Subject: [PATCH 16/19] Added examples for locations --- src/data/locations.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/data/locations.json b/src/data/locations.json index c10e9903..1f472732 100644 --- a/src/data/locations.json +++ b/src/data/locations.json @@ -41,17 +41,20 @@ { "name": "Beat the Game - Strider Hiryu", "category": ["Unlocked Teams", "Left Side"], - "requires": "|Strider Hiryu|" + "requires": "|Strider Hiryu|", + "yaml_option": ["!Example_Toggle"] }, { "name": "Beat the Game - Phoenix Wright", "category": ["Unlocked Teams", "Left Side"], - "requires": "|Phoenix Wright|" + "requires": "|Phoenix Wright|", + "yaml_option": ["Example_Choice=start"] }, { "name": "Beat the Game - Nova", "category": ["Unlocked Teams", "Right Side"], - "requires": "|Nova|" + "requires": "|Nova|", + "yaml_option": ["Example_Range<5"] }, { "name": "Beat the Game - Ghost Rider", From 41e1b39496fba8aff5b5094668fe616fbf20bb32 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:57:01 -0500 Subject: [PATCH 17/19] moved both is_*_enabled resolve_yaml_option calls to _is_manualobject_enabled --- src/Helpers.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 858d7408..425e4a4c 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -119,12 +119,8 @@ def is_item_enabled(multiworld: MultiWorld, player: int, item: dict[str, Any]) - hook_result = before_is_item_enabled(multiworld, player, item) if hook_result is not None: return hook_result - - try_resolve = resolve_yaml_option(multiworld, player, item) - if try_resolve is None: - return _is_manualobject_enabled(multiworld, player, item) - else: - return try_resolve + + return _is_manualobject_enabled(multiworld, player, item) def is_location_name_enabled(multiworld: MultiWorld, player: int, location_name: str) -> bool: """Check if a location named 'location_name' has been disabled by a yaml option.""" @@ -139,12 +135,8 @@ def is_location_enabled(multiworld: MultiWorld, player: int, location: dict[str, hook_result = before_is_location_enabled(multiworld, player, location) if hook_result is not None: return hook_result - - try_resolve = resolve_yaml_option(multiworld, player, location) - if try_resolve is None: - return _is_manualobject_enabled(multiworld, player, location) - else: - return try_resolve + + return _is_manualobject_enabled(multiworld, player, location) def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: dict[str, Any]) -> bool: """Internal method: Check if a Manual Object has any category disabled by a yaml option. @@ -154,7 +146,12 @@ def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: dict[s resolve = is_category_enabled(multiworld, player, category) if resolve == False: return False - + + try_resolve = resolve_yaml_option(multiworld, player, object) + + if try_resolve is not None: + return try_resolve + return True def get_items_for_player(multiworld: MultiWorld, player: int, includePrecollected: bool = False) -> List[Item]: From c43748ca2ae597695e548c0e1f4719054eca81ae Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:08:22 -0500 Subject: [PATCH 18/19] added None/null check for devs that might call resolve_yaml_option directly --- src/Helpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index 425e4a4c..c2074729 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -5,6 +5,7 @@ import re from BaseClasses import MultiWorld, Item, ItemClassification +from Options import Option from copy import deepcopy from enum import IntEnum from typing import Optional, List, Union, get_args, get_origin, Any @@ -63,11 +64,11 @@ def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) resolve_option = resolve_yaml_option(multiworld, player, category_data) return resolve_option or resolve_option is None -def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: +def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool| None: if "yaml_option" in data: for option_name in data["yaml_option"]: eval_1 = lambda x, t: x.value - target = 1 + target = "1" if "<=" in option_name: option_name, target = option_name.split("<=") eval_1 = lambda x, t: x.value <= t @@ -94,9 +95,11 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool eval_2 = lambda x, t: not eval_1(x, t) else: eval_2 = eval_1 - + option_name = format_to_valid_identifier(option_name) - option = getattr(multiworld.worlds[player].options, option_name, None) + option: Option | None = getattr(multiworld.worlds[player].options, option_name, None) + if option is None: + raise ValueError(f"option {option_name} is myspelt or invalid") try: target_eval = int(target) except ValueError: From d4f89af681d36ed82ecfa31660d4a4308267ba19 Mon Sep 17 00:00:00 2001 From: nicopop <6759630+nicopop@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:43:52 -0500 Subject: [PATCH 19/19] Revert resolve_yaml_option to only returning a bool --- src/Helpers.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Helpers.py b/src/Helpers.py index c2074729..41a6b0c3 100644 --- a/src/Helpers.py +++ b/src/Helpers.py @@ -6,7 +6,6 @@ from BaseClasses import MultiWorld, Item, ItemClassification from Options import Option -from copy import deepcopy from enum import IntEnum from typing import Optional, List, Union, get_args, get_origin, Any from types import GenericAlias @@ -61,10 +60,9 @@ def is_category_enabled(multiworld: MultiWorld, player: int, category_name: str) return hook_result category_data = multiworld.worlds[player].category_table.get(category_name, {}) - resolve_option = resolve_yaml_option(multiworld, player, category_data) - return resolve_option or resolve_option is None + return resolve_yaml_option(multiworld, player, category_data) -def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool| None: +def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool: if "yaml_option" in data: for option_name in data["yaml_option"]: eval_1 = lambda x, t: x.value @@ -107,7 +105,7 @@ def resolve_yaml_option(multiworld: MultiWorld, player: int, data: dict) -> bool if not eval_2(option, target_eval): return False return True - return None + return True def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> bool: """Check if an item named 'item_name' has been disabled by a yaml option.""" @@ -150,10 +148,8 @@ def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: dict[s if resolve == False: return False - try_resolve = resolve_yaml_option(multiworld, player, object) - - if try_resolve is not None: - return try_resolve + if object.get("yaml_option") and not resolve_yaml_option(multiworld, player, object): + return False return True