|  | 
| 8 | 8 | 
 | 
| 9 | 9 | import re | 
| 10 | 10 | import math | 
|  | 11 | +import inspect | 
|  | 12 | +import logging | 
| 11 | 13 | 
 | 
| 12 | 14 | if TYPE_CHECKING: | 
| 13 | 15 |     from . import ManualWorld | 
| @@ -94,6 +96,7 @@ def checkRequireStringForArea(state: CollectionState, area: dict): | 
| 94 | 96 |             if not callable(func): | 
| 95 | 97 |                 raise ValueError(f"Invalid function `{func_name}` in {area}.") | 
| 96 | 98 | 
 | 
|  | 99 | +            convert_req_function_args(func, func_args, area["name"]) | 
| 97 | 100 |             result = func(world, multiworld, state, player, *func_args) | 
| 98 | 101 |             if isinstance(result, bool): | 
| 99 | 102 |                 requires_list = requires_list.replace("{" + func_name + "(" + item[1] + ")}", "1" if result else "0") | 
| @@ -271,6 +274,78 @@ def allRegionsAccessible(state): | 
| 271 | 274 |     # Victory requirement | 
| 272 | 275 |     multiworld.completion_condition[player] = lambda state: state.has("__Victory__", player) | 
| 273 | 276 | 
 | 
|  | 277 | +    def convert_req_function_args(func, args: list[str], areaName: str, warn: bool = False): | 
|  | 278 | +        parameters = inspect.signature(func).parameters | 
|  | 279 | +        knownArguments = ["world", "multiworld", "state", "player"] | 
|  | 280 | +        index = 0 | 
|  | 281 | +        for parameter, info in parameters.items(): | 
|  | 282 | +            if parameter in knownArguments: | 
|  | 283 | +                continue | 
|  | 284 | + | 
|  | 285 | +            argType = info.annotation | 
|  | 286 | +            optional = False | 
|  | 287 | +            try: | 
|  | 288 | +                if issubclass(argType, inspect._empty): #if not set then it wont get converted but still be checked for valid data at index | 
|  | 289 | +                    argType = str | 
|  | 290 | + | 
|  | 291 | +            except TypeError: # Optional | 
|  | 292 | +                if argType.__module__ == 'typing' and argType._name == 'Optional': | 
|  | 293 | +                    optional = True | 
|  | 294 | +                    argType = argType.__args__[0] | 
|  | 295 | +                else: | 
|  | 296 | +                    #Implementing complex typing is not simple so ill skip it for now | 
|  | 297 | +                    index += 1 | 
|  | 298 | +                    continue | 
|  | 299 | + | 
|  | 300 | +            try: | 
|  | 301 | +                value = args[index].strip() | 
|  | 302 | + | 
|  | 303 | +            except IndexError: | 
|  | 304 | +                if info is not inspect.Parameter.empty: | 
|  | 305 | +                    value = info.default | 
|  | 306 | + | 
|  | 307 | +                else: | 
|  | 308 | +                    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") | 
|  | 309 | + | 
|  | 310 | +            if optional: | 
|  | 311 | +                if isinstance(value, type(None)): | 
|  | 312 | +                    index += 1 | 
|  | 313 | +                    continue | 
|  | 314 | +                elif isinstance(value, str): | 
|  | 315 | +                    if value.lower() == 'none': | 
|  | 316 | +                        value = None | 
|  | 317 | +                        args[index] = value | 
|  | 318 | +                        index += 1 | 
|  | 319 | +                        continue | 
|  | 320 | + | 
|  | 321 | + | 
|  | 322 | +            if not isinstance(value, argType): | 
|  | 323 | +                if issubclass(argType, bool): | 
|  | 324 | +                    #Special conversion to bool | 
|  | 325 | +                    if value.lower() in ['true', '1']: | 
|  | 326 | +                        value = True | 
|  | 327 | + | 
|  | 328 | +                    elif value.lower() in ['false', '0']: | 
|  | 329 | +                        value = False | 
|  | 330 | + | 
|  | 331 | +                    else: | 
|  | 332 | +                        value = bool(value) | 
|  | 333 | +                        if warn: | 
|  | 334 | +                        # warning here spam the console if called from rules.py, might be worth to make it a data validation instead | 
|  | 335 | +                            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}") | 
|  | 336 | + | 
|  | 337 | +                else: | 
|  | 338 | +                    try: | 
|  | 339 | +                        value = argType(value) | 
|  | 340 | + | 
|  | 341 | +                    except ValueError: | 
|  | 342 | +                        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}") | 
|  | 343 | + | 
|  | 344 | +                args[index] = value | 
|  | 345 | + | 
|  | 346 | +            index += 1 | 
|  | 347 | + | 
|  | 348 | + | 
| 274 | 349 | def YamlEnabled(world: "ManualWorld", multiworld: MultiWorld, state: CollectionState, player: int, param: str) -> bool: | 
| 275 | 350 |     """Is a yaml option enabled?""" | 
| 276 | 351 |     return is_option_enabled(multiworld, player, param) | 
|  | 
0 commit comments