Outfit import/export

* Re-implemented image payload injection using Python3-compliant methods
* Added sanity checks for image importing and exporting
* Added compression for the payload
* Added user input for exported image file names
* Removed redundant layer for Tonks' ribbon bra
* Updated MyMod clothing definition example.
* Fixed regression in outfit clone functions
This commit is contained in:
LoafyLemon 2023-02-10 23:12:16 +00:00
parent 5fdf1e87fe
commit fb8e3aab0f
9 changed files with 132 additions and 116 deletions

Binary file not shown.

View File

@ -1,13 +1,19 @@
# Add new hairstyle for character as an instance of DollCloth, # Add new hairstyle for character as an instance of DollCloth,
# make sure the variable name is unique, preferably starting with mod name. # make sure the variable name is unique, preferably starting with mod name.
default MyMod_ponytail = DollCloth( default MyMod_ponytail = DollCloth(
modpath="mods/mymod/", # shortened filepath to this file modpath="MyMod", # File path; Usually a mod folder name. (case insensitive)
name="hermione", # Character name (case sensitive) name="hermione", # Character name (case sensitive)
categories=("head","hair"), # Main category and subcategory of the item categories=("head","hair"), # Main category and subcategory of the item (case sensitive)
type="hair", # Item type type="hair", # Item type (case sensitive)
id="ponytail", # Item identificator id="ponytail", # Item identifier (case sensitive)
unlocked=True, # True=Item is unlocked by default, False=Item is a part of the outfit and requries to be bought unlocked=True, # True=Item is unlocked by default, False=Item is a part of the outfit and requries to be bought
level=0, # Character whoring/friendship level required to wear this cloth. (Optional) level=0, # Character whoring/friendship level required to wear this cloth. (Optional)
armfix=False, # If cloth images intersect with arm layers, set to True. (Optional) color=["#985930", "#c38959"], # Python list with default colours in one of the formats listed down below.
color=[[152, 89, 48, 255], [195, 137, 89, 255]] # Python list with default colours in RGBA format applicable for each colourable file layer zorder=None # Item zorder number, or None, if None default zorder for the slot will be used.
) )
# Example valid colour formats:
# RGB: [(255, 255, 255), (255, 255, 255)]
# RGBA: [(255, 255, 255, 255), (255, 255, 255, 255)]
# HEX: [("#985930", "#c38959")]
# HEXA: [("#985930FF", "#c38959FF")]

View File

@ -252,8 +252,6 @@ init python:
def clone(self): def clone(self):
"""Creates a clone of this cloth object. Since it requires a parent object it should be used internally only to avoid object depth issue.""" """Creates a clone of this cloth object. Since it requires a parent object it should be used internally only to avoid object depth issue."""
if self.parent:
return self
return DollCloth(self.name, self.categories, self.type, self.id, [x for x in self.color] if self.color else None, self.zorder, self.unlocked, self.level, self.blacklist, self.modpath, self) return DollCloth(self.name, self.categories, self.type, self.id, [x for x in self.color] if self.color else None, self.zorder, self.unlocked, self.level, self.blacklist, self.modpath, self)
def is_modded(self): def is_modded(self):

View File

@ -183,6 +183,4 @@ init python:
def clone(self): def clone(self):
"""Creates a clone of this cloth object. Since it requires a parent object it should be used internally only to avoid object depth issue.""" """Creates a clone of this cloth object. Since it requires a parent object it should be used internally only to avoid object depth issue."""
if self.parent:
return self
return DollClothDynamic(self.name, self.categories, self.type, self.id, [x for x in self.color] if self.color else None, self.zorder, self.unlocked, self.level, self.blacklist, self.modpath, self._tracking, self) return DollClothDynamic(self.name, self.categories, self.type, self.id, [x for x in self.color] if self.color else None, self.zorder, self.unlocked, self.level, self.blacklist, self.modpath, self._tracking, self)

View File

@ -505,11 +505,11 @@ init python:
# Grab data # Grab data
if fromfile: if fromfile:
try: try:
imported = image_payload.decode(path) imported = ImagePayload().extract(path)
except: except Exception as e:
if image_payload._file:
image_payload._file.close()
renpy.notify("Import failed: Corrupted file.") renpy.notify("Import failed: Corrupted file.")
print(e)
renpy.block_rollback()
return None return None
else: else:
imported = get_clipboard() imported = get_clipboard()
@ -518,8 +518,9 @@ init python:
if imported: if imported:
try: try:
imported = make_revertable(evaluate(imported)) imported = make_revertable(evaluate(imported))
except: except Exception as e:
renpy.notify("Import failed: Corrupted outfit data.") renpy.notify("Import failed: Corrupted outfit data.")
print(e)
renpy.block_rollback() renpy.block_rollback()
return None return None

View File

@ -86,16 +86,21 @@ init python:
def export_data(self, filename, tofile=True): def export_data(self, filename, tofile=True):
"""Exports outfit to .png file or clipboard text.""" """Exports outfit to .png file or clipboard text."""
exported = [self.group[0].name] exported = [self.group[0].name]
exported.extend([x.id, x.color] for x in self.group)
for i in self.group:
if i.color:
color = [j.hexcode for j in i.color]
exported.append([i.id, color])
# Encode data # Encode data
if tofile: if tofile:
path = "{}/game/outfits/".format(config.basedir) path = os.path.join(config.gamedir, "outfits")
fn = "{}.png".format(filename)
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(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 = 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( d = Fixed(
"interface/wardrobe/export_background.webp", "interface/wardrobe/export_background.webp",
@ -105,8 +110,9 @@ init python:
Text("Ver. {}".format(config.version), size=10, align=(0.99, 0.99)) Text("Ver. {}".format(config.version), size=10, align=(0.99, 0.99))
) )
displayable_to_file(d, path+fn, size=(310, 470) ) displayable_to_file(d, path, size=(310, 470) )
image_payload.encode(filename, str(exported)) ImagePayload().inject("_temp.png", filename, str(exported))
os.remove(path)
else: else:
set_clipboard(exported) set_clipboard(exported)
renpy.notify("Export successful!") renpy.notify("Export successful!")

View File

@ -97,7 +97,7 @@ init -1 python:
try: try:
return __import__('ast').literal_eval(txt) return __import__('ast').literal_eval(txt)
except Exception as e: except Exception as e:
print("Error evaluating clipboard data:") print("Error evaluating data:")
print(e) print(e)
def reset_variables(*args): def reset_variables(*args):

File diff suppressed because it is too large Load Diff

View File

@ -266,7 +266,12 @@ label wardrobe_menu():
elif _choice[0] == "export": elif _choice[0] == "export":
python: python:
_choice[1].export_data(datetime.datetime.now().strftime("%d %b %Y-%H%M%S")) filename = renpy.input("Save as:", datetime.datetime.now().strftime("%d %b %Y-%H%M%S"))
if not filename.endswith(".png"):
filename += ".png"
_choice[1].export_data(filename)
achievements.unlock("export") achievements.unlock("export")
elif _choice[0] == "import": elif _choice[0] == "import":