Skip to content
Merged
Changes from all commits
Commits
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
75 changes: 75 additions & 0 deletions src/Rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import re
import math
import inspect
import logging

if TYPE_CHECKING:
from . import ManualWorld
Expand Down Expand Up @@ -94,6 +96,7 @@ def checkRequireStringForArea(state: CollectionState, area: dict):
if not callable(func):
raise ValueError(f"Invalid function `{func_name}` in {area}.")

convert_req_function_args(func, func_args, area["name"])
result = func(world, multiworld, state, player, *func_args)
if isinstance(result, bool):
requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", "1" if result else "0")
Expand Down Expand Up @@ -271,6 +274,78 @@ def allRegionsAccessible(state):
# Victory requirement
multiworld.completion_condition[player] = lambda state: state.has("__Victory__", player)

def convert_req_function_args(func, args: list[str], areaName: str, warn: bool = False):
parameters = inspect.signature(func).parameters
knownArguments = ["world", "multiworld", "state", "player"]
index = 0
for parameter, info in parameters.items():
if parameter in knownArguments:
continue

argType = info.annotation
optional = False
try:
if issubclass(argType, inspect._empty): #if not set then it wont get converted but still be checked for valid data at index
argType = str

except TypeError: # Optional
if argType.__module__ == 'typing' and argType._name == 'Optional':
optional = True
argType = argType.__args__[0]
else:
#Implementing complex typing is not simple so ill skip it for now
index += 1
continue

try:
value = args[index].strip()

except IndexError:
if info is not inspect.Parameter.empty:
value = info.default

else:
raise Exception(f"A call of the {func.__name__} function in '{areaName}'s requirement, asks for a value of type {argType}\nfor its argument '{info.name}' but its missing")

if optional:
if isinstance(value, type(None)):
index += 1
continue
elif isinstance(value, str):
if value.lower() == 'none':
value = None
args[index] = value
index += 1
continue


if not isinstance(value, argType):
if issubclass(argType, bool):
#Special conversion to bool
if value.lower() in ['true', '1']:
value = True

elif value.lower() in ['false', '0']:
value = False

else:
value = bool(value)
if warn:
# warning here spam the console if called from rules.py, might be worth to make it a data validation instead
logging.warn(f"A call of the {func.__name__} function in '{areaName}'s requirement, asks for a value of type {argType}\nfor its argument '{info.name}' but an unknown string was passed and thus converted to {value}")

else:
try:
value = argType(value)

except ValueError:
raise Exception(f"A call of the {func.__name__} function in '{areaName}'s requirement, asks for a value of type {argType}\nfor its argument '{info.name}' but its value '{value}' cannot be converted to {argType}")

args[index] = value

index += 1


def YamlEnabled(world: "ManualWorld", multiworld: MultiWorld, state: CollectionState, player: int, param: str) -> bool:
"""Is a yaml option enabled?"""
return is_option_enabled(multiworld, player, param)
Expand Down