init python: class DollCum(DollMethods): layer_types = { "mask": "-1", "skin": 1, "cum": 100, } layer_modifiers = { "back": "-300", "front": "+300", "zorder": None, } def __init__(self, obj): self.char = obj self._cum = {k: None for k in (self.char.clothing_layers | self.char.face_layers).keys()} self._hash = None def generate_hash(self): salt = str( [self.char.name, self.char.pose, str(self.char.face._hash), str(self.char.face._hash), str([x[0]._hash for x in self.char.states.values() if x[0] and x[2]]), sorted(list(self._cum.items()))] ) return hash(salt) def set_cum(self, *args, **kwargs): if args: self._cum = {k: args[0] for k in self._cum} self._cum.update(kwargs) self.is_stale() @functools.cache def get_layers(self, hash, subpath=""): def _lookahead(path): return any(fp.startswith(path) for fp in renpy.list_files()) cum = self._cum extensions = self.extensions types = self.layer_types modifiers = self.layer_modifiers face_layers = self.char.face_layers body_layers = self.char.body_layers clothing_layers = self.char.clothing_layers active_faces = self.char.face._face active_clothes = self.char.states layers = {} for part, name in cum.items(): if name is None: continue if part in face_layers: zorder = face_layers.get(part) identifier = active_faces.get(part, "default") path = posixpath.join(self.char.modpath, "characters", self.char.name, "poses", self.char.pose, subpath, "cum", part, name, identifier) elif part in (clothing_layers | body_layers): cloth, zorder, is_worn = active_clothes.get(part, [None, None, None]) if is_worn is None: # Backwards compatibility for old layering system path = posixpath.join("characters", self.char.name, "poses", self.char.pose, "cum", part, name) if config.developer: renpy.notify(f"Using old-type cum layer, consider updating the code; Layer: \"{part}\"") else: if cloth is None or is_worn is False: identifier = "default" modpath = "" zorder = None else: identifier = cloth.id modpath = cloth.modpath zorder = cloth.zorder + 1 path = posixpath.join(modpath, "characters", self.char.name, "poses", self.char.pose, "cum", part, name, identifier) path = path if _lookahead(path) else posixpath.join(os.path.split(path)[0], (identifier := "default")) else: zorder = types.get("cum") path = posixpath.join(self.char.modpath, "characters", self.char.name, "poses", self.char.pose, "cum", part, name) for f in renpy.list_files(): fp, fn = os.path.split(f) fn, ext = os.path.splitext(fn) if not fp == path or not ext in extensions: continue ltype, *tails = fn.rsplit("_") if not ltype in types: print("Invalid layer type for file: {}".format(f)) continue zorder = zorder or types.get(ltype) if tails: lmodifier, *tails = tails if not lmodifier in modifiers: print("Invalid modifier for file: {}".format(f)) continue zorder_mod = modifiers.get(lmodifier) zorder = (zorder + int(zorder_mod)) layers.setdefault(" ".join([part, name, ltype, lmodifier]), [f, zorder]) else: layers.setdefault(" ".join([part, name, ltype]), [f, zorder]) return layers @functools.cache def build_image(self, hash, subpath="", matrix=None): if matrix is None: matrix = self.char.body.hue processors = { "skin": lambda file: Transform(file, matrixcolor=matrix), "default": lambda file: Image(file), } layers = self.get_layers(hash, subpath) sprites = [] for identifier, (file, zorder) in layers.items(): cum_type, name, ltype = identifier.rsplit(" ") processor = processors.get(identifier, processors["default"]) processed_file = processor(file) sprites.append((identifier, processed_file, zorder)) return sprites @property def image(self): if not renpy.is_skipping() and self.is_stale(): hash = self._hash sprites = self.build_image(hash) sprites.sort(key=itemgetter(2)) sprites = [x[1] for x in sprites] self._image = Fixed(*sprites, fit_first=True) return self._image