WTS/game/scripts/doll/outfits.rpy
2024-11-09 12:10:08 +00:00

281 lines
11 KiB
Plaintext

init python:
class DollOutfit(DollMethods):
default_schedule = {"day": False, "night": False, "cloudy": False, "rainy": False, "snowy": False}
_loading = Fixed(Text("Loading", align=(0.5, 0.5)), xysize=(96, 168))
__slots__ = ("group", "name", "desc", "price", "char", "unlocked", "schedule", "temp", "hidden", "addons", "_hash", "_button")
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.group.sort()
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()
self._button = DefaultQueue()
self.imported = False
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( [x._hash for x in self.group] ) + str(self.schedule)
return hashstring(salt)
def delete(self):
if self in self.char.outfits:
self.char.outfits.remove(self)
if self.imported:
try:
os.remove(os.path.join(config.gamedir, self.imported))
except:
print(f"Warning! Failed to delete imported outfit file - {self.imported}")
@functools.cache
def build_image(self, hash):
if (d := self.get_disk_cache(hash)):
return d
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]
d = Fixed(*sprites, fit_first=True)
return self.create_disk_cache(d, hash)
@property
def image(self):
return self.build_image(self._hash)
@functools.cache
def build_icon(self, hash):
sprite = self.build_image(self._hash)
return Transform(AlphaMask(Transform(sprite, crop=(250, 0, 680, 1200), fit="scale-down", ysize=168), Transform("wheelmenu_frame_opaque_vertical", xysize=(96, 168)), yalign=1.0, fit_first=True))
# 730x1020
@property
def icon(self):
return self.build_icon(self._hash)
def _build_button(self, _hash):
style = "wardrobe_item_rectangular_button"
is_equipped = self.char.is_equipped_item(self)
is_modded = self.is_modded()
# is_inadequate = wardrobe.wardrobe_check_equip_outfit(self)
has_schedule = any(self.schedule.values())
children = [self.icon]
warnings = []
hbox = []
vbox = []
overlay = []
if is_modded:
warnings.append("Outfit contains items from these mods:\n{size=-4}{color=#35aae2}"+ "\n".join(self.get_modname()) + "{/color}{/size}")
hbox.append(Text("🛠️", align=(0.5, 0.5), style="wardrobe_icon_text"))
if self.imported:
warnings.append("This outfit has an associated outfit file in /outfits/ directory.")
hbox.append(Text("📦", align=(0.5, 0.5), style="wardrobe_icon_text"))
if renpy.android:
action = [Function(wardrobe.wheelmenu, self, "outfits"), self.build_button]
unhovered = None
alternate = None
else:
action = [Function(wardrobe.equip, self), self.build_button]
unhovered = None
alternate = [Function(wardrobe.wheelmenu, self, "outfits"), self.build_button]
if has_schedule:
for i, emoji in wardrobe.outfit_schedule.items(): # NONLOCAL
if self.schedule[i]:
vbox.append(Text(emoji, align=(0.5, 0.5), offset=(3, 6), style="wardrobe_icon_text"))
if vbox:
children.append(VBox(*vbox, ypos=34))
if hbox:
children.append(HBox(*hbox))
return Button(child=Fixed(*children, xysize=(96, 168)), focus_mask=None, xysize=(96, 168), action=action, tooltip=("\n".join(warnings)), unhovered=unhovered, style=style, selected=is_equipped, alternate=alternate)
@functools.cache
def build_button(self):
def _func(self, hash):
result = self._build_button(self._hash)
self._button.put(result)
thread = DollThread(target=_func, args=(self, self._hash))
thread.start()
@property
def button(self):
if settings.get("multithreading"):
return self._button.get_with_default(self._loading)
else:
global current_subcategory
return self._build_button(self._hash)
def clear_button_cache(self):
self.build_button.cache_clear()
def exists(self):
return (self in self.char.outfits)
def export_data(self, filename, tofile=True):
"""Exports outfit to .png file or clipboard text."""
# Export data consists of the following:
# [0] - Character name
# [1] - [Item ID, Colour List]
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:
fp_input = os.path.join("game", "outfits").replace("\\", "/") # Ren'py Paths are UNIX based
fn_input = f"{self._hash}_temp.png"
fn_output = f"{self._hash}.png"
fpt_output = os.path.join(fp_input, fn_input).replace("\\", "/") # Ren'py Paths are UNIX based
osfp = os.path.join(config.basedir, fpt_output) # Python paths are OS based
if not os.path.exists(fp_input):
os.makedirs(fp_input)
width, height = self.sizes
d = self.build_image(self._hash)
# d = Transform(d, crop=(220, 0, 680, 1200), size=(96, 168), fit="contain", align=(0.5, 1.0), yoffset=-6)
d = Fixed(
d,
Text(states.active_girl, align=(0.5, 0.995)),
Text(f"Ver. {config.version}", size=24, align=(0.99, 0.99))
)
try:
renpy.render_to_file(d, fpt_output, width=width, height=height, resize=True)
image_payload.inject(fn_input, fn_output, str(exported))
os.remove(osfp) # Remove temp file
except Exception as e:
print(f"Warning! Failed to write to path: {fp}/{fn_input} - {e}")
renpy.notify("Export failed!")
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