init python: import asyncio class Doll(DollMethods): def __init__(self, name, clothes, face, body): self.wardrobe = {} self.wardrobe_list = [] self.blacklist = [] self.outfits = [] self.name = name self.clothes = clothes self.face = DollFace(self, face) self.body = DollBody(self, body) self.cum = DollCum(self) self.pose = None self.emote = Null() # Image properties self.zorder = 15 self.layer = "screens" self.animation = None self.tag = get_character_tag(name) # Transform properties self.pos = (0, 0) self.zoom = 0.5 self.xzoom = 1 self.align = (0.5, 1.0) def rebuild(self): """Rebuild character image cache.""" self.body.rebuild_image() self.face.rebuild_image() self.cum.rebuild_image() for o in self.wardrobe_list: o.rebuild_image() o.rebuild_icon() for o in self.outfits: o.rebuild_image() self.rebuild_image() def rebuild_image(self): self.cached = False if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() def show(self): if renpy.get_screen(("wardrobe", "animatedCG", "studio")): return base_transform = doll_transform(self.pos, self.zoom, self.xzoom) animation = self.animation at_list = [base_transform] if animation: at_list.append(animation) renpy.show(name=self.tag, at_list=at_list, layer=self.layer, what=self.get_image(), zorder=self.zorder) def hide(self): renpy.hide(name=self.tag, layer=self.layer) def make_image(self): asyncio.run(self.build_image()) async def build_image(self): # Add body, face, cum, clothes, masks async def build_clothes(clothes): sprites = [] masks = [] for i in clothes.values(): obj, _, is_worn = i if not obj is None and is_worn: zorder = obj.zorder sprites.extend([ (obj.get_image(), zorder), obj.get_back(), obj.get_front(), obj.get_armfix(), ] + obj.get_zlayers()) if obj.mask: masks.append((obj.mask, zorder-1)) return (sprites, masks) async def build_face(): return (self.face.get_image(), 1) async def build_body(): return (self.body.get_image(), 0) async def build_cum(zorder): return (self.face.get_image(), zorder) face, body, cum, (clothes, masks) = await asyncio.gather( build_cum(self.cum.zorder_cum), build_body(), build_face(), build_clothes(self.clothes), ) sprites = [ face, body, cum, *clothes, (self.emote, 1000) ] # Filter out Nulls sprites = [x for x in sprites if not isinstance(x[0], Null)] sprites.sort(key=itemgetter(1)) masks.sort(key=itemgetter(1)) # Filter out sprites with zorder less than zero, there's no need to iterate over them. back_sprites = [x[0] for x in sprites if x[1] < 0] sprites = [x for x in sprites if x[1] > -1] # Apply alpha mask for m in masks: mask, mask_zorder = m for i, s in enumerate(sprites): sprite, sprite_zorder = s if i < 1 or mask_zorder > sprite_zorder: continue c = tuple(x[0] for x in sprites[:i] if not isinstance(x[0], Null)) masked = AlphaMask(Fixed(*c, fit_first=True), mask) sprites = sprites[i:] sprites.insert(0, (masked, mask_zorder)) break sprites = back_sprites + [x[0] for x in sprites] self.sprite = DollDisplayable(Fixed(*sprites, fit_first=True)) return def equip(self, obj, remove_old=True): """Takes DollCloth or DollOutfit object to equip.""" if isinstance(obj, DollCloth): self.clothes[obj.type][0] = obj self.clothes[obj.type][2] = True if self.is_blacklisted(obj.type): self.unequip(*self.get_blacklister(obj.type)) if obj.blacklist: self.unequip(*obj.blacklist) if self.pose: obj.set_pose(self.pose) elif isinstance(obj, DollOutfit): if remove_old: self.unequip("all") for i in obj.group: self.clothes[i.type][0] = i.parent self.clothes[i.type][0].set_color(i.color) if self.pose: i.parent.set_pose(self.pose) elif isinstance(obj, (list, tuple)): for cloth in obj: self.clothes[cloth.type][0] = cloth self.clothes[cloth.type][2] = True if self.is_blacklisted(cloth.type): self.unequip(*self.get_blacklister(cloth.type)) if cloth.blacklist: self.unequip(*cloth.blacklist) if self.pose: cloth.set_pose(self.pose) self.body.rebuild_image() self.rebuild_image() self.rebuild_blacklist() update_chibi(self.name) def unequip(self, *args): """Takes argument(s) containing string cloth type(s) to unequip.""" if "all" in args: for k, v in self.clothes.items(): if not k in self.blacklist_unequip: if self.pose and v[0]: v[0].set_pose(None) v[0], v[2] = None, True else: for arg in args: if not arg in self.blacklist_unequip: if self.pose and self.clothes[arg][0]: self.clothes[arg][0].set_pose(None) self.clothes[arg][0] = None self.body.rebuild_image() self.rebuild_image() self.rebuild_blacklist() update_chibi(self.name) def get_equipped(self, type): """Takes argument containing string cloth type. Returns equipped object for cloth type.""" return self.clothes[type][0] def get_equipped_item(self, items): """Returns first equipped item from a list or None.""" for i in items: if self.is_equipped_item(i): return i return None def strip(self, *args): """Takes argument(s) containing string cloth type(s) to temporarily displace (hide).""" if "all" in args: for k, v in self.clothes.items(): if not k.startswith(self.blacklist_toggles): v[2] = False else: for arg in args: if arg in self.multislots: for k, v in self.clothes.items(): if k.startswith(arg): v[2] = False else: self.clothes[arg][2] = False self.body.rebuild_image() self.rebuild_image() update_chibi(self.name) def wear(self, *args): """Takes argument(s) containing string cloth type(s) to put on (unhide).""" if "all" in args: if self.is_worn("all"): return for v in self.clothes.values(): v[2] = True else: for arg in args: if arg in self.multislots: for k, v in self.clothes.items(): if k.startswith(arg): v[2] = True else: self.clothes[arg][2] = True self.body.rebuild_image() self.rebuild_image() update_chibi(self.name) def is_equipped(self, *args): """Takes argument containing string cloth type. Returns True if slot is occupied, False otherwise.""" for arg in args: if arg in self.multislots: return any(bool(v[0]) for k, v in self.clothes.items() if k.startswith(arg)) else: if not self.clothes[arg][0]: return False return True def is_any_equipped(self, *args): """Takes arguments containing string cloth types. Returns True if ANY of them is equipped, False otherwise.""" if "clothes" in args: for k, v in self.clothes.items(): if not k.startswith(self.blacklist_toggles): if self.is_equipped(k): return True else: for arg in args: if self.is_equipped(arg): return True return False def is_equipped_item(self, item): """Takes DollCloth object or list of objects. Returns True if item is equipped, False otherwise.""" return self.get_equipped(item.type) == item def is_worn(self, *args): """Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise.""" if "all" in args: for v in self.clothes.values(): if not v[2]: return False else: for arg in args: if arg in self.multislots: return any( (v[0] and v[2]) for k, v in self.clothes.items() if k.startswith(arg)) else: if not self.clothes[arg][0] or not self.clothes[arg][2]: return False return True def is_any_worn(self, *args): """Takes arguments containing string cloth types. Returns True if ANY of them is worn, False otherwise.""" if "clothes" in args: for k, v in self.clothes.items(): if not k.startswith(self.blacklist_toggles): if self.is_worn(k): return True else: for arg in args: if self.is_worn(arg): return True return False def set_face(self, **kwargs): """Takes keyword argument(s) with the string name of expression file(s).""" if self.face.set_face(**kwargs): self.body.rebuild_image() # Rebuild lipstick lipstick = self.clothes.get("makeup4", [None, 1, True])[0] if isinstance(lipstick, DollLipstick): lipstick.rebuild_image() self.rebuild_image() def get_face(self): """Returns a dictionary containing currently set facial expressions. Used in character studio.""" return self.face.get_face() def set_body(self, **kwargs): """Takes keyword argument(s) with the string name of body part file(s).""" if self.body.set_body(**kwargs): self.rebuild_image() def set_body_hue(self, arg): """Takes integer between 0 - 359, rotates the character body colour by given amount.""" self.body.hue = arg self.body.rebuild_image() self.rebuild_image() def set_body_zorder(self, **kwargs): """Takes keyword argument(s) with the name(s) of body part(s) and integer value(s)""" if self.body.set_zorder(**kwargs): self.rebuild_image() def set_cum(self, *args, **kwargs): """Takes keyword argument(s) containing string name(s) of cum layers to apply or None.""" if self.cum.set_cum(*args, **kwargs): self.body.rebuild_image() self.rebuild_image() def set_pose(self, pose): if pose is None or renpy.loadable("characters/{}/poses/{}/loadable.webp".format(self.name, pose)): self.pose = pose self.face.set_pose(pose) self.body.set_pose(pose) self.cum.set_pose(pose) for v in self.clothes.values(): if v[0]: v[0].set_pose(pose) self.rebuild_image() else: raise Exception("'{}' pose doesn't exist for character named '{}'.".format(pose, self.name)) def rebuild_blacklist(self): blacklist = [] for v in self.clothes.values(): if v[0]: blacklist.extend(v[0].blacklist) self.blacklist = list(set(blacklist)) def is_blacklisted(self, type): """Takes string cloth type. Returns True if cloth type is blacklisted.""" return True if type in self.blacklist else False def get_blacklister(self, type): """Takes string cloth type. Returns a list of clothing types that report incompatibility.""" return [x[0].type for x in self.clothes.values() if x[0] and type in x[0].blacklist] def create_outfit(self, temp=False): """Creates a copy of the current character clothes and stores it.""" return DollOutfit([x[0] for x in self.clothes.values() if x[0]], True, temp=temp) def import_outfit(self, path, fromfile=True): """Imports outfit from .png file or clipboard text.""" # Grab data if fromfile: try: imported = image_payload.decode(path) except: if image_payload._file: image_payload._file.close() renpy.notify("Import failed: Corrupted file.") return None else: imported = get_clipboard() # Evaluate data if imported: try: imported = make_revertable(evaluate(imported)) except: renpy.notify("Import failed: Corrupted outfit data.") renpy.block_rollback() return None group = [] for i, x in enumerate(imported): if i == 0 and not x == self.name: renpy.notify("Import failed: Wrong character.") return None for o in self.wardrobe_list: if x[0] == o.id: if not o.unlocked and not game.cheats: renpy.notify("Import failed: You don't own these items. Buy them first.") return None x[0] = o.clone() x[0].set_color(x[1]) group.append(x[0]) if group: renpy.notify("Import successful!") return DollOutfit(group, True) renpy.notify("Import failed: Unknown error.") return None def get_schedule(self): """Returns a list of outfits available for current time of day and weather conditions.""" schedule = [] for o in self.outfits: if o.unlocked and o.schedule["day" if game.daytime else "night"]: if game.weather == "overcast" and o.schedule["cloudy"]: schedule.append(o) elif game.weather in {"storm", "rain"} and o.schedule["rainy"]: schedule.append(o) elif game.weather in {"snow", "blizzard"} and o.schedule["snowy"]: schedule.append(o) elif game.weather in {"clear", "cloudy"} and not (o.schedule["cloudy"] or o.schedule["rainy"] or o.schedule["snowy"]): schedule.append(o) return schedule def equip_random_outfit(self): """Equips random outfit based on Outfits Schedule.""" schedule = self.get_schedule() if schedule: self.equip(renpy.random.choice(schedule)) def set_emote(self, emote): if not emote and not isinstance(emote, Null): self.emote = Null() return if self.pose: path = "characters/{}/poses/{}/emote/{}.webp".format(self.name, self.pose, emote) else: path = "characters/{}/emote/{}.webp".format(self.name, emote) self.emote = DollDisplayable(Image(path))