WTS/game/scripts/doll/clothes.rpy

330 lines
12 KiB
Plaintext
Raw Normal View History

2022-05-16 23:48:22 +00:00
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:\n\"{}0.webp\"".format(path))
2022-05-16 23:48:22 +00:00
def set_layers(self):
for x in self.layers_special:
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] = []
2022-09-21 20:57:04 +00:00
for i in range(self.layers):
2022-05-16 23:48:22 +00:00
path = "{}{}_{}.webp".format(self.imagepath, i, x)
if renpy.loadable(path):
self.__dict__[x].append(path)
else:
self.__dict__[x].append(None)
2022-05-16 23:48:22 +00:00
path = "{}outline_{}.webp".format(self.imagepath, x)
self.__dict__[x+"_outline"] = path if renpy.loadable(path) else None
def build_image(self):
2022-09-21 20:57:04 +00:00
sprites = [(self.apply_color("{}{}.webp".format(self.imagepath, x), x), x) for x in range(self.layers)]
2022-05-16 23:48:22 +00:00
# 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.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_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
2022-05-16 23:48:22 +00:00
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
2022-05-16 23:48:22 +00:00
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")
2022-05-16 23:48:22 +00:00
elif isinstance(n, list):
self.color = [x[:] for x in n]
self.rebuild_image()
self.char.rebuild_image()
2022-05-16 23:48:22 +00:00
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()
class DollClothDummy(DollCloth):
def __init__(self, name, type, *args, **kwargs):
super(DollClothDummy, self).__init__(name=name, categories=("dummy", "dummy"), type=type, id="dummy", color=[[0,0,0,0]], *args, **kwargs)
self.cached = True
self.cached_icon = True
def set_imagepath(self):
if any(x in self.type for x in self.multislots):
subpath = self.type[:-1]
else:
subpath = self.type
virt_path = "{}/characters/{}/clothes/{}/{}/".format(self.modpath, self.name, subpath, self.id)
self.imagepath = virt_path
def get_icon(self):
return self.ico
def get_image(self):
return self.sprite
def clone(self):
return DollClothDummy(self.name, self.type, parent=self)