ID:2883683
 
Problem description:

(bit of rambling background context in this in case it helps, I've included a tl;dr underneath if you just want the barebones question)


Hi all, for the past few days I've been working on a single user tool used to generate custom content for people in the Digimon community to mod their "Digital Monster Ver. Color" virtual pet toy.

Without this tool, the only way to do it is to manually go in and edit the hex data, and even using something like imHex with a good pattern file, it's very easy to get lost and confused - so this program gives a GUI for the user to manually edit the stats (and eventually evolution paths) for their custom Roster, which it adds to a list, converts it to hex, and exports it as a text document, which they can then just paste into their hex editor at the right memory address.

Here's a screenshot:

https://i.imgur.com/16Y2sJR.png

I know BYOND is not the ideal platform for this sort of program, but I am not much of a programmer (my full-time job is actually pixel art).
I've dabbled in Byond on and off since 2001 and for whatever reason, DM is the only programming language that I've ever been able to muddle my way around, but even then I would treat my knowledge somewhere between "complete beginner" and "able to combine tutorials and examples to get something functional."


I need a (preferably easy to understand) way to save user-uploaded icons which have been modified in code. The system I made accepts any of the commonly used formats for Digimon sprite sheets (eg it'll take a 3x4 grid of 16x16 sprites, a 1x15 line of 48x48 sprites, etc.)
It then chops the sprite into two frames, so the Digimon has a little bit of animation while it's on the screen. These frames are saved as obj/Roster/var/firstFrame and obj/Roster/var/secondFrame as /icon.

My gut says maybe something using ftp to save them, using a format like '[DigimonName]_Frame1.png' and '[DigimonName]_Frame2.png' would work, but I can't quite get my head around how to load them in again, given the names will vary depending on whatever the player has named each Digimon in their Roster.

I also thought about assigning each roster "slot" its own ID, and saving/loading the filenames like that, but because the roster size can vary from literally 1 to 90 (though my program only supports around 64 currently) it feels a bit wasteful to cycle through each slot and keep track of whether a slot is active or not in order to save its icons, but I guess that could maybe be a bit of a clunky workaround.

I've read some of the forum posts and the reference for ftp and I feel like the main issue could just be that my lack-of-programmer-brain is maybe just not quite understanding how it works, but I'm keen to learn more!





Problem description tl;dr version:


I need a way to save user-uploaded icons which have been modified in code which is compatible with dmm_suite. The player can currently save their work, load their work, and have everything loads fine, apart from the icons they've set up.

Asking the user to set up their icons multiple times will be quite a large point of frustration for them (these rosters can get pretty sizeable, with commonly over 60 icons which would need re-applied each time.)


Any help would be appreciated! Thanks in advance!



Here's my code for the icon generation and saving/loading sections. I don't mind uploading the whole project source if it helps resolve my issue too - I'm not too precious about it!

Code:
obj/Roster/
DblClick()
..()
if(writing) return
var/I = input("Select an icon for this [name] level Digimon.",icon) as null|icon
if(I==null)
return
customicon=1
edited=1
var/icon/picture=icon(I)
var/icon/picture2=icon(I)
var/IW = picture.Width()
var/IH = picture.Height()
var/IW2 = picture2.Width()
var/IH2 = picture2.Height()
if(IH == 720)
picture.Scale(16,240)
picture2.Scale(16,240)
IW = picture.Width()
IH = picture.Height()
IW2 = picture2.Width()
IH2 = picture2.Height()
picture.Crop(1,IH, 16,IH-15)
picture2.Crop(1,IH2-16, 16,IH2-31)
IW = picture.Width()
IH = picture.Height()
IW2 = picture2.Width()
IH2 = picture2.Height()
else if (IH==64)
picture.Crop(1,IH, 16,IH-15)
picture2.Crop(17,IH,32,IH-15)
IW = picture.Width()
IH = picture.Height()
IW2 = picture2.Width()
IH2 = picture2.Height()
else if(IH==16)
picture.Crop(1,16, 16,1)
picture2.Crop(1,16,16,1)
IW = picture.Width()
IH = picture.Height()
IW2 = picture2.Width()
IH2 = picture2.Height()
else if(IH==48)
picture.Crop(1,48, 48,1)
picture2.Crop(1,48,48,1)
IW = picture.Width()
IH = picture.Height()
IW2 = picture2.Width()
IH2 = picture2.Height()
else
alert(usr,"Please use a DMColor long sprite sheet, a Tortoiseshel grid sprite sheet, or an individual 16x16 or 48x48 sprite in .png format.","Error"," Okay sorry :( ")
return
picture.Scale(32,32)
picture2.Scale(32,32)
picture.SwapColor(rgb(0,255,0,255),rgb(0,255,0,0))
picture2.SwapColor(rgb(0,255,0,255),rgb(0,255,0,0))
firstFrame=picture
secondFrame=picture2
src.icon=picture
src.name=input("Enter Digimon name!",name) as text


mob/proc/SaveFile()
var /dmm_suite/suite = new()
var mapText = suite.write_map(
locate(1, 1, 1),
locate(world.maxx, world.maxy, world.maxz),
DMM_IGNORE_PLAYERS
)
src << "<pre>[mapText]</pre>"
var/datetime=time2text(world.realtime, "-hh-mm_DD-MM-YYYY")
src << text2file (mapText, "Roster[datetime].dmm")
alert(usr,"Saved a .dmm backup of your Roster as \"Roster[datetime].dmm\" in the DMColor folder.","Roster Saved","Okay!")
mob/proc/LoadFile()
var/dmm_file=input("Pick a .dmm Roster file to load.","File") as null|file
if(!null)
var file_name = "[dmm_file]"
var file_extension = copytext(file_name,length(file_name)-2,0)
if(file_extension != "dmm")
usr << "Supplied file must be a .dmm file."
return
for(var/obj/O in world)
del(O)
sleep()
var /dmm_suite/suite = new()
var map_text = file2text(dmm_file)
suite.read_map(map_text, 1, 1, 1)
I don't really know anything about DMM Suite, but there isn't any reason ftp() shouldn't work for saving user-uploaded icons.

I guess I'm not really understanding your question. Is it something about the filenames that presents the issue?
I might just be misunderstanding the syntax or struggling to get my head around how the logic should be structured tbh.

I think I'm getting confused around how the icons would need to be separately associated with each obj which will need to be loaded back to that exact obj when the player loads the dmm. I figure since each obj will have a unique user-defined name, the icon could be saved as the obj's name which would be enough of an association for that, but when setting the icon you can't use a dynamic filename which references a variable as far as I know (for example '[obj.name].dmi')

Incidentally when I try to use the default saving and loading I included in the code snippet above, it does save both frame variables as "/icon" but when loading, it produces errors, so that's why I figured I'd need to somehow ftp the custom icons on save, and loop through the roster to set each one manually on Load().

I did also sort of wonder how necessary ftp would be since they're uploading files which are already on their computer to use ftp to essentially save them back to their computer somewhere else. Maybe there's a way to store the filepath in a variable and just set the icon to that original file upon Load()?

That would be greatly preferred since prompting the player to manually re-save up to 128 icons (1 icon per frame for each roster up to a current max of 64 - but potentially eventually 90) isn't really ideal either. Maybe there's a way I could append each frame as an overlay - or directly to the icon itself as an icon state - to one singular base icon and prompt the player to save that out instead?

I hope this explains what I'm having trouble with a little better. I think it's just down to me not really understanding how to implement the right approach - assuming that's the right approach at all! Any advice is really appreciated!
EDIT: WHOOPS

Not resolved at all - for some reason I get a bunch of errors if there's more than a few Digimon with changed sprites.

It works great if there are up to 7 Digimon (with 14 custom sprites being saved,) but if you have 8 or more (>16 frames) it gives an error for some reason:

runtime error: bad icon operation
proc name: Insert (/icon/proc/Insert)
usr: Guest-1700980739 (/mob)
src: /icon (/icon)
usr.loc: the menuborder (1,1,1) (/turf/menuborder)
call stack:
/icon (/icon): Insert(/icon (/icon), "d9_1", null, null, null, null)
Guest-1700980739 (/client): iconSave(Seadramon (/obj/Roster/Adult))
Guest-1700980739 (/mob): SaveFile()
SaveIcon (/obj/Button/SaveIcon): Click(the menuborder (2,1,1) (/turf/menuborder), "mapwindow.map", "icon-x=16;icon-y=20;left=1;scr...")


Is there a BYOND bug where you can't write more than 14 custom states to an icon in quick succession? I can't figure out any reason why it would work for the first 14 and then fail for anything higher.

Original post:

Sorry for multi-post (is that still a thing?) but I just wanted to update the thread to say I solved it!

Talking it out in the previous post actually let me to un-scrambling the mystery in my head (thanks Lummox JR for prompting me to try to reword the issue!)

It turned out the perfect workaround was to get the user to select the filename and manually select the file to load too - all I needed to keep track of were the icon states!

It's not coded particularly well, but it seems to work pretty well for my purposes:





mob/proc/SaveFile()
for(var/obj/overlays/modified/m in world)
del(m)
var /dmm_suite/suite = new()
var mapText = suite.write_map(
locate(1, 1, 1),
locate(world.maxx, world.maxy, world.maxz),
DMM_IGNORE_PLAYERS
)
for(var/obj/Roster/D in world)
world<<"saving [D]"
src.client.iconSave(D)

sleep()
var/datetime=time2text(world.realtime, "-hh-mm_DD-MM-YYYY")
src << text2file (mapText, "Roster[datetime].dmm")
switch(alert(src,"Saved a .dmm backup of your Roster as \"Roster[datetime].dmm\" in the root of the DMColor folder. Would you like to save a custom sprite backup file? Without this file, sprites will not be saved.",,"Save Sprite Backup","Don't Save Sprites"))
if("Save Sprite Backup")
src<<ftp(CustomIcon1,"SpriteBackup.dmi")
else
returns

client/proc/iconSave(obj/Roster/O)
if(fexists('CustomRoster.dmi'))
del('CustomRoster.dmi')
world<<"Deleted."
var/stateName="d[num2text(O.rosterID)]_1"
var/stateName2="d[num2text(O.rosterID)]_2"
world<<"[O.icon_state]"
if(O.previouslyLoaded==0)
var/I=icon(O.firstFrame)
var/icon/picture=icon(I)
var/I2=icon(O.secondFrame)
var/icon/picture2=icon(I2)
CustomIcon1.Insert(picture,stateName)
CustomIcon1.Insert(picture2,stateName2)
else
O.icon_state=stateName
var/I=icon(O.icon,stateName)
if(I==null)
return
var/icon/picture=icon(I,stateName)
O.icon_state=stateName2
var/I2=icon(O.icon,stateName)
if(I2==null)
return
var/icon/picture2=icon(I2,stateName)
CustomIcon1.Insert(picture,stateName)
CustomIcon1.Insert(picture2,stateName2)


mob/proc/LoadSprites()
var/I = input("Select an icon backup .dmi file to reload your sprites.",icon) as null|icon

if(I==null)
world<<"Invalid"
spawn()
for(var/obj/Roster/D in world)
D.firstFrame=null
D.icon='questionmark.dmi'
D.icon_state=num2text(D.Attribute)
sleep(1)
for(var/obj/Roster/Jogress/J in world)
if(J.y==11)
version=1
for(var/obj/O in world)
if(O.x==J.x &&O.y==7)
del(O)
else if(J.y==7)
version=2
for(var/obj/O in world)
if(O.x==J.x &&O.y==11)
del(O)
else
for(var/obj/Roster/D in world)
D.icon=I
D.icon_state="d[num2text(D.rosterID)]_1"
world<<D.icon_state
D.previouslyLoaded=1