LoafyLemon
8994c77109
* Implemented zorder control for specific clothing layers * Fixed Tonks' breasts missing shading.
329 lines
13 KiB
Plaintext
329 lines
13 KiB
Plaintext
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()
|