WTS/game/scripts/doll/outfits.rpy
LoafyLemon c1b04f6ccd IO Overhaul, Refactoring, and more
* Refactored DollFace
* Refactored DollBody
* Refactored DollCum
* Refactored DollCloth
* Refactored Doll
* Refactored clothing item zorders
* Refactored implementation of body, face, cum, clothing layers
* Refactored function calls
* Removed DollLipstick
* Added DollMakeup class, allowing adding dynamic clothes tracking face states
* Added DollClothDynamic, allowing dynamic clothes tracking other cloth states with bangs support
* Added cache to frequently called functions, drastically reducing the overhead
* Added hash system, reducing clone redundancy
* Added layer modifiers support for all types (face, body, cum, clothes etc.)
* Added support for an arbitrary number of equipped multislot clothing items (makeup, tattoos, piercings, etc.)
* Simplified initialization for clothing items and dolls
* Simplified class function calls
* Reduced the number of image creation calls
* Added hue support for additional skin layers
* Added displayable support to image cropping function
* Replaced store cache with built-in functools cache for _list_files function
* Refactored all character files
* and more...
2023-01-14 23:04:54 +00:00

208 lines
7.4 KiB
Plaintext

init python:
class DollOutfit(DollMethods):
default_schedule = {"day": False, "night": False, "cloudy": False, "rainy": False, "snowy": False}
def __init__(self, group, unlocked=False, name="", desc="", price=0, temp=False, schedule={}, hidden=False, addons=[]):
self.group = [x.clone() if not x.parent else x for x in group]
self.name = name
self.desc = desc
self.price = price
self.char = self.group[0].char
self.unlocked = unlocked
self.schedule = dict(list(self.default_schedule.items()) + list(schedule.items()))
self.temp = temp
self.hidden = hidden
self.addons = addons
self._hash = self.generate_hash()
if not self.temp:
if unlocked:
self.unlock()
if not self.hidden and not self in self.char.outfits:
self.char.outfits.append(self)
def __hash__(self):
return self._hash
def __eq__(self, obj):
if not isinstance(obj, DollOutfit):
return NotImplemented
return self._hash == obj._hash
def generate_hash(self):
salt = str( sorted( [sorted([x.name, x.type, x.id, str(x.color)] ) for x in self.group]) )
return hash(salt)
def delete(self):
if self in self.char.outfits:
self.char.outfits.remove(self)
@functools.cache
async def build_image(self):
# Add body, face, cum, clothes, masks
matrix = SaturationMatrix(0.0)
async def build_clothes(group):
sprites = []
masks = []
for i in group:
for identifier, layer, zorder in i.build_image(i._hash, matrix):
if identifier == "mask":
masks.append((layer, zorder))
else:
sprites.append((layer, zorder))
return (sprites, masks)
async def build_mannequin():
sprites = self.char.body.build_image(matrix)
sprites.sort(key=itemgetter(2))
sprites = [(x[1], x[2]) for x in sprites]
return sprites
mannequin, (clothes, masks) = await asyncio.gather(
build_mannequin(),
build_clothes(self.group),
)
sprites = [
*mannequin,
*clothes,
]
# Filter out Nulls
sprites = [x for x in sprites if not isinstance(x[0], Null)]
sprites.sort(key=itemgetter(1))
masks.sort(key=itemgetter(1))
# Filter out sprites with zorder less than zero, there's no need to iterate over them.
back_sprites = [x[0] for x in sprites if x[1] < 0]
sprites = [x for x in sprites if x[1] > -1]
# 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
c = tuple(x[0] for x in sprites[:i] if not isinstance(x[0], Null))
masked = AlphaMask(Fixed(*c, fit_first=True), mask)
sprites = sprites[i:]
sprites.insert(0, (masked, mask_zorder))
break
sprites = back_sprites + [x[0] for x in sprites]
self._image = Fixed(*sprites, fit_first=True)
return
@property
def image(self):
if not renpy.is_skipping():
if not self._image_cached:
self._image_cached = True
asyncio.run(self.build_image())
return self._image
def exists(self):
return (self in self.char.outfits)
def export_data(self, filename, tofile=True):
"""Exports outfit to .png file or clipboard text."""
exported = [self.group[0].name]
exported.extend([x.id, x.color] for x in self.group)
# Encode data
if tofile:
path = "{}/game/outfits/".format(config.basedir)
fn = "{}.png".format(filename)
if not os.path.exists(path):
os.makedirs(path)
d = Transform(self.get_image(), crop=(210, 200, 700, 1000), anchor=(0.5, 1.0), align=(0.5, 1.0), xsize=310, ysize=470, fit="contain")
d = Fixed(
"interface/wardrobe/export_background.webp",
d,
"interface/wardrobe/export_frame.webp",
Text(active_girl, align=(0.5, 0.995)),
Text("Ver. {}".format(config.version), size=10, align=(0.99, 0.99))
)
displayable_to_file(d, path+fn, size=(310, 470) )
image_payload.encode(filename, str(exported))
else:
set_clipboard(exported)
renpy.notify("Export successful!")
def unlock(self):
"""Unlocks outfit and respective clothing objects from which they were cloned."""
self.unlocked = True
for i in self.group:
i.unlock()
for i in self.addons:
i.unlock()
def save(self):
"""Overwrites this outfit with clothes currently equipped by the character."""
self.group = []
for v in self.char.clothes.values():
if v[0]:
self.group.append(v[0].clone())
self.rebuild_image()
return
def is_modded(self):
"""Returns True if one of the group items comes from a mod."""
for i in self.group:
if i.is_modded():
return True
return False
def get_modname(self):
"""Returns a list of mods contained within the outfit group."""
return list(set([i.get_modname() for i in self.group if i.is_modded()]))
def get_schedule(self):
"""Returns a dictionary with the current schedule."""
return self.schedule
def set_schedule(self, **kwargs):
for k, v in kwargs.items():
self.schedule[k] = v
def has_type(self, *args):
"""Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise."""
types = set(x.type for x in self.group)
for arg in args:
if arg in self.multislots:
if not any(x.startswith(arg) for x in types):
return False
else:
if not arg in types:
return False
return True
def has_any_type(self, *args):
"""Takes arguments containing string cloth types. Returns True if ANY of them is worn, False otherwise."""
if "clothes" in args:
for k in self.char.clothes.keys():
if not k.startswith(self.blacklist_toggles):
if self.has_type(k):
return True
else:
for arg in args:
if self.has_type(arg):
return True
return False