diff --git a/game/characters/cho/chibi/stand/0.webp b/game/characters/cho/chibi/stand/0/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/stand/0.webp rename to game/characters/cho/chibi/stand/0/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/stand/bottom/fallback/0.webp b/game/characters/cho/chibi/stand/0/clothes/bottom/default/overlay.webp similarity index 100% rename from game/characters/cho/chibi/stand/bottom/fallback/0.webp rename to game/characters/cho/chibi/stand/0/clothes/bottom/default/overlay.webp diff --git a/game/characters/cho/chibi/stand/bottom/school_skirt_1/0.webp b/game/characters/cho/chibi/stand/0/clothes/bottom/school_skirt_1/0.webp similarity index 100% rename from game/characters/cho/chibi/stand/bottom/school_skirt_1/0.webp rename to game/characters/cho/chibi/stand/0/clothes/bottom/school_skirt_1/0.webp diff --git a/game/characters/cho/chibi/stand/bra/basic_bra_1/0.webp b/game/characters/cho/chibi/stand/0/clothes/bra/basic_bra_1/0.webp similarity index 100% rename from game/characters/cho/chibi/stand/bra/basic_bra_1/0.webp rename to game/characters/cho/chibi/stand/0/clothes/bra/basic_bra_1/0.webp diff --git a/game/characters/cho/chibi/stand/0/clothes/bra/default/overlay.webp b/game/characters/cho/chibi/stand/0/clothes/bra/default/overlay.webp new file mode 100644 index 00000000..43a7c635 --- /dev/null +++ b/game/characters/cho/chibi/stand/0/clothes/bra/default/overlay.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:183923d268c2a33233ff8a3be265247ac06af3601df359c02d5cbcb6f094d799 +size 6860 diff --git a/game/characters/cho/chibi/stand/footwear/fallback/0.webp b/game/characters/cho/chibi/stand/0/clothes/footwear/default/overlay.webp similarity index 100% rename from game/characters/cho/chibi/stand/footwear/fallback/0.webp rename to game/characters/cho/chibi/stand/0/clothes/footwear/default/overlay.webp diff --git a/game/characters/cho/chibi/stand/panties/basic_panties_1/0.webp b/game/characters/cho/chibi/stand/0/clothes/panties/basic_panties_1/0.webp similarity index 100% rename from game/characters/cho/chibi/stand/panties/basic_panties_1/0.webp rename to game/characters/cho/chibi/stand/0/clothes/panties/basic_panties_1/0.webp diff --git a/game/characters/cho/chibi/stand/0/clothes/panties/default/overlay.webp b/game/characters/cho/chibi/stand/0/clothes/panties/default/overlay.webp new file mode 100644 index 00000000..0b6d637f --- /dev/null +++ b/game/characters/cho/chibi/stand/0/clothes/panties/default/overlay.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b152dd269de3da1ef6deb4d4fdfd2988bc35f74a4864282b6e37a73a04cd2fd +size 3234 diff --git a/game/characters/cho/chibi/stand/robe/robe_school_1/0.webp b/game/characters/cho/chibi/stand/0/clothes/robe/robe_school_1/0.webp similarity index 100% rename from game/characters/cho/chibi/stand/robe/robe_school_1/0.webp rename to game/characters/cho/chibi/stand/0/clothes/robe/robe_school_1/0.webp diff --git a/game/characters/cho/chibi/stand/robe/robe_school_1/0_back.webp b/game/characters/cho/chibi/stand/0/clothes/robe/robe_school_1/0_back.webp similarity index 100% rename from game/characters/cho/chibi/stand/robe/robe_school_1/0_back.webp rename to game/characters/cho/chibi/stand/0/clothes/robe/robe_school_1/0_back.webp diff --git a/game/characters/cho/chibi/stand/stockings/house/0.webp b/game/characters/cho/chibi/stand/0/clothes/stockings/house/outline.webp similarity index 100% rename from game/characters/cho/chibi/stand/stockings/house/0.webp rename to game/characters/cho/chibi/stand/0/clothes/stockings/house/outline.webp diff --git a/game/characters/cho/chibi/stand/top/top_school_1/0.webp b/game/characters/cho/chibi/stand/0/clothes/top/default/overlay.webp similarity index 100% rename from game/characters/cho/chibi/stand/top/top_school_1/0.webp rename to game/characters/cho/chibi/stand/0/clothes/top/default/overlay.webp diff --git a/game/characters/cho/chibi/stand/0/clothes/top/top_school_1/outline.webp b/game/characters/cho/chibi/stand/0/clothes/top/top_school_1/outline.webp new file mode 100644 index 00000000..d5722c03 --- /dev/null +++ b/game/characters/cho/chibi/stand/0/clothes/top/top_school_1/outline.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:353cae5ae13f2ef4d5b655fb3aac15c94a2b1a03fe7958afd0f07539b516efa4 +size 18626 diff --git a/game/characters/cho/chibi/walk/0.webp b/game/characters/cho/chibi/walk/0/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/0.webp rename to game/characters/cho/chibi/walk/0/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/1.webp b/game/characters/cho/chibi/walk/1/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/1.webp rename to game/characters/cho/chibi/walk/1/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/2.webp b/game/characters/cho/chibi/walk/2/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/2.webp rename to game/characters/cho/chibi/walk/2/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/3.webp b/game/characters/cho/chibi/walk/3/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/3.webp rename to game/characters/cho/chibi/walk/3/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/4.webp b/game/characters/cho/chibi/walk/4/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/4.webp rename to game/characters/cho/chibi/walk/4/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/5.webp b/game/characters/cho/chibi/walk/5/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/5.webp rename to game/characters/cho/chibi/walk/5/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/6.webp b/game/characters/cho/chibi/walk/6/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/6.webp rename to game/characters/cho/chibi/walk/6/bodyparts/frame/default/skin.webp diff --git a/game/characters/cho/chibi/walk/7.webp b/game/characters/cho/chibi/walk/7/bodyparts/frame/default/skin.webp similarity index 100% rename from game/characters/cho/chibi/walk/7.webp rename to game/characters/cho/chibi/walk/7/bodyparts/frame/default/skin.webp diff --git a/game/scripts/doll/bodypart.rpy b/game/scripts/doll/bodypart.rpy index 8fcacbdb..ddf07db4 100644 --- a/game/scripts/doll/bodypart.rpy +++ b/game/scripts/doll/bodypart.rpy @@ -19,8 +19,8 @@ init 1 python: return hash(salt) @functools.cache - def get_layers(self, hash): - path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, "bodyparts", self.type, self.id) + def get_layers(self, hash, subpath=""): + path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, subpath, "bodyparts", self.type, self.id) extensions = self.extensions types = self.layer_types diff --git a/game/scripts/doll/chibi.rpy b/game/scripts/doll/chibi.rpy index 11f0a30b..e58bd9d0 100644 --- a/game/scripts/doll/chibi.rpy +++ b/game/scripts/doll/chibi.rpy @@ -1,24 +1,11 @@ init 5 python: - # def pairwise(iterable): - # a = iter(iterable) - # return zip(a, a) + class DollChibi(renpy.Displayable, DollMethods): - class DollChibi(renpy.Displayable): - layers_additional = { - "back": -100, - "front": 100, - "skin": 0.1, - "extra": 1, - } - - extensions = [ ".jpg", ".jpeg", ".png", ".webp" ] - - def __init__(self, name, doll, pose="stand", layer="screens", zorder=12, zoom=0.28, *args, **properties): + def __init__(self, name, pose=None, layer="screens", zorder=12, zoom=0.28, *args, **properties): super(DollChibi, self).__init__(**properties) self.name = name - self.doll = doll self.pose = pose self.idle = "stand" self.layer = layer @@ -27,16 +14,19 @@ init 5 python: self.pos = (0,0) self.xzoom = 1 + self.char = eval(name) + self.poses = {} + self.animation = None # Animation + self.anim_frames = None + self.anim_sprites = None + self.anim_prev_sprites = None self.anim_speed = 1.0 self.anim_fps = 8.0 self.anim_trans = None self.anim_interval = None self.anim_interval_total = None - self.anim_path = None - - self.anim_constructor() # ATL self.atl_time = 0 @@ -46,101 +36,61 @@ init 5 python: self.atl_pause = False self.atl_at_list = [] - def anim_constructor(self): - """This function is responsible for creating an animation out of raw image files.""" - doll = self.doll + def register_pose(self, pose, frames): + self.poses[pose] = frames + print(f"Registered \"{pose}\" pose for {self.name}") - path = f"characters/{self.name}/chibi/{self.pose}" - files = [f for f in renpy.list_files() if f.startswith(path)] - images = [] - groups = {} + @functools.cache + def build_image(self, hash, pose, frame): + states = self.char.states + items = [v[0] for v in states.values() if v[0] and v[2]] - # Construct Animation - for fn in files: - root = os.path.dirname(fn) - name, ext = os.path.splitext(os.path.basename(fn)) - frame = name.split("_")[0] + sprites = [] + for i in items: + subpath = posixpath.join("chibi", pose, str(frame)) + sprites.extend(i.build_image(i._hash, subpath)) - if not ext.lower() in self.extensions: - continue + masks = [sprites.pop(sprites.index(x)) for x in sprites if x[0] == "mask"] - d = renpy.displayable(fn) + sprites.sort(key=itemgetter(2)) + masks.sort(key=itemgetter(2)) - if name.isdigit() and root.endswith(path): - # Create frame group if filename is a frame number and not a cloth - groups.setdefault(name, {}).setdefault(0, [d]) # Body layer is zorder zero - continue + back_sprites = [x[1] for x in sprites if x[2] < 0] - # Get frame id, clothing type, and clothing id - ctype, cid = root.rsplit("/")[-2::] + #Apply alpha mask + for m in masks: + _, mask, mask_zorder = m - if not frame in groups: - # Frame group is missing, skip construction - continue + for i, s in enumerate(sprites): + _, sprite, sprite_zorder = s - # Check if clothing type slot is occupied and currently visible - if (not doll.is_equipped(ctype) or - not doll.is_worn(ctype)): - continue + if i < 1 or mask_zorder > sprite_zorder: + continue - equipped = doll.get_equipped(ctype) - zorder = equipped.zorder + 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 - # Check if the supplied file is a special layer, - # in which case we need to treat it differently - for layer, modifier, in self.layers_additional.items(): - suffix = f"_{layer}" + sprites = back_sprites + [x[1] for x in sprites] - if name.endswith(suffix): - zorder += modifier - cid = cid.removesuffix(suffix) - break + return Fixed(*sprites, fit_first=True) - # Check if clothing type matches - if not equipped.type == ctype: - continue + def build_animation(self): + pose = self.pose + frames = self.poses[pose] + sprites = [self.build_image(self.char._hash, pose, i) for i in range(frames)] - # Check if clothing id matches, if not, add fallback image instead - if not equipped.id == cid: - fpath = f"{path}/{ctype}/fallback/0" - - for i in self.extensions: - f = fpath + i - - if renpy.loadable(f): - d = renpy.displayable(f) - break - - groups[frame].setdefault(zorder, []).append(d) - - # Iterate through frame groups and nested zorder groups - for fgroup in groups.values(): - igroup = [] - - # Sort zorder groups - for zgroup in dict(sorted(fgroup.items())).values(): - # Unpack zorder groups into displayables - igroup.append(Fixed(*zgroup, fit_first=True)) - - images.append(Fixed(*igroup, fit_first=True)) - - if not images: - raise IOError(f"Animation Constructor could not find any images inside directory:\n\n\"{path}\"") - - # For singular images it's optimal to halt the animation, - # instead of replaying the same frame over and over again. - frames = len(images) if frames > 1: interval = self.anim_speed / self.anim_fps else: interval = (365.25 * 86400.0) # About one year - self.anim_path = path self.anim_frames = frames self.anim_interval = interval self.anim_interval_total = (frames * interval) - self.anim_images = images - self.anim_prev_images = [ images[-1] ] + images[:-1] + self.anim_sprites = sprites + self.anim_prev_sprites = [ sprites[-1] ] + sprites[:-1] def render(self, width, height, st, at): @@ -154,7 +104,7 @@ init 5 python: if st > self.atl_time_total: renpy.timeout(0) - for image, prev in zip(self.anim_images, self.anim_prev_images): + for image, prev in zip(self.anim_sprites, self.anim_prev_sprites): if t < interval: if not renpy.game.less_updates: renpy.redraw(self, interval - t) @@ -198,14 +148,7 @@ init 5 python: return self.pose = pose - self.anim_constructor() - - def set_idle(self, pose): - if self.idle == pose: - return - - self.idle = pose - self.anim_constructor() + self.build_animation() def stop(self): # Freezes the animation @@ -355,7 +298,7 @@ init 5 python: # TODO: Using zorders is suboptimal and expensive, using 3D staging would be preferable. zpos = self.zorder + (1.0 / (self.pos[1] / config.screen_height)) - renpy.change_zorder(self.layer, self.name, zpos) + renpy.change_zorder(self.layer, self.char.name, zpos) return 0 def show(self, transform, at_list, layer, zorder): @@ -365,7 +308,11 @@ init 5 python: image = At(self, transform) # IMPORTANT: Enable perspective and gl_depth for 3D staging if not renpy.is_init_phase(): - renpy.show(name=self.name, what=image, at_list=at_list, layer=layer, zorder=zorder) + renpy.show(name=self.char.name, what=image, at_list=at_list, layer=layer, zorder=zorder) + + @property + def image(self): + return self init offset = 5 @@ -373,6 +320,8 @@ default hooch_chibi = DollChibi(name="hooch", doll=hooch) default cho_chibi_new = DollChibi(name="cho", doll=cho) label chibitest: + $ cho_chibi_new.register_pose("stand", 1) + $ cho_chibi_new.register_pose("walk", 8) "Rollback block" $ renpy.block_rollback() diff --git a/game/scripts/doll/clothes.rpy b/game/scripts/doll/clothes.rpy index b4e2f5e4..83e8b429 100644 --- a/game/scripts/doll/clothes.rpy +++ b/game/scripts/doll/clothes.rpy @@ -57,8 +57,8 @@ init python: return hash(salt) @functools.cache - def get_layers(self, hash): - path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, "clothes", self.type, self.id) + def get_layers(self, hash, subpath=""): + path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, subpath, "clothes", self.type, self.id) extensions = self.extensions types = self.layer_types @@ -120,7 +120,7 @@ init python: return layers @functools.cache - def build_image(self, hash, matrix=None): + def build_image(self, hash, subpath="", matrix=None): if matrix is None: matrix = self.char.body.hue @@ -131,7 +131,7 @@ init python: "default": lambda file, _: Image(file), } - layers = self.get_layers(hash) + layers = self.get_layers(hash, subpath) sprites = [] for identifier, (file, zorder) in layers.items(): diff --git a/game/scripts/doll/clothes_dynamic.rpy b/game/scripts/doll/clothes_dynamic.rpy index 8c7d9536..de103fc0 100644 --- a/game/scripts/doll/clothes_dynamic.rpy +++ b/game/scripts/doll/clothes_dynamic.rpy @@ -33,8 +33,8 @@ init python: return hash(salt) @functools.cache - def get_layers(self, hash, _ignore_equipped=False): - path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, "clothes", self.type, self.id) + def get_layers(self, hash, subpath="", _ignore_equipped=False): + path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, subpath, "clothes", self.type, self.id) _tracking = self._tracking def _negative_lookahead(): diff --git a/game/scripts/doll/cum.rpy b/game/scripts/doll/cum.rpy index 4e6b5eb2..37b5fd39 100644 --- a/game/scripts/doll/cum.rpy +++ b/game/scripts/doll/cum.rpy @@ -29,7 +29,7 @@ init python: self.is_stale() @functools.cache - def get_layers(self, hash): + def get_layers(self, hash, subpath=""): def _lookahead(path): return any(fp.startswith(path) for fp in renpy.list_files()) @@ -53,7 +53,7 @@ init python: 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, self.char.pose, "cum", part, name, identifier) + path = posixpath.join(self.char.modpath, "characters", self.char.name, self.char.pose, subpath, "cum", part, name, identifier) else: cloth, zorder, is_worn = active_clothes.get(part, [None, None, None]) @@ -108,7 +108,7 @@ init python: return layers @functools.cache - def build_image(self, hash, matrix=None): + def build_image(self, hash, subpath="", matrix=None): if matrix is None: matrix = self.char.body.hue @@ -117,7 +117,7 @@ init python: "default": lambda file: Image(file), } - layers = self.get_layers(hash) + layers = self.get_layers(hash, subpath) sprites = [] for identifier, (file, zorder) in layers.items(): diff --git a/game/scripts/doll/face.rpy b/game/scripts/doll/face.rpy index e41d622f..84e9822f 100644 --- a/game/scripts/doll/face.rpy +++ b/game/scripts/doll/face.rpy @@ -27,7 +27,7 @@ init python: return hash(salt) @functools.cache - def get_layers(self, hash): + def get_layers(self, hash, subpath=""): face = self._face extensions = self.extensions @@ -41,7 +41,7 @@ init python: if name is None: continue - path = posixpath.join(self.char.modpath, "characters", self.char.name, self.char.pose, "face", part, name) + path = posixpath.join(self.char.modpath, "characters", self.char.name, self.char.pose, subpath, "face", part, name) for f in renpy.list_files(): fp, fn = os.path.split(f) @@ -74,8 +74,8 @@ init python: return layers @functools.cache - def build_image(self, hash, matrix=None): - layers = self.get_layers(hash) + def build_image(self, hash, subpath="", matrix=None): + layers = self.get_layers(hash, subpath) eyemask = next((layers.pop(k, None) for k in layers if "eyemask" in k), [None])[0] if matrix is None: diff --git a/game/scripts/doll/makeup.rpy b/game/scripts/doll/makeup.rpy index 201a3f0c..3667dc35 100644 --- a/game/scripts/doll/makeup.rpy +++ b/game/scripts/doll/makeup.rpy @@ -13,14 +13,14 @@ init python: return hash(salt) @functools.cache - def get_layers(self, hash): + def get_layers(self, hash, subpath=""): tracking = self.char.face._face.get(self.tracking, None) if tracking is None: print(f"Invalid tracker for object: {self}") return [] - path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, "clothes", self.type, self.id, tracking) + path = posixpath.join(self.modpath, "characters", self.name, self.char.pose, subpath, "clothes", self.type, self.id, tracking) extensions = self.extensions types = self.layer_types