init python: class DollCloth(DollMethods): def __init__(self, name, categories, type, id, color, zorder=None, unlocked=False, level=0, blacklist=[], parent=None, armfix=False, modpath=None): self.name = name self.char = eval(name) self.categories = categories self.type = type self.id = id self.color = color self.color_default = [x[:] for x in color] self.unlocked = unlocked self.layers = len(color) self.parent = parent self.ico = Null() self.blacklist = blacklist self.level = level self.modpath = posixpath.normpath(modpath) if modpath else "" self.armfix = armfix self.back_outline = None self.front_outline = None # Inherit zorder from character if needed self.zorder = zorder or self.char.clothes[type][1] self.seen = self.unlocked self.cached_icon = False self.set_imagepath() self.set_layers() # Add to character wardrobe and unordered list if not parent: self.char.wardrobe.setdefault(self.categories[0], {}).setdefault(self.categories[1], []).append(self) self.char.wardrobe_list.append(self) def set_imagepath(self): if any(x in self.type for x in self.multislots): subpath = self.type[:-1] else: subpath = self.type path = "{}/characters/{}/clothes/{}/{}/".format(self.modpath, self.name, subpath, self.id) if renpy.loadable(path + "0.webp"): self.imagepath = path return raise IOError("Couldn't find file \"{}/characters/{}/clothes/{}/{}/0.webp\"".format(self.modpath, self.name, subpath, self.id)) def set_layers(self): for x in self.layers_special: if x == "zorder": self.__dict__["zlayers"] = [f for f in renpy.list_files() if f.startswith(self.imagepath.lstrip("/")) and "zorder" in f] else: path = "{}{}.webp".format(self.imagepath, x) self.__dict__[x] = path if renpy.loadable(path) else None for x in self.layers_additional: self.__dict__[x] = [] for i in xrange(self.layers): path = "{}{}_{}.webp".format(self.imagepath, i, x) if renpy.loadable(path): self.__dict__[x].append(path) else: self.__dict__[x].append(None) path = "{}outline_{}.webp".format(self.imagepath, x) self.__dict__[x+"_outline"] = path if renpy.loadable(path) else None def build_image(self): sprites = [(self.apply_color("{}{}.webp".format(self.imagepath, x), x), x) for x in xrange(self.layers)] # Add extra layers if exist for n, x in enumerate(self.layers_extra): path = "{}{}.webp".format(self.imagepath, x) if renpy.loadable(path): sprites.append((path, self.layers+n)) sprites.sort(key=itemgetter(1)) sprites = tuple(x[0] for x in sprites) return sprites def build_mannequin(self): sprites = [ self.get_back(), self.get_mannequin(), self.get_front(), (self.get_image(), self.zorder), self.get_armfix(mannequin=True) ] sprites.extend(self.get_zlayers()) sprites.sort(key=itemgetter(1)) # Apply Alpha mask if self.mask: mask_zorder = self.zorder-1 for i, s in enumerate(sprites): sprite, sprite_zorder = s if mask_zorder > sprite_zorder >= 0: continue c = tuple(x[0] for x in sprites[:i] if not isinstance(x[0], Null)) masked = AlphaMask(Fixed(*c, fit_first=True), self.mask) sprites = sprites[i:] sprites.insert(0, (masked, mask_zorder)) break bounds = "{}outline.webp".format(self.imagepath) if renpy.loadable("{}outline.webp".format(self.imagepath)) else "{}0.webp".format(self.imagepath) sprites = tuple(x[0] for x in sprites) return (sprites, bounds) def make_icon(self): thread = DollThread(target=self.build_icon) thread.daemon = True thread.start() sprite = thread.join() self.ico = DollDisplayable(sprite) def build_icon(self): sprites, bounds = self.build_mannequin() wmax, hmax = self.sizes wmin = hmin = 96 x, y, w, h = crop_whitespace(bounds) xoffset, yoffset = w/2, h/2 w = h = max(w, h, wmin, hmin) w = max(wmin, w + w/2) h = max(hmin, h + h/2) x = clamp( (x - w/2) + xoffset, 0, wmax) y = clamp( (y - h/2) + yoffset, 0, hmax) # Forbid exceeding the image height. if y+h > hmax: y = hmax-h return Transform(Fixed(*sprites, fit_first=True), crop=(x, y, w, h)) def rebuild_icon(self): # Defers rebuild until next time get_image is called self.cached_icon = False def get_zlayers(self): """Returns a list of zordered layers""" zlayers = [] for i in self.zlayers: path, filename = os.path.split(i) filename = os.path.splitext(filename)[0] # I.e "0_zorder_35", we don't need the middle control layertype, _, zorder = filename.split("_") if layertype.isdigit(): zlayers.append((self.apply_color(i, int(layertype)), int(zorder))) else: zlayers.append((i, int(zorder))) return zlayers def get_back(self): """Returns a list of layers displayed in the back of object/character""" back_outline = [self.back_outline] if self.back_outline else [] sprites = [self.apply_color(x, n) for n, x in enumerate(self.back) if x] + back_outline if sprites: return (Fixed(*sprites, fit_first=True), -100+self.zorder) w, h = self.sizes return (Null(width=w, height=h), -100+self.zorder) def get_front(self): """Returns a list of layers displayed in the front of object/character""" front_outline = [self.front_outline] if self.front_outline else [] sprites = [self.apply_color(x, n) for n, x in enumerate(self.front) if x] + front_outline if sprites: return (Fixed(*sprites, fit_first=True), 100+self.zorder) w, h = self.sizes return (Null(width=w, height=h), 100+self.zorder) def get_armfix(self, mannequin=False): if self.armfix: if mannequin: armleft = gray_tint("{}armleft/{}_fix.webp".format(self.char.body.imagepath, self.char.body.get_part("armleft"))) armright = gray_tint("{}armright/{}_fix.webp".format(self.char.body.imagepath, self.char.body.get_part("armright"))) else: armleft = Transform("{}armleft/{}_fix.webp".format(self.char.body.imagepath, self.char.body.get_part("armleft")), matrixcolor=HueMatrix(self.char.body.hue)) armright = Transform("{}armright/{}_fix.webp".format(self.char.body.imagepath, self.char.body.get_part("armright")), matrixcolor=HueMatrix(self.char.body.hue)) return (Fixed(armleft, armright, fit_first=True), 0.5+self.zorder) w, h = self.sizes return (Null(width=w, height=h), 0.5+self.zorder) def get_mannequin(self): return (self.char.body.get_mannequin(group=[self]), 0) def get_icon(self): """Returns cropped Fixed displayable""" if not renpy.is_skipping(): if not self.cached_icon: self.cached_icon = True self.make_icon() return self.ico def apply_color(self, img, n): """Takes image and int layer number. Used internally.""" c = TintMatrix(tuple(self.color[n])) return Transform(img, matrixcolor=c) def set_color(self, n): """Takes int layer number for manual color picking or a list to replace the cloth color in its entirety.""" if isinstance(n, int): col = Color(tuple(self.color[n])) dcol = Color(tuple(self.color_default[n])) cp.live_replace(col) cp.start_replace(col) cp.default_replace(dcol) renpy.show_screen("colorpickerscreen", self) while True: action, value = ui.interact() if action == "layer": n = value col = Color(tuple(self.color[value])) dcol = Color(tuple(self.color_default[n])) cp.live_replace(col) cp.start_replace(col) cp.default_replace(dcol) elif action == "released": self.color[n] = [int(255*x) for x in value.rgba] self.rebuild_image() self.char.rebuild_image() elif action == "replace": self.color[n] = [int(255*x) for x in value.rgba] cp.live_replace(value) self.rebuild_image() self.char.rebuild_image() elif action == "finish": break renpy.hide_screen("colorpickerscreen") elif isinstance(n, list): self.color = [x[:] for x in n] self.rebuild_image() self.char.rebuild_image() self.rebuild_icon() def reset_color(self, n=None): """Reset cloth color. Takes optional int layer number to reset only specific layer color.""" if n: self.color[n] = [x for x in self.color_default] else: self.color = [x[:] for x in self.color_default] self.rebuild_image() self.char.rebuild_image() self.rebuild_icon() def clone(self): """Creates a clone of this cloth object. Since it requires a parent object it should be used internally only to avoid object depth issue.""" return DollCloth(self.name, self.categories, self.type, self.id, [x[:] for x in self.color], self.zorder, self.unlocked, self.level, self.blacklist, self, self.armfix, self.modpath) def set_pose(self, pose): compatible = False for x in (self.categories[0], self.categories[1], self.type): if pose is None: path = "{}/characters/{}/clothes/{}/{}/".format(self.modpath, self.name, x, self.id) if renpy.loadable(path + "0.webp"): self.imagepath = path self.char.wear(self.type) compatible = True break else: path = "{}/characters/{}/poses/{}/clothes/{}/{}/".format(self.modpath, self.name, pose, x, self.id) if renpy.loadable(path + "0.webp"): self.imagepath = path self.char.wear(self.type) compatible = True break if not compatible: self.char.strip(self.type) return self.set_layers() self.rebuild_image() return def is_compatible(self): return def is_modded(self): """Returns True if item comes from a mod.""" if self.modpath: return True return False def get_modname(self): """Return the name of the mod directory if exists.""" return self.modpath.split("/")[1] if self.is_modded() else None def mark_as_seen(self): self.seen = True def is_multislot(self): return any(x in self.type for x in self.multislots) def unlock(self): self.unlocked = True if self.parent: self.parent.unlock()