import logging from typing import Any, Callable, ClassVar, Literal from renpy import config from renpy.atl import RawBlock from renpy.lexer import SubParse from . import translation, util def parse_store_name(name: str) -> str: if not name: return "" if name == "store": return "" return name.lstrip("store.") def align_imspec(imspec): """ Align imspec to a tuple of 7 elements. """ if len(imspec) == 7: return imspec elif len(imspec) == 6: return imspec + (None,) elif len(imspec) == 3: return imspec + (None, None, None, None) else: raise ValueError(f"Invalid imspec length: {len(imspec)}") def get_imspec_name(imspec) -> str: name, expression, tag, at_list, layer, zorder, behind = align_imspec(imspec) if expression: return f"expression {expression}" return " ".join(name) def get_imspec_expr(imspec, **kwargs) -> str: name, expression, tag, at_list, layer, zorder, behind = align_imspec(imspec) rv = [] if expression: rv.append(f"expression {util.get_code(expression, **kwargs)}") else: rv.append(" ".join(name)) if layer: rv.append(f"onlayer {layer}") if at_list: at_list = ", ".join([util.get_code(at, **kwargs) for at in at_list]) rv.append(f"at {at_list}") if tag: rv.append(f"as {util.get_code(tag, **kwargs)}") if zorder: rv.append(f"zorder {util.get_code(zorder, **kwargs)}") if behind: behind = ", ".join([util.get_code(b, **kwargs) for b in behind]) rv.append(f"behind {behind}") return " ".join(rv) class Node(object): filename: str linenumber: int next: "Node | None" """ Node that unconditionally follows this one in the abstract syntax tree, or None if this node is the last one in the block. """ translatable: ClassVar[bool] = False """ True if this node is translatable, False otherwise. (This can be set on the class or the instance.) """ translation_relevant: ClassVar[bool] = False @property def name(self) -> str | tuple[Any, ...] | None: """ The name property stores and retreives the name for the node. This is one of: * A string, when the node is a label. * A tuple, in (filename, version, serial) format. This is stored efficently, as it makes up most nodes in Ren'Py. * Longer tuples, like (filename, version, serial, ...) are rare, but used. * None, when the name is not known. """ if self._name: return self._name elif self.name_version: return (self.filename, self.name_version, self.name_serial) else: return None @name.setter def name(self, value: str | tuple[Any, ...] | None): if hasattr(self, "filename"): match value: case (self.filename, int(version), int(serial)): self._name = None self.name_version = version self.name_serial = serial return self._name = value def __init__(self, loc: tuple[str, int] = ("", 0)): """ Initializes this Node object. `loc` A (filename, physical line number) tuple giving the logical line on which this Node node starts. """ self.filename = loc[0] self.linenumber = loc[1] self.name = None self.next = None def get_translation_strings(self) -> list[tuple[int, str]]: """ Return a possibly empty list of linenumber, string pairs of strings that are additional translation strings for this node. """ return [] def get_children(self, f: Callable[["Node"], Any]) -> None: """ Calls `f` with this node and its children. """ f(self) class ParameterInfo(object): def get_code(self, **kwargs) -> str: """ >>> ParameterInfo([("a", 1), ("b", 2)]).get_code() '(a=1, b=2)' >>> ParameterInfo().get_code() '()' """ param_str = [] positional = list(filter(lambda i: i[0] in self.positional, self.parameters)) for parameter in positional: if parameter[1] is not None: param_str.append(f"{parameter[0]}={parameter[1]}") else: param_str.append(f"{parameter[0]}") if self.extrapos: param_str.append("*%s" % self.extrapos) nameonly = list(filter(lambda i: i not in positional, self.parameters)) if nameonly: if not self.extrapos: param_str.append("*") for parameter in nameonly: if parameter[1] is not None: param_str.append(f"{parameter[0]}={parameter[1]}") else: param_str.append(f"{parameter[0]}") if self.extrakw: param_str.append(f"**{self.extrakw}") param = ", ".join(param_str) return f"({param})" class ArgumentInfo(object): def get_code(self, **kwargs): args = [] for key, val in self.arguments: if key is not None: args.append(f"{key}={val}") else: args.append(val) if getattr(self, "starred_indexes", None): raise NotImplementedError if getattr(self, "doublestarred_indexes", None): raise NotImplementedError if getattr(self, "extrakw", None): raise NotImplementedError extrapos = getattr(self, "extrapos", None) if extrapos: args.append(f"*{extrapos}") return "(" + ", ".join(args) + ")" class PyExpr(str): """ Represents a string containing python code. """ __slots__ = [ "filename", "linenumber", "py", "hashcode", "column", ] def __new__(cls, s, filename=None, linenumber=0, py=3, hashcode=None, column=0): self = str.__new__(cls, s) self.filename = filename # type: ignore self.linenumber = linenumber # type: ignore self.py = py # type: ignore self.hashcode = hashcode # type: ignore self.column = column # type: ignore return self def __getnewargs__(self): return (str(self), self.filename, self.linenumber, self.py, self.hashcode, self.column) def get_code(self, **kwargs) -> str: return str(self) class PyCode(object): def __setstate__(self, state): if isinstance(state, dict): self.__dict__.update(state) else: self.state = state def get_code(self, **kwargs) -> str: source = getattr(self, "source", None) if source: return source return util.get_code(self.state[1]) class Scry(object): pass class Say(Node): """ https://www.renpy.org/doc/html/dialogue.html#say-statement Grammar:: [WHO [ATTRIBUTES] [@TEMPORARY_ATTRIBUTES]] WHAT [nointeract] [id IDENTIFIER] [with TRANSITION] Examples:: "Hello, world." e "Hello, world." e happy "Hello, world." with dissolve e "Hello, world." id greet_001 """ def get_code(self, dialogue_filter=None, **kwargs): rv = [] who = util.attr(self, "who") if who: rv.append(util.get_code(who, **kwargs)) attributes = util.attr(self, "attributes") if attributes is not None: rv.extend(attributes) temporary_attributes = util.attr(self, "temporary_attributes") if temporary_attributes: rv.append("@") rv.extend(temporary_attributes) what = util.attr(self, "what") if dialogue_filter is not None: what = dialogue_filter(what) rv.append(translation.encode_say_string(what)) interact = util.attr(self, "interact") if not interact: rv.append("nointeract") identifier = util.attr(self, "identifier") explicit_identifier = util.attr(self, "explicit_identifier") if identifier and explicit_identifier: rv.append("id") rv.append(util.get_code(identifier, **kwargs)) arguments = util.attr(self, "arguments") if arguments: rv.append(util.get_code(arguments, **kwargs)) # This has to be at the end. with_ = util.attr(self, "with_") if with_: rv.append("with") rv.append(util.get_code(with_, **kwargs)) return " ".join(rv) class Init(Node): """ https://www.renpy.org/doc/html/python.html#init-python-statement init python: https://www.renpy.org/doc/html/lifecycle.html#init-offset-statement init offset = 42 """ block: list[Node] priority: int def get_code(self, **kwargs) -> str: if len(self.block) == 1: next = self.block[0] if isinstance(next, Python) or isinstance(next, EarlyPython): return f"init {util.get_code(next, **kwargs)}" if self.priority == -500 and isinstance(next, Screen): return util.get_code(self.block, **kwargs) if self.priority == 500 and isinstance(next, Image): return util.get_code(next, **kwargs) if ( isinstance(next, Define) or isinstance(next, Default) or isinstance(next, Transform) ): return util.get_code(next, **kwargs) if self.priority == 0 and all( isinstance(item, TranslateString) for item in self.block ): return util.get_code(self.block, **kwargs) start = "init" if self.priority: start += f" {self.priority}" if len(self.block) == 0: raise NotImplementedError inner_code = util.get_code(self.block, **kwargs) if inner_code.count("\n") == 0: return f"{start} {inner_code}" rv = [start + ":"] rv.append(util.indent(inner_code)) return "\n".join(rv) def get_children(self, f): f(self) for i in self.block: i.get_children(f) class Label(Node): """ https://www.renpy.org/doc/html/label.html Grammar:: label NAME [PARAMETERS] [hide] : BLOCK Examples:: label sample1: "Here is 'sample1' label." label sample2(a="default"): "Here is 'sample2' label." "a = [a]" label hidden_scope hide: $ x = 1 """ translation_relevant = True block: list[Node] parameters: ParameterInfo | None = None hide: bool = False def get_name(self): """ Get the name of the label. """ return util.attr(self, "name") or util.attr(self, "_name") def get_code(self, **kwargs) -> str: start = "label" name = self.get_name() if name: start += f" {name}" parameters = util.attr(self, "parameters") if parameters: start += f"{util.get_code(parameters, **kwargs)}" hide = util.attr(self, "hide") if hide: start += " hide" block = util.attr(self, "block") if not block: block = Pass() return util.label_code(start, block, **kwargs) def get_children(self, f): f(self) for i in self.block: i.get_children(f) class Python(Node): """ https://www.renpy.org/doc/html/python.html#python-statements Grammar:: python [hide] [in STORE] : BLOCK $ PYTHON_CODE Examples:: python: flag = True python hide: secret = "not in global namespace" python in mystore: myvar = 1 $ flag = True """ def get_code(self, **kwargs) -> str: inner_code = util.get_code(self.code, **kwargs) store = parse_store_name(util.attr(self, "store")) hide = util.attr(self, "hide") # For single-line Python statements # without store or hide attributes, use the $ shorthand if not store and not hide and "\n" not in inner_code: return f"$ {inner_code}" start = "python" if hide: start += " hide" if store: start += f" in {store}" rv = [start + ":"] rv.append(util.indent(f"{inner_code}")) return "\n".join(rv) class EarlyPython(Node): """ https://www.renpy.org/doc/html/python.html#python-statements Like ``python``, but runs before all other ``init`` blocks. Grammar:: python early [hide] [in STORE] : BLOCK $ PYTHON_CODE Examples:: python early: flag = True python early hide: secret = "not in global namespace" $ flag = True """ def get_code(self, **kwargs) -> str: inner_code = util.get_code(self.code, **kwargs) storename = parse_store_name(util.attr(self, "store")) hide = util.attr(self, "hide") if not storename and not hide and len(inner_code.split("\n")) == 1: return f"$ {inner_code}" start = "python early" if hide: start += " hide" if storename: start += f" in {storename}" return util.label_code(start, util.attr(self, "code"), **kwargs) class Image(Node): """ https://www.renpy.org/doc/html/displayables.html#images Grammar:: image NAME = EXPRESSION image NAME : atl_block Examples:: image logo = "logo.png" image logo = Image("logo.png", xalign=1.0) """ def get_code(self, **kwargs) -> str: start = "image" if self.imgname: start += f" {' '.join(self.imgname)}" code = util.attr(self, "code") if code: return f"{start} = {util.get_code(code,**kwargs)}" return util.label_code(start, util.attr(self, "atl"), **kwargs) class Transform(Node): """ https://www.renpy.org/doc/html/transforms.html#transform-statement atl_transform ::= "transform" qualname ( "(" parameters ")" )? ":" atl_block """ def get_code(self, **kwargs) -> str: """ transform notif_t: alpha 0 ease .2 alpha 1 pause 2.6 ease .2 alpha 0 yzoom 0 """ start = "transform" varname = util.attr(self, "varname") if varname: start += f" {varname}" parameters = util.attr(self, "parameters") if parameters: start += f"{util.get_code(parameters, **kwargs)}" return util.label_code(start, util.attr(self, "atl"), **kwargs) class Show(Node): """ https://www.renpy.org/doc/html/displaying_images.html#show-statement Grammar:: show (NAME | expression EXPR) [onlayer LAYER] [at TRANSFORMS] [as TAG] [zorder ZORDER] [behind TAGS] [with TRANSITION] show NAME : atl_block Examples:: show eileen happy show eileen happy at left with dissolve show expression "eileen_happy.png" as eileen """ def get_code(self, **kwargs) -> str: start = "show" name = get_imspec_expr(self.imspec) if name: start += f" {name}" with_expr = kwargs.get("with_expr", None) if with_expr: start += f" with {util.get_code(with_expr, **kwargs)}" return util.label_code(start, util.attr(self, "atl"), **kwargs) class ShowLayer(Node): """ https://www.renpy.org/doc/html/displaying_images.html#camera-and-show-layer-statements show layer at a,b,c : atl """ def get_code(self, **kwargs) -> str: start = "show layer" layer = util.attr(self, "layer") if layer: start += f" {layer}" if not layer: start += " master" logging.warning( "ShowLayer not have a layer name: %s:%s", self.filename, self.linenumber ) at_list = util.attr(self, "at_list") if at_list: at_list = ", ".join([util.get_code(at, **kwargs) for at in at_list]) start += f" at {at_list}" return util.label_code(start, util.attr(self, "atl"), **kwargs) class Scene(Node): """ https://www.renpy.org/doc/html/displaying_images.html#scene-statement example: scene bg room [with dissolve] """ def get_code(self, **kwargs) -> str: start = "scene" imspec = util.attr(self, "imspec") layer = util.attr(self, "layer") if imspec: start += f" {get_imspec_expr(imspec, **kwargs)}" elif layer: start += f" onlayer {layer}" with_expr = kwargs.get("with_expr", None) if with_expr: start += f" with {util.get_code(with_expr, **kwargs)}" return util.label_code(start, util.attr(self, "atl"), **kwargs) class Hide(Node): """ https://www.renpy.org/doc/html/displaying_images.html#hide-statement Grammar:: hide (NAME | expression EXPR) [onlayer LAYER] [with TRANSITION] Examples:: hide eileen hide eileen with dissolve """ def get_code(self, **kwargs) -> str: start = "hide" name = get_imspec_expr(self.imspec) if name: start += f" {name}" with_expr = kwargs.get("with_expr", None) if with_expr: start += f" with {util.get_code(with_expr, **kwargs)}" return start class With(Node): """ https://www.renpy.org/doc/html/displaying_images.html#with-statement Grammar:: with TRANSITION Examples:: with dissolve with fade """ def get_code(self, **kwargs) -> str: paired = util.attr(self, "paired") if paired: raise Exception("With paired not covered") expr = util.attr(self, "expr") if isinstance(expr, str): return f"with {expr}" return f"with {util.get_code(expr, **kwargs)}" class Call(Node): """ https://www.renpy.org/doc/html/label.html#call-statement call subroutine call subroutine(2) call expression "sub" + "routine" pass (count=3) call [expression SIMPLE_EXPRESSION] [pass] [ARGUMENTS] [from LABEL] call LABEL [ARGUMENTS] """ def get_code(self, **kwargs) -> str: start = "call" expression = util.attr(self, "expression") if expression: if expression == True: start += " expression" else: start += f" expression {util.get_code(expression,**kwargs)}" label = util.attr(self, "label") if label: start += f" {label}" arguments = util.attr(self, "arguments") if arguments: start += f"{util.get_code(arguments,**kwargs)}" from_label = kwargs.get("from_label", None) if from_label: start += f" from {from_label.get_name()}" return start class Return(Node): """ https://www.renpy.org/doc/html/label.html#return-statement Grammar:: return [EXPRESSION] Examples:: return return "result" """ def __new__(cls, *args, **kwargs): self = Node.__new__(cls) self.expression = None return self def get_code(self, **kwargs) -> str: expression = util.attr(self, "expression") if expression: return f"return {util.get_code(expression, **kwargs)}" else: return "return" class Menu(Node): """ https://www.renpy.org/doc/html/menus.html#in-game-menus """ translation_relevant = True items: list[tuple[str, str, list[Node] | None]] statement_start: Node # type: ignore set: str | None = None with_: str | None = None has_caption: bool = False arguments: ArgumentInfo | None = None item_arguments: list[ArgumentInfo | None] | None = None rollback: str = "force" # type: ignore def _is_caption(item): label, condition, block = item return condition == "True" and block is None def get_code(self, **kwargs) -> str: """ default menuset = set() menu chapter_1_places: set menuset "Where should I go?" "Dallas, TX" (150, sale=True): jump dallas "Go to class." if True: jump go_to_class "Go to the bar.": jump go_to_bar "Go to jail.": jump go_to_jail """ start = "menu" label = util.attr(kwargs, "menu_label") if label: if isinstance(label, Label): start += f" {label.get_name()}" else: start += f" {label}" arguments = util.attr(self, "arguments") if arguments: start += f" {util.get_code(arguments,**kwargs)}" rv = [start + ":"] with_ = util.attr(self, "with_") if with_: rv.append(util.indent(f"with {with_}")) attrset = util.attr(self, "set") if attrset: rv.append(util.indent(f"set {attrset}")) say = util.attr(kwargs, "menu_say") if say: rv.append(util.indent(util.get_code(say, **kwargs))) items = util.attr(self, "items") item_arguments = util.attr(self, "item_arguments") for idx, (say, cond, expr) in enumerate(items): sel = translation.encode_say_string(say) if item_arguments: argument = item_arguments[idx] # argument may None if argument: sel += f"{util.get_code(argument,**kwargs)}" if cond and cond != "True": sel += f" if {util.get_code(cond,**kwargs)}" # a empty expr means a caption rv.append(util.indent(util.label_code(sel, expr, **kwargs))) if len(rv) == 1: rv.append("pass") # if no items, add a pass statement return "\n".join(rv) def get_translation_strings(self): rv = super().get_translation_strings() for caption, _, block in self.items: if config.old_substitutions: caption = caption.replace("%%", "%") if caption is None: continue # Empty lines after the caption will strill make # this caption to be repoprted on wrong line, # but it is still better than line number of the menu itself # which can be hundreds of lines away. if block: loc = block[0].linenumber - 1 else: loc = self.linenumber rv.append((loc, caption)) return rv def get_children(self, f): f(self) for _label, _condition, block in self.items: if block: for i in block: i.get_children(f) class Jump(Node): """ https://www.renpy.org/doc/html/label.html#jump-statement """ def get_code(self, **kwargs) -> str: rv = "jump" expression = util.attr(self, "expression") if expression is True: rv += " expression" elif expression: # maybe backward compatibility? rv += f" {util.get_code(expression, **kwargs)}" target = util.attr(self, "target") if target: rv += f" {target}" return rv class Pass(Node): """ pass """ def get_code(self, **kwargs) -> str: # return "" # TODO: check if this is correct # may be return "pass" or "" at cases ? return "pass" class While(Node): """ https://www.renpy.org/doc/html/conditional.html#while-statement while lines: # evaluates to True as long as the list is not empty play sound lines.pop(0) # removes the first element pause while True: "This is the song that never terminates." "It goes on and on, my compatriots." """ condition: str block: list[Node] def get_code(self, **kwargs) -> str: start = f"while {self.condition}" return util.label_code(start, util.attr(self, "block"), **kwargs) def get_children(self, f): f(self) for i in self.block: i.get_children(f) class If(Node): """ https://www.renpy.org/doc/html/conditional.html#if-statement Grammar:: if CONDITION : BLOCK [elif CONDITION : BLOCK] [else : BLOCK] Examples:: if flag: "Flag is set." elif other: "Other is set." else: "Nothing is set." """ def get_code(self, **kwargs) -> str: rv = [] entries = util.attr(self, "entries") for index, (cond, body) in enumerate(entries): if cond is None: logging.warning( "Unexpected None condition in if statement at %s:%s", self.filename, self.linenumber, ) block = util.indent(util.get_code(body, **kwargs)) if index == 0: rv.append(f"if {cond}:\n{block}") continue if index == len(entries) - 1: rv.append(f"else:\n{block}") continue rv.append(f"elif {cond}:\n{block}") return "\n".join(rv) def get_children(self, f): f(self) for _condition, block in self.entries: for i in block: i.get_children(f) class UserStatement(Node): line: str parsed: Any block: list[Any] = [] translatable: bool = False # type: ignore code_block: list[Node] | None = None translation_relevant: bool = False # type: ignore rollback: Literal["normal", "never", "force"] = "normal" atl: "RawBlock | None" = None subparses: list["SubParse"] = [] init_priority: int | None = None init_offset: int | None = None def __new__(cls, *args, **kwargs): self = Node.__new__(cls) self.block = [] self.code_block = None self.translatable = False self.translation_relevant = False self.rollback = "normal" self.subparses = [] return self def get_code(self, **kwargs) -> str: start = self.line rv = [start] if self.block: rv.append(util.indent(util.get_block_code(self.block, **kwargs))) return "\n".join(rv) def get_translation_strings(self) -> list[tuple[int, str]]: rv = super().get_translation_strings() if strings := self.call("translation_strings"): for i in strings: if not isinstance(i, tuple): i = (self.linenumber, i) rv.append(i) return rv def get_children(self, f): f(self) if self.code_block is not None: for i in self.code_block: i.get_children(f) for i in self.subparses: for j in i.block: j.get_children(f) class PostUserStatement(Node): """Post-execution node for user-defined statements (Ren'Py 8.x+). Appears after a UserStatement to handle post-execution logic. Carries a reference back to the parent statement. """ parent: Node | None = None def get_code(self, **kwargs) -> str: return "" class StoreNamespace(object): pass class Define(Node): """ https://www.renpy.org/doc/html/python.html#define-statement Grammar:: define [PRIORITY] [STORE.]NAME (= | |= | += ) EXPRESSION Examples:: define e = Character("Eileen") define -2 gui.accent_color = '#ffdd1e' define config.tag_layer |= { "eileen": "master" } """ def get_code(self, **kwargs) -> str: start = "define" priority = util.attr(self, "priority") if priority: start += f" {priority}" store_name = parse_store_name(util.attr(self, "store")) varname = util.attr(self, "varname") operator = "=" if getattr(self, "operator", None): operator = self.operator if store_name: varname = f"{store_name}.{varname}" return f"{start} {varname} {operator} {util.get_code(self.code,**kwargs)}" # All the default statements, in the order they were registered. default_statements = [] class Default(Node): """ https://www.renpy.org/doc/html/python.html#default-statement Grammar:: default [STORE.]NAME = EXPRESSION Examples:: default points = 0 default mystore.flag = False """ def get_code(self, **kwargs) -> str: # trim store or store. prefix st = parse_store_name(util.attr(self, "store")) varname = util.attr(self, "varname") if st: varname = f"{st}.{varname}" return f"default {varname} = {util.get_code(self.code,**kwargs)}" class Screen(Node): """ https://www.renpy.org/doc/html/screens.html#screen-language Grammar:: screen NAME [PARAMETERS] [KEYWORDS]: SCREEN_LANGUAGE_BLOCK """ def get_code(self, **kwargs) -> str: return util.get_code(self.screen, **kwargs) class Translate(Node): """ translate arabic python: gui.REGULAR_FONT = "DejaVuSans.ttf" gui.LIGHT_FONT = "DejaVuSans.ttf" gui.FONT_SCALE = .9 gui.REGULAR_BOLD = True """ rollback = "never" translation_relevant = True identifier: str alternate: str | None language: str | None block: list[Node] after: Node | None def __init__(self, loc, identifier, language, block, alternate=None): super(Translate, self).__init__(loc) self.identifier = identifier self.alternate = alternate self.language = language self.block = block def get_code(self, **kwargs) -> str: language = util.attr(self, "language") identifier = util.attr(self, "identifier") alternate = util.attr(self, "alternate") if not language and not identifier: return "" start = f"translate {language} {identifier}" if alternate: start += f" alternate {alternate}" if self.block: start += ":" rv = [start] for item in self.block: callkwargs = kwargs.copy() # inject translate language and label to all children callkwargs.update({"language": language, "label": identifier}) rv.append(util.indent(util.get_code(item, **callkwargs))) return "\n".join(rv) def get_children(self, f): f(self) for i in self.block: i.get_children(f) class EndTranslate(Node): def get_code(self, **kwargs) -> str: return "" class TranslateString(Node): """ https://www.renpy.org/doc/html/translating_renpy.html#language-specific-translations translate french strings: old "OK" new "" """ translation_relevant = True language: str old: str new: str newloc: tuple[str, int] def get_code(self, **kwargs) -> str: language = util.attr(self, "language") if not language: language = "None" return ( f"translate {language} strings:\n" f"{util.indent(f'old {translation.encode_say_string(self.old)}')} \n" f"{util.indent(f'new {translation.encode_say_string(self.new)}')}" ) class TranslatePython(Node): """ https://www.renpy.org/doc/html/translating_renpy.html A ``python`` block inside a ``translate`` statement, run for a specific language during init. Grammar:: translate LANGUAGE python: BLOCK Example:: translate arabic python: gui.REGULAR_FONT = "DejaVuSans.ttf" """ language: str code: PyCode class TranslateBlock(Node): translation_relevant = True block: list[Node] language: str def get_code(self, **kwargs) -> str: return util.get_code(self.block, **kwargs) def get_children(self, f): f(self) for i in self.block: i.get_children(f) class TranslateSay(Node): """ translate say: "Hello" "Bonjour" """ translatable = True translation_relevant = True alternate: str | None language: str | None def get_code(self, **kwargs) -> str: old, new = util.attr(self, "old"), util.attr(self, "new") if old or new: return f'translate say:\n{util.indent(f"{self.old} {self.new}")}' return "" class TranslateEarlyBlock(TranslateBlock): def get_code(self, **kwargs) -> str: for item in self.block: kwargs.update({"language": self.language}) return util.get_code(item, **kwargs) class Style(Node): """ # https://www.renpy.org/doc/html/style.html#defining-styles-style-statement # Creates a new style, inheriting from default. style big_red: size 40 # Updates the style. style big_red color "#f00" # Takes the properties of label_text from big_red, but only if we're # on a touch system. style label_text: variant "touch" take big_red # Style a has all the properties of style c, except that when not present # in c, the properties are taken from b or b's parents. style a is b: take c """ def get_code(self, **kwargs) -> str: start = f"style {self.style_name}" properties = self.properties.copy() parent = util.attr(self, "parent") if parent: start += f" is {parent}" clear = util.attr(self, "clear") if clear: properties["clear"] = None take = util.attr(self, "take") if take: properties["take"] = take delattr = util.attr(self, "delattr") if delattr: for d in delattr: properties["delattr"] = d variant = util.attr(self, "variant") if variant: properties["variant"] = variant if not properties: return start if len(properties) < 3: return f"{start} {util.get_code_properties(properties)}" rv = [start + ":"] rv.append(util.indent(util.get_code_properties(properties, newline=True))) return "\n".join(rv) class Testcase(Node): """ Ren'Py 8.x+: Represents a testcase statement for automated testing. Grammar:: testcase LABEL [OPTIONS] : BLOCK Example:: testcase "test_menu" label start: "Hello" """ label: str = "" test: str = "" block: list[Node] = [] description: str | None = None options: str | None = None def get_code(self, **kwargs) -> str: start = "testcase" label = util.attr(self, "label") if label: start += f" {translation.encode_say_string(label)}" test = util.attr(self, "test") if test: start += f" label {test}" options = util.attr(self, "options") if options: start += f" {options}" block = util.attr(self, "block") return util.label_code(start, block, **kwargs) class Camera(Node): """ https://www.renpy.org/doc/html/3dstage.html#using-the-3d-stage # Enabling the 3D stage for the background layer. camera background: perspective True camera master at a,b,c: atl """ def get_code(self, **kwargs) -> str: start = "camera" if self.layer: start += f" {self.layer}" if self.at_list: start += f" at {util.get_code(self.at_list,**kwargs)}" return util.label_code(start, util.attr(self, "atl"), **kwargs)