ID:273538
 
I've recently taken my save system away froom client.Del() and put it in mob.Logout()...
As a result, I am finding the odd person is having their savefile corrupted...
the x/y/z vars all save. As do 2 other vars responsible for showing a preview image of their character on logins...
but the src variable seems to save as null...

I can, in theory post my code, but I didn't on purpose. I am obviously doing something horrendously wrong and as such want to start in an entirely new direction.

So, forum, what would be the best/safest/most efficient way of saving:
src, src.x, src.y, src.z, src.var1, src.var2, src.verbs

NB- I did look @ Deadron's character handling, but discovered it was only for saving @ logout, and not after logout. Also, the delay from logout to save is currently set to 70ticks/70 seconds. And I am running BYOND 454.1036 (so I can still map)
Well SPM the first thing i would recommend after looking at your previous save system code that you mentioned in your last thread that i viewed out of pure curiosity and after talking to mystery about your save sizes.

My first suggestion would be to go and pay a visit to 2 threads on save files by Nadrew and lummox, they have so good information that you could find usefull to you.

the links are
Nadrew: http://www.byond.com/members/ DreamMakers?command=view_post&post=80125
Lummox: http://www.byond.com/members/ DreamMakers?command=view_post&post=50892

In these threads they both mention a process of wiping the entirety of icons and overlays and then rebuilding them on logout. I use a similar but modified version of these 2 guides and has proven effective for me so far (with rebuilding bases and hairs) since i have yet to venture into the realm of creating my weapon types and clothing.

So for now id say go take a peak at those if you have not yet done so.
Saucepan Man wrote:
So, forum, what would be the best/safest/most efficient way of saving:
src, src.x, src.y, src.z, src.var1, src.var2, src.verbs

Note: Try to think of things in a more objective, accurate manner. Especially while writing posts meant for others to read and understand. "saving src" isn't nearly as unambiguous and accurate as "saving player". Likewise, your post's topic ("Death Avoid System") is barely relevant to the contents (worse still, I could only find that minor relevance by specifically thinking about it for a while).

To answer your question:
mob/player
proc/Save(filename = src.ckey)
if(fexists(filename)) fdel(filename)
var/savefile/F = new(filename)
F << src
proc/Load(filename = src.ckey)
if(!fexists(filename)) return 0
var/savefile/F = new(filename)
var/temporary_var
F >> temporary_var //what var you load the mob into doesn't really matter
//because the 'key' var is saved and loaded, the player, if present, is automatically logged in to the loaded mob.
. = 1 //return a true value at the end of the proc.
del src //delete the player's previous mob.

Write(savefile/F)
//here is where you override or customize BYOND's built-in object saving routine (for /mob/player)
..() //run the built-in routine. among other things, this automatically saves 'var1' and 'var2', unless they're specially defined (as 'tmp' or something else)
F["x"] << src.x
F["y"] << src.y
F["z"] << src.z
F["verbs"] << src.verbs

F.dir.Remove("icon","overlays","underlays") //we don't want to keep graphical data stored in the savefile, so remove those saved vars

Read(savefile/F)
//here is where you override or customize BYOND's built-in object loading routine (for /mob/player)
..() //run the built-in routine. among other things, this automatically loads 'var1' and 'var2' provided they're saved within the savefile.

/* Note: below a non-conventional, undocumented method to
read the x,y,z values from the savefile is used.
The syntax is like reading an associated value from an
associative list. You should most likely avoid doing
this when loading any 'complex' data such as an object or file.
To be safe, use the normal way (the >> operator) when loading 'complex' data - or always
if you want to be absolutely safe. The shorthand
method seems to be just fine with numbers however. */


var/saved_loc = locate(F["x"],F["y"],F["z"])
if(!saved_loc)
//report an error...
world.log << "Invalid saved loc for player mob [src]!" //be much more descriptive than this, of course...
var/default_location = ...
src.Move(default_location)
else src.Move(saved_loc)

/* 'verbs' loading is done this way because:
a) verbs var is read only, so we've no choice.
b) we want to keep any new verbs that were possibly added since the save. */

var/saved_verbs
F["verbs"] >> saved_verbs
src.verbs += saved_verbs
src.RebuildAppearance() //restore the graphical data manually, since it wasn't saved - 'icon','overlays','underlays' vars are at their default values
//Extra:
mob/proc/RebuildAppearance() //possible example
src.overlays += src.hair
for(var/obj/equipment/E as anything in src.equipment)
if(E.visible) src.overlays += E
src.underlays += whatever

//saving and mob deleting on player leave
client/Del()
var/mob/player/P = src.mob
P.Save()
del P //or 'del src.mob'
..()

//loading on player enter
world/mob = /mob/player/just_logged_in

mob/player/just_logged_in/Login()
if(!src.Load())
/* make sure the below block of code only runs if Load()
failed (returned false). this is actually only a failsafe,
because normally if Load() completed, the source of this proc
would be deleted, hence the proc automatically stops. */

src << "Hey there! Welcome to [world.name]. It seems you are a new player."
//<character creation process etc is initiated here>


...but note that this is only the basic model. If I tried to truly answer your "best/safest/most efficient" specification, I could likely drone on endlessly about many things, really - for a few reasons (f.ex.: what's best or most suitable can vary according to the project at hand, its design, goals and its other parts and properties).
For instance, here are some of the faults and imperfections created if the code presented above was used as-is:
  • Since the Save() and Load() procs are fairly generic, you could convert them to generic versions declared on a more general type such as /atom or even /datum. This may be useful if you save other stuff in your project.
    At any case, there are many different ways to design/write those two little procs.
  • Save():
  • When saving, instead of immediately deleting the current savefile if it already exists, some mere variations create better results. You can move the current/old file deletion to the end so it's only done if the saving was actually successful, or try to rename or move it instead of deleting, keeping it as a backup.
  • Ideally the savefile would contain more info and will be structured in a more organized manner. The mob would be saved in a subdirectory instead of into the root buffer, and the savefile will contain misc and relevant info such as savefile and game version, date and time, etc.
  • Write(): In addition to completely clearing any traces of the graphical data save (the call to list.Remove()), you can also prevent it from being saved in the first place by temporarily clearing (setting to null) those vars before calling the default proc (..()), so their saved value is a simple null and no processing power is wasted on dumping the graphical data to the file.
  • Read():
  • Of course, it would be better to have a specialized error reporting function or system instead of merely outputting to world.log.
  • Restoring the player's location by using Move() isn't robust, since Move() by nature can fail, according to density or other Enter()/Exit() rulings. Setting loc directly isn't acceptable either, since it bypasses the movement system entirely, which can cause various problems and breakages. What should be used is a forced move that still alerts the movement system by calling Entered() and Exited() accordingly (on the relevant locations as well as on the respective areas). In reality, ideally this whole part needs to be expanded anyway to ensure the saved location is legal and that the player isn't relocated to somewhere he shouldn't be at or shouldn't be able to be at because of map changes, possible bugs or anything else.

For further insight about the topic it's also important to read the articles you've been linked to by the previous poster ("All about savefiles" and "The Pitfalls of Savefiles") as well as to consult other resources (for example, look up language features used in the example code such as del, the savefile operators << and >>, and the savefile object procs Write() and Read(). While mentioning those procs, avoid ever calling them directly, as that can cause issues yet to be remedied).
In response to Kaioken (#2)
I love you kaioken. o_o And I promise to post whent its not 5am next time. So its a little more objective.

Kaioken times 9001 :D
As for the so called "Death Avoid System", to prevent players logging off to escape death, you could do it like this:
client/Del()
new /delhelper(src.mob)
..()

delhelper
New(mob/p)
src.tag = "delhelper |[p.ckey]|"
src.DelayedSaveDel(p)
proc/DelayedSaveDel(mob/player/P)
spawn(600)
P.Save()
del P

client/New()
. = ..()
if(src.mob && src.mob.type != world.mob) //player was reconnected to an existing mob
var/dhelper = locate("delhelper |[src.ckey]|")
if(dhelper) del dhelper //prevent the scheduled spawn() from occuring

It's gotten late so no time for big explanations. Hopefully I've got it right, eh.
In response to Kaioken (#4)
It works for the most part but any custom verbs that you have when you re log in before the timer you wont have anything but your default mob verbs so verbs that are defined on loads etc such as admin and what not arnt given to you.

I guess a fix would be to relaunch the load in some way?
In response to Midgetbuster (#5)
That seems like a project-dependent problem.
If you return before your mob is deleted, you're simply reconnected back to the mob you 'occupied' before, so unless something in other code caused its verbs list to change, it would remain intact.
As your client was inevitably deleted and you have a new one after reconnection, its verbs list would be the one that isn't the same as previously. That's normal and always the case with reconnections, and as always, it's in the project's hands to re-add any necessary client verbs.