init python: import asyncio class Doll(DollMethods): # 0 - 50 = Skin Layers # 51 - 100 = Face Layers # 101 - 300+ = Clothes Layers clothing_layers = { "buttplug": -11, "makeup": 111, # multislot "accessory": 121, # multislot "piercing": 131, # multislot "tattoo": 141, # multislot "pubes": 151, "stockings": 161, "panties": 171, "garterbelt": 181, "bottom": 191, "bra": 201, "top": 211, "gloves": 221, "robe": 231, "neckwear": 241, "hair": 251, "earrings": 261, "glasses": 271, "headgear": 281 } face_layers = { "tears": 75, "eyebrows": 70, "pupils": 65, "eyes": 60, "mouth": 55, "cheeks": 51 } def __init__(self, name): self.wardrobe = {} self.wardrobe_list = [] self.blacklist = [] self.outfits = [] self.name = name self.clothes = {k: [None, v, True] for k, v in self.clothing_layers.items()} self.face = DollFace(self) self.body = DollBody(self) self.cum = DollCum(self) self.pose = "" self.emote = Null() self._hash = None # 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 generate_hash(self): salt = str( [self.name, self.pose, str(self.body._hash), str(self.face._hash), str(self.cum._hash), str([x[0]._hash for x in self.clothes.values() if x[0] and x[2]])] ) return hash(salt) 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.image, zorder=self.zorder) def hide(self): renpy.hide(name=self.tag, layer=self.layer) @functools.cache def build_image(self, hash): from itertools import chain sprites = list(chain.from_iterable( (self.body.build_image(self.body._hash), self.face.build_image(self.face._hash), self.cum.build_image(self.cum._hash), *(x[0].build_image(x[0]._hash) for x in self.clothes.values() if x[0] and x[2])) )) masks = [sprites.pop(sprites.index(x)) for x in sprites if x[0] == "mask"] sprites.sort(key=itemgetter(2)) masks.sort(key=itemgetter(2)) back_sprites = [x[1] for x in sprites if x[2] < 0] #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 masked = AlphaMask(Fixed(*(x[1] for x in sprites[:i]), fit_first=True), mask) sprites = sprites[i:] sprites.insert(0, (None, masked, mask_zorder)) break sprites = back_sprites + [x[1] for x in sprites] return Fixed(*sprites, fit_first=True) @property def image(self): if not renpy.is_skipping() and self.is_stale(): if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() return self.build_image(self._hash) def equip(self, obj, remove_old=True): """Takes DollCloth or DollOutfit object to equip.""" if isinstance(obj, DollCloth): self._equip_cloth(obj) elif isinstance(obj, DollOutfit): if remove_old: self.unequip("all") for cloth in obj.group: self._equip_cloth(cloth.parent, color=cloth.color) elif isinstance(obj, (list, tuple)): for cloth in obj: self._equip_cloth(cloth) self.rebuild_blacklist() update_chibi(self.name) self.cum.is_stale() self.is_stale() if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() def _equip_cloth(self, cloth, color=None): if cloth.type in self.multislots: for i in range(100): multitype = cloth.type + str(i) if multitype not in self.clothes or self.clothes[multitype][0] is None: zorder = self.clothes[cloth.type][1] self.clothes[multitype] = [cloth, zorder, True] break else: zorder = self.clothes[cloth.type][1] self.clothes[cloth.type] = [cloth, zorder, True] if self.is_blacklisted(cloth.type): self.unequip(*self.get_blacklister(cloth.type)) if cloth.blacklist: self.unequip(*cloth.blacklist) for tracking in self.get_trackers_list(cloth.type): tracking.is_stale() if color: cloth.set_color(color) cloth.is_stale() 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: v[0], v[2] = None, True else: for arg in args: if isinstance(arg, DollCloth): if arg.type in self.multislots: slot = next((k for k, v in self.clothes.items() if v[0] == arg), None) if not slot: continue self.clothes[slot][0] = None else: self.clothes[arg.type][0] = None else: if arg in self.multislots: for k, v in self.clothes.items(): if not k in self.blacklist_unequip and any((x in k) for x in self.multislots): v[0], v[2] = None, True else: if not arg in self.blacklist_unequip: self.clothes[arg][0] = None self.rebuild_blacklist() update_chibi(self.name) self.cum.is_stale() self.is_stale() if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() 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 update_chibi(self.name) self.is_stale() if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() 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 update_chibi(self.name) self.is_stale() if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() 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.""" if item.is_multislot(): return bool(next((k for k, v in self.clothes.items() if v[0] == item), False)) 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, *args, **kwargs): self.face.set_face(*args, **kwargs) [x[0].is_stale() for x in self.clothes.values() if isinstance(x[0], DollMakeup) and x[2]] self.cum.is_stale() def get_face(self): """Returns a dictionary containing currently set facial expressions. Used in character studio.""" return self.face._face # def set_body(self, **kwargs): # OBSOLETE! the code in scripts needs to be changed # """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.set_hue(arg) [x[0].is_stale() for x in self.clothes.values() if x[0] and x[2]] self.is_stale() if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() # def set_body_zorder(self, **kwargs): # OBSOLETE! the code in scripts needs to be changed # """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.""" self.cum.set_cum(*args, **kwargs) if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() def set_pose(self, pose): pose = "" if pose is None else os.path.join("poses", pose) self.pose = pose self.body.is_stale() self.face.is_stale() self.cum.is_stale() [x[0].is_stale() for x in self.clothes.values() if x[0] and x[2]] if renpy.showing(get_character_tag(self.name), layer=self.layer): self.show() 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 get_trackers_list(self, type): """Takes string cloth type. Returns a list of clothing types that report incompatibility.""" return [x[0] for x in self.clothes.values() if isinstance(x[0], DollClothDynamic) and type == x[0].tracking] 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 = Image(path) self.is_stale()