WTS/game/scripts/doll/main.rpy

482 lines
18 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):
# 0 - 50 = Skin Layers
# 51 - 100 = Face Layers
# 101 - 300+ = Clothes Layers
clothing_layers = {
"buttplug": -11,
"makeup": 111, # multislot
"accessory": 121, # multislot
"piercing": 131, # multislot
"tattoo": 141, # multislot
"pubes": 151,
"stockings": 161,
"panties": 171,
"garterbelt": 181,
"bottom": 191,
"bra": 201,
"top": 211,
"gloves": 221,
"robe": 231,
"neckwear": 241,
"hair": 251,
"earrings": 261,
"glasses": 271,
"headgear": 281
}
face_layers = {
"tears": 75,
"eyebrows": 70,
"pupils": 65,
"eyes": 60,
"mouth": 55,
"cheeks": 51
}
def __init__(self, name):
2022-05-16 23:48:22 +00:00
self.wardrobe = {}
self.wardrobe_list = []
self.blacklist = []
self.outfits = []
self.name = name
self.clothes = {k: [None, v, True] for k, v in self.clothing_layers.items()}
self.face = DollFace(self)
self.body = DollBody(self)
2022-05-16 23:48:22 +00:00
self.cum = DollCum(self)
self.pose = ""
2022-05-16 23:48:22 +00:00
self.emote = Null()
self._hash = None
2022-05-16 23:48:22 +00:00
# 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 generate_hash(self):
salt = str( [self.name, self.pose, str(self.body._hash), str(self.face._hash), str(self.cum._hash), str([x[0]._hash for x in self.clothes.values() if x[0] and x[2]])] )
return hash(salt)
2022-05-16 23:48:22 +00:00
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.image, zorder=self.zorder)
2022-05-16 23:48:22 +00:00
def hide(self):
renpy.hide(name=self.tag, layer=self.layer)
@functools.cache
def build_image(self, hash):
from itertools import chain
2022-05-16 23:48:22 +00:00
sprites = list(chain.from_iterable(
(self.body.build_image(self.body._hash),
self.face.build_image(self.face._hash),
self.cum.build_image(self.cum._hash),
*(x[0].build_image(x[0]._hash) for x in self.clothes.values() if x[0] and x[2]))
))
masks = [sprites.pop(sprites.index(x)) for x in sprites if x[0] == "mask"]
sprites.sort(key=itemgetter(2))
masks.sort(key=itemgetter(2))
2022-05-16 23:48:22 +00:00
back_sprites = [x[1] for x in sprites if x[2] < 0]
#Apply alpha mask
2022-05-16 23:48:22 +00:00
for m in masks:
_, mask, mask_zorder = m
2022-05-16 23:48:22 +00:00
for i, s in enumerate(sprites):
_, sprite, sprite_zorder = s
2022-05-16 23:48:22 +00:00
if i < 1 or mask_zorder > sprite_zorder:
continue
masked = AlphaMask(Fixed(*(x[1] for x in sprites[:i]), fit_first=True), mask)
2022-05-16 23:48:22 +00:00
sprites = sprites[i:]
sprites.insert(0, (None, masked, mask_zorder))
2022-05-16 23:48:22 +00:00
break
sprites = back_sprites + [x[1] for x in sprites]
return Fixed(*sprites, fit_first=True)
2022-05-16 23:48:22 +00:00
@property
def image(self):
if not renpy.is_skipping() and self.is_stale():
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
return self.build_image(self._hash)
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._equip_cloth(obj)
2022-05-16 23:48:22 +00:00
elif isinstance(obj, DollOutfit):
if remove_old:
self.unequip("all")
for cloth in obj.group:
self._equip_cloth(cloth.parent, color=cloth.color)
elif isinstance(obj, (list, tuple)):
for cloth in obj:
self._equip_cloth(cloth)
2022-05-16 23:48:22 +00:00
self.rebuild_blacklist()
update_chibi(self.name)
self.cum.is_stale()
self.is_stale()
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
def _equip_cloth(self, cloth, color=None):
if cloth.type in self.multislots:
for i in range(100):
multitype = cloth.type + str(i)
if multitype not in self.clothes or self.clothes[multitype][0] is None:
zorder = self.clothes[cloth.type][1]
self.clothes[multitype] = [cloth, zorder, True]
break
else:
zorder = self.clothes[cloth.type][1]
self.clothes[cloth.type] = [cloth, zorder, True]
if self.is_blacklisted(cloth.type):
self.unequip(*self.get_blacklister(cloth.type))
if cloth.blacklist:
self.unequip(*cloth.blacklist)
for tracking in self.get_trackers_list(cloth.type):
tracking.is_stale()
if color:
cloth.set_color(color)
cloth.is_stale()
2022-05-16 23:48:22 +00:00
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:
v[0], v[2] = None, True
else:
for arg in args:
if isinstance(arg, DollCloth):
if arg.type in self.multislots:
slot = next((k for k, v in self.clothes.items() if v[0] == arg), None)
if not slot:
continue
self.clothes[slot][0] = None
else:
self.clothes[arg.type][0] = None
else:
if arg in self.multislots:
for k, v in self.clothes.items():
if not k in self.blacklist_unequip and any((x in k) for x in self.multislots):
v[0], v[2] = None, True
else:
if not arg in self.blacklist_unequip:
self.clothes[arg][0] = None
2022-05-16 23:48:22 +00:00
self.rebuild_blacklist()
update_chibi(self.name)
self.cum.is_stale()
self.is_stale()
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
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
2022-05-16 23:48:22 +00:00
update_chibi(self.name)
self.is_stale()
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
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
2022-05-16 23:48:22 +00:00
update_chibi(self.name)
self.is_stale()
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
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[arg][0]:
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."""
if item.is_multislot():
return bool(next((k for k, v in self.clothes.items() if v[0] == item), False))
2022-05-16 23:48:22 +00:00
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, *args, **kwargs):
self.face.set_face(*args, **kwargs)
2022-05-16 23:48:22 +00:00
[x[0].is_stale() for x in self.clothes.values() if isinstance(x[0], DollMakeup) and x[2]]
self.cum.is_stale()
2022-05-16 23:48:22 +00:00
def get_face(self):
"""Returns a dictionary containing currently set facial expressions. Used in character studio."""
return self.face._face
2022-05-16 23:48:22 +00:00
# def set_body(self, **kwargs):
# OBSOLETE! the code in scripts needs to be changed
# """Takes keyword argument(s) with the string name of body part file(s)."""
# if self.body.set_body(**kwargs):
# self.rebuild_image()
2022-05-16 23:48:22 +00:00
def set_body_hue(self, arg):
"""Takes integer between 0 - 359, rotates the character body colour by given amount."""
self.body.set_hue(arg)
2022-05-16 23:48:22 +00:00
[x[0].is_stale() for x in self.clothes.values() if x[0] and x[2]]
self.is_stale()
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
# def set_body_zorder(self, **kwargs):
# OBSOLETE! the code in scripts needs to be changed
# """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()
2022-05-16 23:48:22 +00:00
def set_cum(self, *args, **kwargs):
"""Takes keyword argument(s) containing string name(s) of cum layers to apply or None."""
self.cum.set_cum(*args, **kwargs)
2022-05-16 23:48:22 +00:00
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
def set_pose(self, pose):
pose = "" if pose is None else os.path.join("poses", pose)
self.pose = pose
self.body.is_stale()
self.face.is_stale()
self.cum.is_stale()
[x[0].is_stale() for x in self.clothes.values() if x[0] and x[2]]
if renpy.showing(get_character_tag(self.name), layer=self.layer):
self.show()
2022-05-16 23:48:22 +00:00
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 get_trackers_list(self, type):
"""Takes string cloth type. Returns a list of clothing types that report incompatibility."""
return [x[0] for x in self.clothes.values() if isinstance(x[0], DollClothDynamic) and type == x[0].tracking]
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 = Image(path)
self.is_stale()