# init python: # def catch_character_call(label, called): # if called: # if label.startswith(("her_main", "cho_main", "ast_main", "ton_main", "sus_main", "lun_main", "hoo_main")): # editor.catch(label) # def lookup(what): # if isinstance(what, (int, str, float, bool)): # return None # for k, v in store.__dict__.iteritems(): # if k.startswith("_"): # continue # if v == what: # return k # return None # class ExpressionEditor(NoRollback): # transforms = ("move_fade", "sprite_fly_idle") # char = {"her_main": "hermione", # "ton_main": "tonks", # "ast_main": "astoria", # "cho_main": "cho", # "sus_main": "susan", # "lun_main": "luna", # "hoo_main": "hooch", # } # def __init__(self): # self.label = None # self.line = None # self.line_contents = None # self.args = None # self._file = None # self._file_contents = None # self.define_expressions() # self.changes = _dict() # def launch_editor(self, file=None, line=None): # """Launches external text editor.""" # if renpy.in_rollback() or renpy.in_fixed_rollback(): # return # renpy.launch_editor([file], line) # return # def overwrite_statement(self): # # Backup file contents # with open(self._file, "r") as f: # self._file_contents = f.readlines() # found = False # try: # new = self.get_new_statement() # old = self.line_contents # # Overwrite the file contents # with open(self._file, "w") as f: # for n, l in enumerate(self._file_contents, 1): # if n == self.line and l.partition("#")[0].strip() == old: # found = True # f.writelines(l.replace(old, new)) # # Is it a new line, or have we already edited it? # if n not in self.changes.get(self._file, _list()): # self.changes.setdefault(self._file, _dict()).setdefault(n, [old, new, self.args, self.char[self.label], None]) # else: # self.changes[self._file][n] = [old, new, self.args, self.char[self.label], None] # self.line_contents = new # else: # f.writelines(l) # if not found: # renpy.notify("Line {} contents changed unexpectedly, no changes were made.".format(self.line)) # return # except: # # Restore backup # with open(self._file, "w") as f: # for l in self._file_contents: # f.writelines(l) # renpy.notify("An error occurred, no changes were made.") # return # renpy.notify("Saved.") # return # def get_new_statement(self): # text = "\"\", " if not self.args["text"] else "\"{}\", ".format(self.args["text"].replace("\"", "\\\"")) # mouth = "" if self.args["mouth"] == False else "\"{}\", ".format(self.args["mouth"]) # eyes = "" if self.args["eyes"] == False else "\"{}\", ".format(self.args["eyes"]) # eyebrows = "" if self.args["eyebrows"] == False else "\"{}\", ".format(self.args["eyebrows"]) # pupils = "" if self.args["pupils"] == False else "\"{}\", ".format(self.args["pupils"]) # hair = "" if self.label != "ton_main" or self.args["hair"] in (None, "(No change)") else "hair=\"{}\", ".format(self.args["hair"]) # cheeks = "" if self.args["cheeks"] in (None, "(No change)") else "cheeks=\"{}\", ".format(self.args["cheeks"]) # tears = "" if self.args["tears"] in (None, "(No change)") else "tears=\"{}\", ".format(self.args["tears"]) # emote = "" if self.args["emote"] == None else "emote=\"{}\", ".format(self.args["emote"]) # face = "" if self.args["face"] == None else "face=\"{}\", ".format(self.args["face"]) # xpos = self.args["xpos"] # if xpos == None: # xpos = "" # else: # xpos = "xpos={}, ".format(xpos) if is_integer(xpos) else "xpos=\"{}\", ".format(xpos) # ypos = self.args["ypos"] # if ypos == None: # ypos = "" # else: # ypos = "ypos={}, ".format(ypos) if is_integer(ypos) else "ypos=\"{}\", ".format(ypos) # flip = "" if self.args["flip"] == None else "flip={}, ".format(self.args["flip"]) # trans = "" if self.args["trans"] == None else "trans={}, ".format(self.args["trans"]) # animation = "" if self.args["animation"] == False else "animation={}".format(self.args["animation"]) # new = "call {}({}{}{}{}{}{}{}{}{}{}{}{}{}{}{})".format(self.label, text, mouth, eyes, eyebrows, pupils, hair, cheeks, tears, emote, face, xpos, ypos, flip, trans, animation) # new = new.replace(", )", ")") # return new # def catch(self, label): # # Get file and line number # stack = renpy.get_return_stack()[-1] # node = renpy.game.script.namemap.get(stack, None) # # Console call fallback # if node.filename == "": # return # self._file = config.basedir+"/"+node.filename # self.line = node.linenumber # self.label = label # if self.changes.get(self._file, _dict()).get(self.line, None): # # Get arguments from cache # self.args = self.changes[self._file][self.line][2] # else: # # Get arguments from node # node = renpy.game.script.lookup_or_none(self.label) # self.args = _dict([(k, getattr(store, k)) for k in node.parameters.apply(None, None).iterkeys()]) # if self.args["cheeks"] == None: # self.args["cheeks"] = "(No change)" # if self.args["tears"] == None: # self.args["tears"] = "(No change)" # if self.label == "ton_main": # if self.args["hair"] == None: # self.args["hair"] = "(No change)" # # Lookup transition name (Reverse lookup) # trans_arg = self.args["trans"] # if trans_arg: # self.args["trans"] = lookup(trans_arg) # #_transitions = _dict([(obj.callable, name) for (name, obj) in store.__dict__.iteritems() if name in self.transitions]) # #self.args["trans"] = _transitions[self.args["trans"].callable] # # Lookup transform name # anim_arg = self.args["animation"] # if not anim_arg in (False, None): # #self.args["animation"] = lookup(anim_arg) # _transforms = _dict([(obj, name) for (name, obj) in store.__dict__.iteritems() if isinstance(obj, renpy.atl.ATLTransformBase)]) # self.args["animation"] = _transforms[anim_arg] # # Check for already made changes and hijack the line. # if self.changes.get(self._file, _dict()).get(self.line, None): # self.line_contents = self.changes[self._file][self.line][1] # else: # # Read file and find the line in question # with open(self._file, "U") as f: # for n, l in enumerate(f, 1): # if n == self.line: # l = l.partition("#")[0].strip() # Ignore comments and strip spaces # if l.startswith("call {}".format(self.label)): # self.line_contents = l # break # return # def set_data(self, arg, val): # if not isinstance(val, basestring): # return # if self.args[arg] != val: # self.args[arg] = val # self.overwrite_statement() # def get_tooltip(self, file, line): # if self.changes[file][line][4] == None: # imagepath = config.basedir.replace("\\", "/") # args = self.changes[file][line][2] # char = self.changes[file][line][3] # if char == "hermione": # hair = her_hair_base # box = (450, 275, 320, 240) # elif char == "tonks": # hair = ton_hair_base_new # box = (450, 275, 320, 240) # elif char == "astoria": # hair = ast_hair_base # box = (450, 320, 280, 240) # elif char == "cho": # hair = cho_hair_ponytail1 # box = (450, 310, 280, 240) # elif char == "susan": # hair = sus_hair_base # box = (450, 310, 280, 240) # elif char == "luna": # hair = lun_hair_base # box = (450, 310, 280, 240) # elif char == "hooch": # hair = hoo_hair_base # box = (370, 240, 280, 240) # sprites = [imagepath+"/game/characters/"+char+"/body/base/front.webp"] # if not "No change" in args["cheeks"]: # sprites.append(imagepath+"/game/characters/"+char+"/face/cheeks/"+args["cheeks"]+".webp") # sprites.append(imagepath+"/game/characters/"+char+"/face/eyes/"+args["eyes"]+".webp") # if not any(x in args["eyes"] for x in ("closed", "happyCl")): # m = imagepath+"/game/characters/"+char+"/face/eyes/"+args["eyes"]+"_mask.webp" # sprites.append(AlphaMask(imagepath+"/game/characters/"+char+"/face/pupils/"+args["pupils"]+".webp", m)) # sprites.append(imagepath+"/game/characters/"+char+"/face/eyebrows/"+args["eyebrows"]+".webp") # if not "No change" in args["tears"]: # sprites.append(imagepath+"/game/characters/"+char+"/face/tears/"+args["tears"]+".webp") # sprites.append(imagepath+"/game/characters/"+char+"/face/mouth/"+args["mouth"]+".webp") # # Hair # sprites.append(hair.get_back()[0]) # if hair.back_outline: # sprites.append(hair.back_outline) # sprites.append(hair.get_image()) # sprites = tuple(itertools.chain.from_iterable(((0,0), x) for x in sprites)) # self.changes[file][line][4] = At(Crop(box, Composite((1010, 1200), *sprites)), Transform(zoom=0.5)) # return self.changes[file][line][4] # def set_expressions(self): # if not self.char or not self.label: # return # c = getattr(renpy.store, self.char[self.label]) # cheeks = None if self.args["cheeks"] in (None, "(No change)") else self.args["cheeks"] # tears = None if self.args["tears"] in (None, "(No change)") else self.args["tears"] # if self.label == "ton_main": # hair = None if self.args["hair"] in (None, "(No change)") else self.args["hair"] # # Hardcoded for tonks # if hair: # if hair in ("neutral", "basic", "reset"): # target_color = tonks_haircolor # elif hair in ("red", "angry", "furious"): # target_color = [[164, 34, 34, 255], [219, 83, 83, 255]] # elif hair in ("orange", "upset", "annoyed"): # target_color = [[228, 93, 34, 255], [246, 193, 170, 255]] # elif hair in ("yellow", "happy", "cheerful"): # target_color = [[255, 213, 23, 255], [255, 239, 167, 255]] # elif hair in ("green", "disgusted"): # target_color = [[111, 205, 75, 255], [200, 237, 186, 255]] # elif hair in ("blue", "sad"): # target_color = [[64, 75, 205, 255], [182, 186, 237, 255]] # elif hair == "purple": # target_color = [[205, 75, 205, 255], [237, 186, 237, 255]] # elif hair in ("white", "scared"): # target_color = [[238, 238, 241, 255], [249, 249, 250, 255]] # elif hair in ("pink", "horny"): # target_color = [[255, 105, 180, 255], [251, 205, 222, 255]] # tonks.get_equipped("hair").set_color(target_color) # c.set_face(mouth=self.args["mouth"], eyes=self.args["eyes"], eyebrows=self.args["eyebrows"], pupils=self.args["pupils"], cheeks=cheeks, tears=tears) # def get_expressions(self, type): # return sorted(self.expressions[self.char[self.label]][type]) # def define_expressions(self): # def scan_files(char): # dirs = { # "mouth": None, # "eyes": None, # "eyebrows": None, # "pupils": None, # "cheeks": None, # "tears": None, # "hair": ["(No change)", "neutral", "angry", "upset", "happy", "disgusted", "sad", "purple", "scared", "horny"], # } # for k, v in dirs.iteritems(): # if k == "hair": # continue # path = "{}/game/characters/{}/face/{}/".format(config.basedir, char, k) # if not os.path.exists(path): # os.makedirs(path) # dirs[k] = list(x.rsplit(".webp")[0] for x in os.listdir(path) if x.endswith(".webp") and not "_mask" in x and not "_skin" in x) # dirs["cheeks"] += ["(No change)"] # dirs["tears"] += ["(No change)"] # return _dict(dirs) # self.expressions = _dict([(x, scan_files(x)) for x in self.char.itervalues()]) # def editor_reset(): # editor.changes = _dict() # if config.developer and not renpy.mobile: # editor = ExpressionEditor() # config.label_callback = catch_character_call # config.after_load_callbacks.append(editor_reset) # config.interact_callbacks.append(editor.set_expressions) # screen editor(): # layer "interface" # tag editor # zorder 0 # style_prefix "editor" # default minimised = False # default minimised_history = False # default minimised_cg = False # default frame_size = (250, 500) # default expressions = ("mouth", "eyes", "eyebrows", "pupils", "cheeks", "tears", "hair") # if config.developer and not _menu: # # Editor # drag: # drag_name "editor" # draggable True # drag_offscreen False # drag_handle (0, 0, 1.0, 26) # pos (50, 50) # window: # background "#00000080" # xysize (frame_size if not minimised else (250, 28)) # button action NullAction() style "empty" ypos 18 # text "Expression Editor {size=-2}ver 0.2{/size}" size 10 color "#FFF" outlines [(1, "#00000080", 1, 0)] # textbutton "_" ysize 28 offset (-32, -7) text_size 15 text_yalign 0.5 xalign 1.0 action [ToggleScreenVariable("minimised", True, False), SelectedIf(None)] tooltip ("Maximise" if minimised else "Minimise") # textbutton "x" ysize 28 offset (6, -7) text_size 15 text_yalign 0.5 xalign 1.0 action Hide("editor") tooltip "Close editor" # frame: # style "empty" # xysize (236, 2) # pos (0, 18) # background "#FFFFFF80" # if not minimised: # if editor.label: # $ f = editor._file.split("/")[-1] # $ l = editor.line # vbox: # ypos 24 # for i in expressions: # if i == "hair" and not editor.label == "ton_main": # pass # else: # text i size 14 color "#fff" outlines [(1, "#00000080", 1, 0)] # hbox: # xpos 12 # box_wrap True # for j in editor.get_expressions(i): # textbutton j: # selected (j == editor.args[i]) # action Function(editor.set_data, i, j) # text_size 8 # text_selected_color "#009900" # text_xalign 0.5 # xminimum 32 # # History # drag: # drag_name "editor_history" # draggable True # drag_offscreen False # drag_handle (0, 0, 1.0, 26) # pos (300, 50) # frame: # background "#00000080" # xysize (frame_size if not minimised_history else (250, 28)) # button action NullAction() style "empty" xysize (frame_size if not minimised_history else (250, 28)) ypos 18 # text "History" size 10 color "#FFF" outlines [(1, "#00000080", 1, 0)] # textbutton "_" ysize 28 offset (-32, -7) text_size 15 text_yalign 0.5 xalign 1.0 action [ToggleScreenVariable("minimised_history", True, False), SelectedIf(None)] tooltip ("Maximise" if minimised_history else "Minimise") # textbutton "x" ysize 28 offset (6, -7) text_size 15 text_yalign 0.5 xalign 1.0 action Hide("editor") tooltip "Close editor" # frame: # style "empty" # xysize (236, 2) # pos (0, 18) # background "#FFFFFF80" # if not minimised_history: # if editor.changes: # $ n = 0 # $ nn = 0 # for k, v in editor.changes.iteritems(): # $ n += 1 # $ nn += len(v) # text "[nn] changes in [n] files" size 10 color "#FFF" xalign 0.5 ypos 26 outlines [(1, "#00000080", 1, 0)] # frame: # style "empty" # xsize 236 # ymaximum 400 # yfill True # ypos 42 # side "c r": # area (0, 0, 236, 400) # viewport id "editor_history": # draggable False # mousewheel True # vbox: # for fn in editor.changes.iterkeys(): # text (fn.split("/")[-1]) size 10 color "#FFF" xalign 0.5 outlines [(1, "#00000080", 1, 0)] # frame style "empty" xysize (236, 2) background "#FFFFFF80" # for l in editor.changes[fn].iterkeys(): # textbutton "Line [l]": # text_size 10 # text_color "#FFF" # text_outlines [(1, "#00000080", 1, 0)] # if editor.line == l: # background "#FFFFFF80" # tooltip editor.get_tooltip(fn, l) # action Function(editor.launch_editor, file=fn, line=l) # vbar value YScrollValue("editor_history") xsize 10 # else: # text "No history." size 15 color "#FFF" align (0.5, 0.5) outlines [(1, "#00000080", 1, 0)] # # CG poser # if renpy.get_screen("animatedCG"): # drag: # drag_name "editor_cg" # draggable True # drag_offscreen False # drag_handle (0, 0, 1.0, 26) # pos (550, 50) # frame: # background "#00000080" # xysize (frame_size if not minimised_cg else (250, 28)) # button action NullAction() style "empty" xysize (frame_size if not minimised_cg else (250, 28)) ypos 18 # text "CG Poser" size 10 color "#FFF" outlines [(1, "#00000080", 1, 0)] # textbutton "_" ysize 28 offset (-32, -7) text_size 15 text_yalign 0.5 xalign 1.0 action [ToggleScreenVariable("minimised_cg", True, False), SelectedIf(None)] tooltip ("Maximise" if minimised_history else "Minimise") # textbutton "x" ysize 28 offset (6, -7) text_size 15 text_yalign 0.5 xalign 1.0 action Hide("editor") tooltip "Close editor" # frame: # style "empty" # xysize (236, 2) # pos (0, 18) # background "#FFFFFF80" # if not minimised_cg: # $ x, y = camera.pos # $ zoom = camera.zoom # $ rotate = camera.rotate # default pos_step = 5 # default zoom_step = 0.05 # default rotate_step = 5 # vbox: # text "Pos Step ({})".format(pos_step) color "#fff" # hbox: # textbutton "-": # action SetScreenVariable("pos_step", max(pos_step-5, 5)) # textbutton "+": # action SetScreenVariable("pos_step", min(pos_step+5, 100)) # text "Pos (x={}, y={})".format(x, y) color "#fff" # hbox: # textbutton "X-": # action [SetVariable("camera.pos", (x-pos_step, y)), Function(camera.redraw, 0)] # textbutton "X+": # action [SetVariable("camera.pos", (x+pos_step, y)), Function(camera.redraw, 0)] # hbox: # textbutton "Y-": # action [SetVariable("camera.pos", (x, y-pos_step)), Function(camera.redraw, 0)] # textbutton "Y+": # action [SetVariable("camera.pos", (x, y+pos_step)), Function(camera.redraw, 0)] # null height 20 # text "Zoom Step ({})".format(zoom_step) color "#fff" # hbox: # textbutton "-": # action SetScreenVariable("zoom_step", max(zoom_step-0.01, 0.01)) # textbutton "+": # action SetScreenVariable("zoom_step", min(zoom_step+0.01, 0.25)) # text "Zoom (zoom={})".format(zoom) color "#fff" # hbox: # textbutton "-": # action [SetVariable("camera.zoom", zoom-zoom_step), Function(camera.redraw, 0)] # textbutton "+": # action [SetVariable("camera.zoom", zoom+zoom_step), Function(camera.redraw, 0)] # null height 20 # text "Rotate Step ({})".format(rotate_step) color "#fff" # hbox: # textbutton "-": # action SetScreenVariable("rotate_step", max(rotate_step-1, 1)) # textbutton "+": # action SetScreenVariable("rotate_step", min(rotate_step+1, 1)) # text "Rotate ({})".format(rotate) color "#fff" # hbox: # textbutton "-": # action [SetVariable("camera.rotate", rotate-rotate_step), Function(camera.redraw, 0)] # textbutton "+": # action [SetVariable("camera.rotate", rotate+rotate_step), Function(camera.redraw, 0)] # null height 50 # textbutton "Copy to Clipboard" action [Function(set_clipboard, "$ camera.set(zoom={}, pos=({}, {}), rotate={}, t=1.0)".format(zoom, x, y, rotate)), Notify("Copied!")] # style editor_button is empty: # margin (3, 3) # style editor_button_text: # color "#cccccc" # hover_color "#ffffff" # outlines [(1, "#00000080", 1, 0)]