forked from SilverStudioGames/WTS
LoafyLemon
9b771d1a77
* Fixed character calls breaking when the supplied number of expressions was less than 4, from now on expressions must be supplied as keyword arguments (this is already internally enforced)
115 lines
3.8 KiB
Plaintext
115 lines
3.8 KiB
Plaintext
init python:
|
|
class DollFace(DollMethods):
|
|
layer_types = {
|
|
"eyemask": -1,
|
|
"skin": 1,
|
|
"expression": None,
|
|
}
|
|
|
|
layer_modifiers = {
|
|
"zorder": None,
|
|
}
|
|
|
|
def __init__(self, obj):
|
|
self.char = obj
|
|
self._face = {k: None for k in self.char.face_layers.keys()}
|
|
self._hash = None
|
|
|
|
def set_face(self, **kwargs):
|
|
self._face.update((k, v) for k, v in kwargs.items() if not v is None)
|
|
self.is_stale()
|
|
|
|
def generate_hash(self):
|
|
salt = str( [self.char.name, self.char.pose, str(self.char.body._hash), sorted(list(self._face.items()))] )
|
|
return hash(salt)
|
|
|
|
@functools.cache
|
|
def get_layers(self, hash, subpath=""):
|
|
face = self._face
|
|
|
|
extensions = self.extensions
|
|
types = self.layer_types
|
|
modifiers = self.layer_modifiers
|
|
face_layers = self.char.face_layers
|
|
|
|
layers = {}
|
|
for part, name in face.items():
|
|
|
|
if name is None:
|
|
continue
|
|
|
|
path = posixpath.join(self.char.modpath, "characters", self.char.name, "poses", self.char.pose, subpath, "face", part, name)
|
|
|
|
for f in renpy.list_files():
|
|
fp, fn = os.path.split(f)
|
|
fn, ext = os.path.splitext(fn)
|
|
|
|
if not fp == path or not ext in extensions:
|
|
continue
|
|
|
|
ltype, *tails = fn.rsplit("_")
|
|
|
|
if not ltype in types:
|
|
print("Invalid layer type for file: {}".format(f))
|
|
continue
|
|
|
|
zorder = types.get(ltype) or face_layers.get(part)
|
|
|
|
if tails:
|
|
lmodifier, *tails = tails
|
|
|
|
if not lmodifier in modifiers:
|
|
print("Invalid modifier for file: {}".format(f))
|
|
continue
|
|
|
|
zorder_mod = modifiers.get(lmodifier)
|
|
zorder = (zorder + int(zorder_mod))
|
|
layers.setdefault(" ".join([part, name, ltype, lmodifier]), [f, zorder])
|
|
else:
|
|
layers.setdefault(" ".join([part, name, ltype]), [f, zorder])
|
|
|
|
return layers
|
|
|
|
@functools.cache
|
|
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:
|
|
matrix = self.char.body.hue
|
|
|
|
processors = {
|
|
"skin": lambda file: Transform(file, matrixcolor=matrix),
|
|
"pupils": lambda file: AlphaMask(file, eyemask),
|
|
"default": lambda file: Image(file),
|
|
}
|
|
|
|
sprites = []
|
|
for identifier, (file, zorder) in layers.items():
|
|
|
|
expr_type, name, ltype = identifier.rsplit(" ")
|
|
|
|
if expr_type == "pupils":
|
|
if not eyemask:
|
|
continue
|
|
processor = processors["pupils"]
|
|
else:
|
|
processor = processors.get(identifier, processors["default"])
|
|
|
|
processed_file = processor(file)
|
|
sprites.append((identifier, processed_file, zorder))
|
|
|
|
return sprites
|
|
|
|
@property
|
|
def image(self):
|
|
if not renpy.is_skipping() and self.is_stale():
|
|
hash = self._hash
|
|
|
|
sprites = self.build_image(hash)
|
|
sprites.sort(key=itemgetter(2))
|
|
sprites = [x[1] for x in sprites]
|
|
|
|
self._image = Fixed(*sprites, fit_first=True)
|
|
return self._image
|