WTS/game/scripts/doll/main.rpy

462 lines
17 KiB
Plaintext
Raw Normal View History

2022-05-16 23:48:22 +00:00
init python:
import asyncio
2022-05-16 23:48:22 +00:00
class Doll(DollMethods):
def __init__(self, name, clothes, face, body):
self.wardrobe = {}
self.wardrobe_list = []
self.blacklist = []
self.outfits = []
self.name = name
self.clothes = clothes
self.face = DollFace(self, face)
self.body = DollBody(self, body)
self.cum = DollCum(self)
self.pose = None
self.emote = Null()
# Image properties
self.zorder = 15
self.layer = "screens"
self.animation = None
2022-05-16 23:48:22 +00:00
self.tag = get_character_tag(name)
# Transform properties
self.pos = (0, 0)
self.zoom = 0.5
self.xzoom = 1
self.align = (0.5, 1.0)
def rebuild(self):
"""Rebuild character image cache."""
self.body.rebuild_image()
self.face.rebuild_image()
self.cum.rebuild_image()
for o in self.wardrobe_list:
o.rebuild_image()
o.rebuild_icon()
for o in self.outfits:
o.rebuild_image()
self.rebuild_image()
def rebuild_image(self):
self.cached = False
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
def show(self):
if renpy.get_screen(("wardrobe", "animatedCG", "studio")):
return
base_transform = doll_transform(self.pos, self.zoom, self.xzoom)
animation = self.animation
2022-05-16 23:48:22 +00:00
at_list = [base_transform]
if animation:
at_list.append(animation)
renpy.show(name=self.tag, at_list=at_list, layer=self.layer, what=self.get_image(), zorder=self.zorder)
2022-05-16 23:48:22 +00:00
def hide(self):
renpy.hide(name=self.tag, layer=self.layer)
def make_image(self):
asyncio.run(self.build_image())
async def build_image(self):
2022-05-16 23:48:22 +00:00
# Add body, face, cum, clothes, masks
async def build_clothes(clothes):
sprites = []
masks = []
for i in clothes.values():
obj, _, is_worn = i
if not obj is None and is_worn:
zorder = obj.zorder
sprites.extend([
(obj.get_image(), zorder),
obj.get_back(),
obj.get_front(),
obj.get_armfix(),
])
2022-05-16 23:48:22 +00:00
if obj.mask:
masks.append((obj.mask, zorder-1))
return (sprites, masks)
2022-05-16 23:48:22 +00:00
async def build_face():
return (self.face.get_image(), 1)
async def build_body():
return (self.body.get_image(), 0)
async def build_cum(zorder):
return (self.face.get_image(), zorder)
face, body, cum, (clothes, masks) = await asyncio.gather(
build_cum(self.cum.zorder_cum),
build_body(),
build_face(),
build_clothes(self.clothes),
)
sprites = [
face,
body,
cum,
*clothes,
(self.emote, 1000)
]
2022-05-16 23:48:22 +00:00
# Filter out Nulls
sprites = [x for x in sprites if not isinstance(x[0], Null)]
2022-05-16 23:48:22 +00:00
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]
2022-05-16 23:48:22 +00:00
# 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.sprite = DollDisplayable(Fixed(*sprites, fit_first=True))
return
2022-05-16 23:48:22 +00:00
def equip(self, obj, remove_old=True):
"""Takes DollCloth or DollOutfit object to equip."""
if isinstance(obj, DollCloth):
self.clothes[obj.type][0] = obj
self.clothes[obj.type][2] = True
if self.is_blacklisted(obj.type):
self.unequip(*self.get_blacklister(obj.type))
if obj.blacklist:
self.unequip(*obj.blacklist)
if self.pose:
obj.set_pose(self.pose)
elif isinstance(obj, DollOutfit):
if remove_old:
self.unequip("all")
for i in obj.group:
self.clothes[i.type][0] = i.parent
self.clothes[i.type][0].set_color(i.color)
if self.pose:
i.parent.set_pose(self.pose)
elif isinstance(obj, (list, tuple)):
for cloth in obj:
self.clothes[cloth.type][0] = cloth
self.clothes[cloth.type][2] = True
if self.is_blacklisted(cloth.type):
self.unequip(*self.get_blacklister(cloth.type))
if cloth.blacklist:
self.unequip(*cloth.blacklist)
if self.pose:
cloth.set_pose(self.pose)
2022-05-16 23:48:22 +00:00
self.body.rebuild_image()
self.rebuild_image()
self.rebuild_blacklist()
update_chibi(self.name)
def unequip(self, *args):
"""Takes argument(s) containing string cloth type(s) to unequip."""
if "all" in args:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if not k in self.blacklist_unequip:
if self.pose and v[0]:
v[0].set_pose(None)
v[0], v[2] = None, True
else:
for arg in args:
if not arg in self.blacklist_unequip:
if self.pose and self.clothes[arg][0]:
self.clothes[arg][0].set_pose(None)
self.clothes[arg][0] = None
self.body.rebuild_image()
self.rebuild_image()
self.rebuild_blacklist()
update_chibi(self.name)
def get_equipped(self, type):
"""Takes argument containing string cloth type. Returns equipped object for cloth type."""
return self.clothes[type][0]
def get_equipped_item(self, items):
"""Returns first equipped item from a list or None."""
for i in items:
if self.is_equipped_item(i):
return i
return None
def strip(self, *args):
"""Takes argument(s) containing string cloth type(s) to temporarily displace (hide)."""
if "all" in args:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if not k.startswith(self.blacklist_toggles):
v[2] = False
else:
for arg in args:
if arg in self.multislots:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if k.startswith(arg):
v[2] = False
else:
self.clothes[arg][2] = False
self.body.rebuild_image()
self.rebuild_image()
update_chibi(self.name)
def wear(self, *args):
"""Takes argument(s) containing string cloth type(s) to put on (unhide)."""
if "all" in args:
if self.is_worn("all"):
return
2022-09-21 20:57:04 +00:00
for v in self.clothes.values():
2022-05-16 23:48:22 +00:00
v[2] = True
else:
for arg in args:
if arg in self.multislots:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if k.startswith(arg):
v[2] = True
else:
self.clothes[arg][2] = True
self.body.rebuild_image()
self.rebuild_image()
update_chibi(self.name)
def is_equipped(self, *args):
"""Takes argument containing string cloth type. Returns True if slot is occupied, False otherwise."""
for arg in args:
if arg in self.multislots:
2022-09-21 20:57:04 +00:00
return any(bool(v[0]) for k, v in self.clothes.items() if k.startswith(arg))
2022-05-16 23:48:22 +00:00
else:
if not self.clothes.get(arg, [None])[0]:
2022-05-16 23:48:22 +00:00
return False
return True
def is_any_equipped(self, *args):
"""Takes arguments containing string cloth types. Returns True if ANY of them is equipped, False otherwise."""
if "clothes" in args:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if not k.startswith(self.blacklist_toggles):
if self.is_equipped(k):
return True
else:
for arg in args:
if self.is_equipped(arg):
return True
return False
def is_equipped_item(self, item):
"""Takes DollCloth object or list of objects. Returns True if item is equipped, False otherwise."""
return self.get_equipped(item.type) == item
def is_worn(self, *args):
"""Takes argument(s) containing string cloth type(s). Returns True if worn, False otherwise."""
if "all" in args:
2022-09-21 20:57:04 +00:00
for v in self.clothes.values():
2022-05-16 23:48:22 +00:00
if not v[2]:
return False
else:
for arg in args:
if arg in self.multislots:
2022-09-21 20:57:04 +00:00
return any( (v[0] and v[2]) for k, v in self.clothes.items() if k.startswith(arg))
2022-05-16 23:48:22 +00:00
else:
if not self.clothes[arg][0] or not self.clothes[arg][2]:
return False
return True
def is_any_worn(self, *args):
"""Takes arguments containing string cloth types. Returns True if ANY of them is worn, False otherwise."""
if "clothes" in args:
2022-09-21 20:57:04 +00:00
for k, v in self.clothes.items():
2022-05-16 23:48:22 +00:00
if not k.startswith(self.blacklist_toggles):
if self.is_worn(k):
return True
else:
for arg in args:
if self.is_worn(arg):
return True
return False
def set_face(self, **kwargs):
"""Takes keyword argument(s) with the string name of expression file(s)."""
if self.face.set_face(**kwargs):
self.body.rebuild_image()
# Rebuild lipstick
lipstick = self.clothes.get("makeup4", [None, 1, True])[0]
if isinstance(lipstick, DollLipstick):
lipstick.rebuild_image()
self.rebuild_image()
def get_face(self):
"""Returns a dictionary containing currently set facial expressions. Used in character studio."""
return self.face.get_face()
def set_body(self, **kwargs):
"""Takes keyword argument(s) with the string name of body part file(s)."""
if self.body.set_body(**kwargs):
self.rebuild_image()
def set_body_hue(self, arg):
"""Takes integer between 0 - 359, rotates the character body colour by given amount."""
self.body.hue = arg
self.body.rebuild_image()
self.rebuild_image()
def set_body_zorder(self, **kwargs):
"""Takes keyword argument(s) with the name(s) of body part(s) and integer value(s)"""
if self.body.set_zorder(**kwargs):
self.rebuild_image()
def set_cum(self, *args, **kwargs):
"""Takes keyword argument(s) containing string name(s) of cum layers to apply or None."""
if self.cum.set_cum(*args, **kwargs):
self.body.rebuild_image()
self.rebuild_image()
def set_pose(self, pose):
if pose is None or renpy.loadable("characters/{}/poses/{}/loadable.webp".format(self.name, pose)):
self.pose = pose
self.face.set_pose(pose)
self.body.set_pose(pose)
self.cum.set_pose(pose)
2022-09-21 20:57:04 +00:00
for v in self.clothes.values():
2022-05-16 23:48:22 +00:00
if v[0]:
v[0].set_pose(pose)
self.rebuild_image()
else:
raise Exception("'{}' pose doesn't exist for character named '{}'.".format(pose, self.name))
def rebuild_blacklist(self):
blacklist = []
2022-09-21 20:57:04 +00:00
for v in self.clothes.values():
2022-05-16 23:48:22 +00:00
if v[0]:
blacklist.extend(v[0].blacklist)
self.blacklist = list(set(blacklist))
def is_blacklisted(self, type):
"""Takes string cloth type. Returns True if cloth type is blacklisted."""
return True if type in self.blacklist else False
def get_blacklister(self, type):
"""Takes string cloth type. Returns a list of clothing types that report incompatibility."""
2022-09-21 20:57:04 +00:00
return [x[0].type for x in self.clothes.values() if x[0] and type in x[0].blacklist]
2022-05-16 23:48:22 +00:00
def create_outfit(self, temp=False):
"""Creates a copy of the current character clothes and stores it."""
2022-09-21 20:57:04 +00:00
return DollOutfit([x[0] for x in self.clothes.values() if x[0]], True, temp=temp)
2022-05-16 23:48:22 +00:00
def import_outfit(self, path, fromfile=True):
"""Imports outfit from .png file or clipboard text."""
# Grab data
if fromfile:
try:
imported = image_payload.decode(path)
except:
if image_payload._file:
image_payload._file.close()
renpy.notify("Import failed: Corrupted file.")
return None
else:
imported = get_clipboard()
# Evaluate data
if imported:
try:
imported = make_revertable(evaluate(imported))
except:
renpy.notify("Import failed: Corrupted outfit data.")
renpy.block_rollback()
return None
group = []
for i, x in enumerate(imported):
if i == 0 and not x == self.name:
renpy.notify("Import failed: Wrong character.")
return None
for o in self.wardrobe_list:
if x[0] == o.id:
if not o.unlocked and not game.cheats:
renpy.notify("Import failed: You don't own these items. Buy them first.")
return None
x[0] = o.clone()
x[0].set_color(x[1])
group.append(x[0])
if group:
renpy.notify("Import successful!")
return DollOutfit(group, True)
renpy.notify("Import failed: Unknown error.")
return None
def get_schedule(self):
"""Returns a list of outfits available for current time of day and weather conditions."""
schedule = []
for o in self.outfits:
if o.unlocked and o.schedule["day" if game.daytime else "night"]:
if game.weather == "overcast" and o.schedule["cloudy"]:
schedule.append(o)
elif game.weather in {"storm", "rain"} and o.schedule["rainy"]:
schedule.append(o)
elif game.weather in {"snow", "blizzard"} and o.schedule["snowy"]:
schedule.append(o)
elif game.weather in {"clear", "cloudy"} and not (o.schedule["cloudy"] or o.schedule["rainy"] or o.schedule["snowy"]):
schedule.append(o)
return schedule
def equip_random_outfit(self):
"""Equips random outfit based on Outfits Schedule."""
schedule = self.get_schedule()
if schedule:
self.equip(renpy.random.choice(schedule))
def set_emote(self, emote):
if not emote and not isinstance(emote, Null):
self.emote = Null()
return
if self.pose:
path = "characters/{}/poses/{}/emote/{}.webp".format(self.name, self.pose, emote)
else:
path = "characters/{}/emote/{}.webp".format(self.name, emote)
self.emote = DollDisplayable(Image(path))