Okay, I know I ranted and raved about how people save their files wrong constantly, then just gave you an intro to how savefiles work. "But where's the code, Ter?". It's in this tutorial. Just stick around.
Making it easy.
Let's talk about ease of use. A lot of people don't bother to write in saving early in their project because they don't want to constantly keep editing their save function every time they add or remove a variable. Yeah, I get you.
Some people also resort to doing this:
F << src
And that's their entire saving system.
Well, I've got some news for you. Doing it right the first time is easier than you would think.
Let's start talking about what it needs to do.
By default BYOND saves EVERYTHING that isn't the compile-time value, or a tmp/const/global/static variable.
Since in part 1, I spent most of my time telling you when to NOT save things. Well, I've just made it a bit easier on you. There are some built-in variables that save whenever they are changed, and aren't temporary. A lot of them are the variables that cause serious problems when they are being saved, so let's just rig up a system that will allow us to specify variables that aren't temporary that should never be saved.
//modify standard saving/loading behavior a bit for all objects
datum
//set up the new save behavior.
Write(var/savefile/F,var/list/neversave=null)
//make sure neversave has been initialized as a list with contents
if(neversave!=null&&istype(neversave,/list)&&neversave.len>=1)
//define i for storing initial variables
var/i
//run through neversave one time
for(var/v in neversave)
//if the variable is not savable (tmp/const/global/static) just remove it,
//as it will never save anyway.
if(!issaved(src.vars[v]))
neversave.Remove(v)
else
//get the initial value of the variable.
i = initial(src.vars[v])
//now, check if the variable would normally save.
if(i!=src.vars[v])
//if it has been changed since runtime, store the current value in neversave.
neversave[v] = src.vars[v]
//and set the variable to default so it won't save.
src.vars[v] = i
else
//remove the variable as it won't save because it's the default value.
neversave.Remove(v)
//call the default behavior.
. = ..(F)
//go back through everything and set it to whatever it should be.
for(var/v in neversave)
src.vars[v] = neversave[v]
//return whatever default would have returned.
return.
else
//fall back to normal behavior if neversave is not set up.
return ..(F)
//set up the new load behavior.
Read(var/savefile/F,var/list/neversave=null)
//check if neversave has been initialized as a list with contents
if(neversave!=null&&istype(neversave,/list)&&neversave.len>=1)
//run through neversave once
for(var/v in neversave)
//if the variable shouldn't be saved (tmp/const/global/static)
if(!issaved(src.vars[v]))
//remove the variable
neversave.Remove(v)
else
//store the current value.
neversave[v] = src.vars[v]
//call the default behavior.
. = ..(F)
//run back through the list
for(var/v in neversave)
//reset the values that may have been overwritten
src.vars[v] = neversave[v]
//return the default return.
return .
else
//fall back to normal behavior if neversave is not set up.
return ..(F)
It's not really that important that you deeply understand that bit of code. It's pretty simple actually.
Next, let's figure out how to use it.
In order to use the saving system, we need to start telling the game what to save, and what not to save.
We can actually do this by overriding the default variables for datum (already done), atom, turf atom/movable, mob, and obj.
Let's get cracking:
Looking through the reference, I don't ever want to save icon variables, icon_state, overlays, or underlays. Most of that is introduced by atom. For movable, I don't want to save screen_loc, and for mob, I never want to save the key.
We're going to introduce a new function to the mix, so that we can better control what we don't save later on down the road.
The function itself is designed to build a list of variables that won't be saved every time it's run. The reason I'm adding it as a separate function, is so that I can later specify that there are neversave elements that I want to remove from the list, thus allowing "neversave" variables to be saved by subclasses of an object that won't save them.
datum
proc
NeverSave(var/list/L)
return L
atom
NeverSave(var/list/L)
//add what we don't want to save
L.Add("icon","icon_state","overlays","underlays")
return ..(L) //return whatever the parent type does.
Write(var/savefile/F,var/list/neversave=null)
//if we don't have any defined nonsavables yet.
if(neversave==null)
neversave = src.NeverSave(list())
//we want to get a local copy of the overlays and underlays
//because you can't just assign a list to overlays or underlays
//at runtime. Things get messy.
var/list/ol
var/list/ul
if(src.overlays!=initial(src.overlays)&&neversave.Find("overlays"))
ol = src.overlays.Copy(1,0)
src.overlays = initial(src.overlays)
neversave.Remove("overlays")
if(src.underlays!=initial(src.underlays)&&neversave.Find("underlays"))
ul = src.underlays.Copy(1,0)
src.underlays = initial(src.underlays)
neversave.Remove("underlays")
. = ..(F,neversave)
if(ol!=null&&ol.len)
src.overlays.Add(ol)
if(ul!=null&&ul.len)
src.underlays.Add(ul)
Read(var/savefile/F,var/list/neversave=null)
if(neversave==null)
neversave = src.NeverSave(list())
return ..(F,neversave)
movable
NeverSave(var/list/L)
L.Add("screen_loc")
return ..(L)
mob
Write(var/savefile/F,var/list/neversave=null)
. = ..(F,neversave)
F.dir.Remove("key")
return .
Note here that I have to remove key from the savefile manually. This is because keys cannot be unset. If you change the key, the client disconnects from the mob. So for this one and only variable, we let it save, then delete it.
Now, let's say I want to later on down the road, allow a player to save their icon?
Well, because we implemented NeverSave as a separate callback, we can remove "icon" from the list like so:
mob
player
NeverSave(var/list/L)
L = ..(L)
L.Remove("icon")
return L
And now you will no longer find "icon" in your neversave list for player or any subclass of it unless you readd it in a child.
So you want to add a variable to the nosave list later?
This is how easy it is:
mob
player
var
list/equipment = list()
NeverSave(var/list/L)
L.Add("equipment")
. = ..(L)
return .
Or, you can do it the way you are supposed to:
mob
var
tmp
equipment = list()
As long as you use temp variables the way you are supposed to, you should never have to change a line of this code.
How do I actually save?
I know, we're this far into a tutorial and we haven't saved anything at all yet. You're probably getting rather upset trying to read through all of this and understand it, when we haven't even shown you how to save yet.
Well, lucky for you, this is part 2/3. You finally get to learn how to save.
Well, let's set up a simple environment:
world
mob = /mob/logging
Del()
for(var/mob/player/p in world)
del p
world
mob = /mob/logging
Del()
for(var/mob/player/p in world)
del p
mob
logging
Login()
spawn()
if(fexists("saves/[copytext(src.ckey,1,2)]/[src.ckey].sav"))
var/savefile/F = new/savefile("saves/[copytext(src.ckey,1,2)]/[src.ckey].sav")
var/mob/player/p = null
F >> p
p.key = client.key
del src
else
client.mob = new/mob/player()
return 1
player
Login()
spawn(10)
src.save()
//set up your icons, icon_state, screen objects, screen locs, underlays, and overlays here.
return ..()
Del()
src.save()
return ..()
proc
save()
var/savefile/F = new/savefile("saves/[copytext(src.ckey,1,2)]/[src.ckey].sav")
F << src
Yeah, I know I told you guys that it was going to be harder than "F << src", but with the setup work we did, it eliminates the need for the player to specify much of anything. There is one thing you really should do, though, which is save the player's location on the map. I'll show you here how to save additional data, and not just less:
mob
player
Write(var/savefile/F,var/list/neversave=null)
. = ..(F,neversave)
var/ocd = F.cd
F.cd = "location"
F << src.x
F << src.y
F << src.z
F.cd = ocd
return .
Read(var/savefile/F,var/list/neversave=null)
. = ..(F,neversave)
var/ocd = F.cd
F.cd = "location"
F >> src.x
F >> src.y
F >> src.z
F.cd = ocd
return .
Note the change of directory to "location". When you save a player, it puts all your data in object zero (".0"). The period means that the directory is hidden. The first object it finds in your variables is object .1, and so forth. We're using that location just for consistency with the default system.
And that concludes part 2.
Next time, we're going to cover some more advanced topics, like handling equipment, overlays, underlays, and the like.
See you then!</<>
Also, it's very redundant to do ". = ..()" and then "return ."
Just do "return ..()"
I'm being picky, I guess.