* Reduced memory footprint by excluding DollCloth and DollOutfit instances from directly participating in rollback unless reachable through renpy scope
178 lines
6.3 KiB
Plaintext
178 lines
6.3 KiB
Plaintext
init python:
|
|
class DollOutfit(DollMethods, SlottedNoRollback):
|
|
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( [self.name, str([x._hash for x in self.group]) ] )
|
|
return hash(salt)
|
|
|
|
def delete(self):
|
|
if self in self.char.outfits:
|
|
self.char.outfits.remove(self)
|
|
|
|
@functools.cache
|
|
def build_image(self, hash):
|
|
from itertools import chain
|
|
|
|
matrix = SaturationMatrix(0.0)
|
|
|
|
sprites = list(chain.from_iterable(
|
|
(self.char.body.build_image(self.char.body._hash, matrix=matrix),
|
|
*(x.build_image(x._hash, matrix=matrix) for x in self.group))
|
|
))
|
|
|
|
masks = [sprites.pop(sprites.index(x)) for x in sprites if x[0] == "mask"]
|
|
|
|
sprites.sort(key=itemgetter(2))
|
|
masks.sort(key=itemgetter(2))
|
|
|
|
back_sprites = [x[1] for x in sprites if x[2] < 0]
|
|
|
|
#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
|
|
|
|
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
|
|
|
|
sprites = back_sprites + [x[1] for x in sprites]
|
|
|
|
return Fixed(*sprites, fit_first=True)
|
|
|
|
@property
|
|
def image(self):
|
|
return self.build_image(self._hash)
|
|
|
|
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]
|
|
|
|
for i in self.group:
|
|
if i.color:
|
|
color = [j.hexcode for j in i.color]
|
|
exported.append([i.id, color])
|
|
|
|
# Encode data
|
|
if tofile:
|
|
path = os.path.join(config.gamedir, "outfits")
|
|
|
|
if not os.path.exists(path):
|
|
os.makedirs(path)
|
|
|
|
path = os.path.join(path, "_temp.png")
|
|
|
|
d = Transform(self.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, size=(310, 470) )
|
|
ImagePayload().inject("_temp.png", filename, str(exported))
|
|
os.remove(path)
|
|
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 = [x[0].clone() for x in self.char.states.values() if x[0]]
|
|
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.states.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
|