From 395c3d4397d4b9606f97573d9170e7c6681d5345 Mon Sep 17 00:00:00 2001 From: LoafyLemon Date: Thu, 27 Apr 2023 20:46:24 +0100 Subject: [PATCH] Expression Editor * Added support for temporary attribues * Redesigned persistent expressions toggles and simplified them * Improved defined expressions dictionary handling and added imap sorting * Simplified parts of code * Fixed prompt screen incompatibility with Text displayables --- game/scripts/gui/misc.rpy | 11 ++- game/scripts/utility/editor.rpy | 136 ++++++++++++++------------------ 2 files changed, 68 insertions(+), 79 deletions(-) diff --git a/game/scripts/gui/misc.rpy b/game/scripts/gui/misc.rpy index 2e80c161..54316b16 100644 --- a/game/scripts/gui/misc.rpy +++ b/game/scripts/gui/misc.rpy @@ -28,9 +28,14 @@ screen confirm(message, yes_action=Return(True), no_action=Return(False)): vbox: spacing 25 - text "[message!t]": - xalign 0.5 - text_align 0.5 + if isinstance(message, str): + text "[message!t]": + xalign 0.5 + text_align 0.5 + else: + text message: + xalign 0.5 + text_align 0.5 hbox: xalign 0.5 diff --git a/game/scripts/utility/editor.rpy b/game/scripts/utility/editor.rpy index 3bbb28ca..25e639ee 100644 --- a/game/scripts/utility/editor.rpy +++ b/game/scripts/utility/editor.rpy @@ -5,13 +5,17 @@ init python: class Editor(python_object): + doll_keywords = ["mouth", "eyes", "eyebrows", "pupils", "cheeks", "tears", "emote",\ + "xpos", "ypos", "pos", "flip", "trans", "animation", "hair"] + other_keywords = ["expression", "xpos", "ypos", "pos", "flip", "trans", "animation", "wand"] + def __init__(self): self.node = None self._live_code = None # Volatile; Can be changed at any given moment. self.history = _dict() self.expressions = self.define_expressions() self.active_expressions = _dict() - self.persistent_expressions = _dict() + self.persistent_expressions = _set() self.last_expressions = _dict() self.active = False self.sayers = {i[:3]:i for i in states.dolls} @@ -51,7 +55,6 @@ init python: self.last_expressions = deepcopy(self.active_expressions) self.active_expressions.update(self.resolve_expressions()) self.live_code = code - self.apply_persistent() # Consistency check if not matches(code, code_file): @@ -80,18 +83,11 @@ init python: raise Exception("Starred arguments are not implemented.") node.arguments.arguments = contents + elif what == "temp_attr": + setattr(node, "temporary_attributes", tuple(contents)) else: raise TypeError("Type '{}' is not implemented.".format(what)) - file = node.filename - line = node.linenumber - code = node.get_code() - - self.write_file(file, line, code) - self.write_history(file, line, code) - self.live_code = code - renpy.run(RestartStatement()) # This has to be run last. - def replace_expression(self, expr, val): node = self.node who = node.who @@ -111,10 +107,19 @@ init python: # l = [(k, "\"{}\"".format(v)) for k, v in d.items() if not v is None] # This is faster, but not robust enough. l = _list() + l2 = _list() for key, val in d.items(): - if key in kw_args and val is None: - continue + + if key in kw_args: + if val is None: + continue + elif key in self.persistent_expressions: + pass + else: + val = val.strip("\"") # When accessed, temporary attribute values have quotes by default, we need to strip them. + l2.extend((key, val)) + continue # First four arguments are preferred to be keywordless, # so we'll just insert them into positions to avoid issues. @@ -124,6 +129,16 @@ init python: l.append((key, val)) self.replace("args", l) + self.replace("temp_attr", l2) + + file = node.filename + line = node.linenumber + code = node.get_code() + + self.write_file(file, line, code) + self.write_history(file, line, code) + self.live_code = code + renpy.run(RestartStatement()) # This has to be run last. @property def live_code(self): @@ -227,8 +242,9 @@ init python: f.writelines(data) renpy.notify("Saved.") - # except FileNotFoundError: # Python 3 only :( - # renpy.notify("Source file is missing.") + except FileNotFoundError as e: + renpy.notify("Source file is missing.") + print(e) except EnvironmentError as e: renpy.notify("File write error.\n{size=-8}(Check console for details){/size}") print(e) @@ -257,11 +273,12 @@ init python: # Define expressions for Doll type characters. all_files = renpy.list_files() - d = _dict() + d = OrderedDict() for charname in states.dolls: charobj = get_character_object(charname) extensions = charobj.extensions + who = charname[:3] for part in sorted(charobj.face._face.keys()): @@ -276,22 +293,25 @@ init python: continue if part in ("cheeks", "tears"): - expressions = d.setdefault(charname[:3], OrderedDict()).setdefault(part, _list((None,))) + expressions = d.setdefault(who, OrderedDict()).setdefault(part, _list((None,))) else: - expressions = d.setdefault(charname[:3], OrderedDict()).setdefault(part, _list()) + expressions = d.setdefault(who, OrderedDict()).setdefault(part, _list()) if not expression in expressions: expressions.append(expression) + imap = {v: i for i, v in enumerate(self.doll_keywords)} + d[who] = OrderedDict(sorted(list(d[who].items()), key=lambda x: imap[x[0]])) + # Define additional Tonks' hair choices. - d["ton"]["hair"] = [None, "neutral", "angry", "annoyed", "happy", "disgusted", "sad", "purple", "scared", "horny"] + d["ton"]["hair"] = [None] + list(tonks_haircolor_table.keys()) # Define expressions for Genie. filters = None path = "characters/genie/" files = None - d["gen"] = _dict() + d["gen"] = OrderedDict() d["gen"]["expression"] = ["angry", "grin", "base", "open"] # Define expressions for Snape. @@ -299,7 +319,7 @@ init python: path = "characters/snape/main/" files = [x for x in all_files if path in x] - d["sna"] = _dict() + d["sna"] = OrderedDict() d["sna"]["expression"] = [x.split(path)[1].split(".webp")[0] for x in files if x.endswith(".webp") and not any(f in x for f in filters)] d["sna"]["special"] = [None, "wand", "picture_frame"] @@ -311,10 +331,9 @@ init python: args = node.arguments if who in self.sayers: - keywords = ["mouth", "eyes", "eyebrows", "pupils", "cheeks", "tears", "emote", - "face", "xpos", "ypos", "pos", "flip", "trans", "animation", "hair"] + keywords = self.doll_keywords else: - keywords = ["expression", "face", "xpos", "ypos", "pos", "flip", "trans", "animation", "wand"] + keywords = self.other_keywords # Arguments are contained within a list, # they consist of opaque tuples, @@ -341,6 +360,12 @@ init python: d[who][key] = val + # Resolve temporary attributes + temp_attr = node.temporary_attributes + + if temp_attr: + d[who].update(dict(zip(temp_attr[::2], temp_attr[1::2]))) + # Sort our dictionary using an index map, # if we don't, we'll end up messing up # the order of keywordless arguments. @@ -360,19 +385,6 @@ init python: expr = strip(expr) return expr - def get_expressions_persistent(self, who): - return self.persistent_expressions.get(who, _dict()) - - def get_expressions_persistent_type(self, who, type): - return self.get_expressions_persistent(who).get(type, False) - - def set_expressions_persistent_type(self, who, type): - self.persistent_expressions.setdefault(who, _dict())[type] = True - - def toggle_expressions_persistent_type(self, who, type): - val = self.get_expressions_persistent_type(who, type) - self.persistent_expressions.setdefault(who, _dict())[type] = not val - def get_expressions_last(self, who): return self.last_expressions.get(who, _dict()) @@ -382,34 +394,6 @@ init python: expr = strip(expr) return expr - def apply_persistent(self): - # This function will break the editor - # if it participates in rollback - if renpy.in_rollback() or renpy.in_fixed_rollback(): - return - - node = self.node - who = node.who - - persistent = self.get_expressions_persistent(who) - last = self.get_expressions_last(who) - active = self.get_expressions_active(who) - - if last == active: - return - - for type, val in persistent.items(): - if val is False: - continue - - last = self.get_expressions_last_type(who, type) - active = self.get_expressions_active_type(who, type) - - if last == active: - continue - - self.replace_expression(type, last) - def get_node_history(self): node = self.node file = node.filename @@ -417,6 +401,12 @@ init python: return self.read_history(file, line) + def toggle_persistent(self, expr): + if expr in self.persistent_expressions: + self.persistent_expressions.remove(expr) + else: + self.persistent_expressions.add(expr) + class ToggleEditor(Action, NoRollback): def __call__(self): if not config.developer: @@ -459,7 +449,7 @@ screen editor(): fixed: fit_first True - text "Expression Editor {size=-2}ver 0.3a{/size}" style "editor_title" + text "Expression Editor {size=-2}ver 0.4b{/size}" style "editor_title" if e.node: vbox: @@ -471,20 +461,14 @@ screen editor(): if e.node.who in e.sayers: textbutton "😊": - action Function(e.toggle_expressions_persistent_type, e.node.who, "cheeks") - selected (e.get_expressions_persistent_type(e.node.who, "cheeks")) + action Function(e.toggle_persistent, "cheeks") + selected ("cheeks" in e.persistent_expressions) tooltip "Toggle persistent cheeks" textbutton "😢": - action Function(e.toggle_expressions_persistent_type, e.node.who, "tears") - selected (e.get_expressions_persistent_type(e.node.who, "tears")) + action Function(e.toggle_persistent, "tears") + selected ("tears" in e.persistent_expressions) tooltip "Toggle persistent tears" - if e.node.who == "ton": - textbutton "✂️": - action Function(e.toggle_expressions_persistent_type, e.node.who, "hair") - selected (e.get_expressions_persistent_type(e.node.who, "hair")) - tooltip "Toggle persistent hair" - add Solid("#ffffff80") xysize (480, 1) hbox: