Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions schemas/Manual.items.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,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
},
"sort-key": {
"description": "(Optional) A string to sort the items by. If not provided, items will always be sorted by name.",
"type": "string"
Expand Down
9 changes: 9 additions & 0 deletions schemas/Manual.locations.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
"sort-key": {
"description": "(Optional) A string to sort the locations by. If not provided, locations will always be sorted by name.",
"type": "string"
Expand Down
54 changes: 44 additions & 10 deletions src/Helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re

from BaseClasses import MultiWorld, Item, ItemClassification
from Options import Option
from enum import IntEnum
from typing import Optional, List, Union, get_args, get_origin, Any
from types import GenericAlias
Expand Down Expand Up @@ -53,26 +54,57 @@ 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, {})
Comment thread
nicopop marked this conversation as resolved.
return resolve_yaml_option(multiworld, player, category_data)

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_1 = lambda x, t: x.value
target = "1"
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:
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:
option_name, target = option_name.split("=")
Comment thread
axxroytovu marked this conversation as resolved.
eval_1 = lambda x, t: x.value == t
if option_name.startswith("!"):
option_name = option_name[1:]
required = False
eval_2 = lambda x, t: not eval_1(x, t)
else:
eval_2 = eval_1

option_name = format_to_valid_identifier(option_name)
if is_option_enabled(multiworld, player, option_name) != required:
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:
target_eval = option.options[target]
if not eval_2(option, target_eval):
return False
return True
return True

def is_item_name_enabled(multiworld: MultiWorld, player: int, item_name: str) -> bool:
Expand Down Expand Up @@ -111,13 +143,15 @@ def _is_manualobject_enabled(multiworld: MultiWorld, player: int, object: dict[s
"""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
resolve = is_category_enabled(multiworld, player, category)
if resolve == False:
return False

return enabled
if object.get("yaml_option") and not resolve_yaml_option(multiworld, player, object):
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"""
Expand Down
18 changes: 18 additions & 0 deletions src/Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@ 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
if c in option_name:
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)
Expand All @@ -214,6 +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"]:
skip = False
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("!")
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)
Expand Down
3 changes: 3 additions & 0 deletions src/data/items.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"Left Side"
],
"progression": true,
"yaml_option": ["!Example_Toggle"],
"sort-key": "row-1-3"
},
{
Expand All @@ -62,6 +63,7 @@
"Left Side"
],
"progression": true,
"yaml_option": ["Example_Choice=start"],
"sort-key": "row-1-4"
},
{
Expand All @@ -71,6 +73,7 @@
"Right Side"
],
"progression": true,
"yaml_option": ["Example_Range<5"],
"sort-key": "row-1-5"
},
{
Expand Down
9 changes: 6 additions & 3 deletions src/data/locations.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,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",
Expand Down