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.simple_expression() action = l.simple_expression() return (who, action) def execute(self): print(f"{self}") who, action = self func = eval(f"{who}_chibi.{action}") # print(f"Execution: {who} {action}") def lint(self): who, action = self try: chibi = eval(f"{who}_chibi") except Exception: renpy.error(f"Character chibi not defined: {who}") def predict(self): who, action = self chibi = eval(f"{who}_chibi") doll = eval(f"{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 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: return block n -= weight # In the rare event where weights are incorrectly assigned, # return a random available block, and in any other case None. return renpy.random.choice(blocks)[0] if blocks else None