python early hide: import inspect try: from renpy.lint import python_builtins, renpy_builtins except ImportError: import builtins python_builtins = set(dir(builtins)) del builtins renpy_builtins = set() __register_params = frozenset(inspect.signature(renpy.register_statement).parameters).difference({"name", "parse"}) def register_decorator(cls): """ A class decorator which registers a new statement. The name of the statement will be the class name unless a `name` class attribute is present, which should be a string. The `parse` method should be a static method that returns an object which will be passed to the other methods as `self`. Returning an instance of the class is disabled for now. """ # security def raiser(*args, **kwargs): raise TypeError("Returning an instance of the class is disabled") cls.__init__ = raiser name = getattr(cls, "name", cls.__name__) parse = getattr(cls, "parse", cls) # left in renpy.register_statement(name, parse=parse, **{k:getattr(cls, k) for k in __register_params.intersection(vars(cls))}) return cls @register_decorator class dynamic: block = "possible" @staticmethod def parse(l): rv = {} def parse_simple(ll): target = ll.require(ll.name, "variable name") if target in rv: ll.error(f"Variable {target} already set in the same dynamic block") ll.require("=", "equals sign") expression = ll.simple_expression() ll.expect_eol() rv[target] = expression if l.match(":"): l.expect_block("dynamic block") l.expect_eol() ll = l.subblock_lexer() while ll.advance(): parse_simple(ll) else: parse_simple(l) return rv def execute(self): evaled = {n: eval(e) for n, e in self.items()} renpy.dynamic(**evaled) def lint(self): for domain, st in (("Python", python_builtins), ("Ren'Py", renpy_builtins)): inter = st.intersection(self) if inter: renpy.error(f"Dynamic statement redefines one or several {domain} builtins: {', '.join(map(repr, inter))}") @register_decorator class chibi: @staticmethod def parse(l): who = l.require(l.name) action = l.require(l.name) arguments = l.arguments() # may be None l.expect_eol() return (who, action, arguments) def execute(self): print(self) who, action, arguments = self chibi = DollChibi.instances[who] method = getattr(chibi, action) if arguments is None: args, kwargs = (), {} else: args, kwargs = arguments.evaluate() method(*args, **kwargs) # print(f"Execution: {who} {action}") def lint(self): who, action = self if who not in DollChibi.instances: renpy.error(f"Character chibi not defined: {who}") chibi = DollChibi.instances[who] if not hasattr(chibi, action): renpy.error(f"Chibi action not defined: {who} {action}") def predict(self): who, action = self chibi = DollChibi.instances[who] doll = states.dolls[who] layers = ( l[0] for pose in chibi.poses.keys() for k in doll.states.values() if k[0] and k[2] for l in k[0].get_layers(k[0]._hash, subpath=posixpath.join("chibi", pose)).values() ) return layers @register_decorator class random: block = True predict_all = True @staticmethod def parse(l): l.require(":") l.expect_eol() ll = l.subblock_lexer() blocks = [] while ll.advance(): with ll.catch_error(): weight = 1.0 condition = "True" if ll.keyword("block"): ll.expect_block("block") block = ll.subblock_lexer().renpy_block() if ll.keyword("weight"): weight = float(ll.require(ll.float)) if ll.keyword("if"): ll.expect_block("if block") condition = ll.require(ll.python_expression) else: block = ll.renpy_statement() blocks.append((block, weight, condition)) return {"blocks": blocks} def lint(self): any_true = False for block, weight, condition in self["blocks"]: if not isinstance(weight, (int, float)): renpy.error(f"Weight must be a number, not {weight!r}") if condition == "True": any_true = True else: try: eval(condition) except Exception: renpy.error(f"Condition could not be evaluated: {condition!r}") if not any_true: renpy.error("All blocks have a condition, which will raise an exception if all conditions are False at the same time at runtime") def next(self): blocks = [(block, weight) for block, weight, condition in self["blocks"] if eval(condition)] total_weight = sum(weight for _, weight in blocks) n = renpy.random.random() * total_weight for block, weight in blocks: if n <= weight: break else: n -= weight return block