SaveStateLib

OK folks; long time in the works, but I've got something that kind of works in some cases now.

I wanted to set it up so that you can save a game and then resume it on a later version of the game (so can't use Quest's built in save system). I've basically done this by taking a copy of all objects on startup (with attributes to specify that some objects/attributes don't need to be saved), and serialising only the values of attributes which have changed. I've got a couple of other features to reduce the size of the save file, such as dividing a game into 'domains'. If there's a point where the player can't come back to a certain location, there's no need to save any objects there.

But anyway… on to the question.
I've got 2 methods of saving. One where the player is presented with a textbox containing a string to copy and paste into a text editor, so they can save it for later, and one which saves the string to LocalStorage.

However, last time I checked LocalStorage is disabled on the desktop version of Quest.
So, I'm coming up with an alternate solution which feels like a lot of work, but might be simpler. I just want some opinions on whether players would bother using it.

Basically, when you save you have the option of copying a savestring which you'll need to paste into the 'load' dialog later; or save in your browser; or "save online".
The save online option would prompt the user to pick a username and password, which can then be used to save games to a dedicated space on my webserver. It's a simple database, allowing you to store and retrieve base64 strings.

But then I got to thinking. Having to remember a password would be a bit of a pain. Obviously, I could store it in a client cookie, but that doesn't work for the desktop player.
(I'm assuming that the desktop player even supports loading external sites in iframes; but I see no reason for that to be disabled).

So… when you start a game on the desktop player, a little box pops up asking if you want to enable 'quick saving'. Then you can enter your username/pass, the username and a login token are stored in attributes, and a save is fired using Quest's built-in save system. This basically saves the initial state of the game, plus those two values. Then when the player resumes that save, it automatically loads their latest save from the server (possibly with a notice if there is a newer version of the game available).

The desktop player would only need to login to the save system when they get a new version of the game; otherwise their save will keep them logged in.

It's inelegant, but it's the best I can come up with.
Do you think it's worth the additional complexity?


In case anyone has a strong opinion, I've also got 2 different types of save strings. Not sure which would be best if we're asking the player to copy and paste these to store them somewhere.

From my test game:
Format 1: Ǡ汁䅜઀✦禤ɹ䂇ƘɐỳΐÒ䒠猢Û倭䟊犂䀽䔒߀ঠ✠徐˻ǣ匮┽ᭃẀ氡滑栣㐢ᠠ棈啌煰ૠ்搠
Format 2: [A4GwhgngpgTg3mEBLMBnAvAIwPaYNwAWUSA5gQC7oBseq5MUAdieQegEwCcAvuALbA4dMOSjpsjPMGwB3WOgDaARgA0VFUs4qArAF1uQA===

I think most utilities you might use to save a string for later use wouldn't have a problem with 1; but there's a chance something could get confused. There's less chance of the string getting mangled with type 2, but it's quite a bit longer [these two examples were created using the same save data]


It would be nice to use this save system on the online version.

Format 2 is best, because Chinese characters on most American-English speaking computers is a big NO.


Interesting. If I understand this right, each time you save, all changes from the original game template (not from the last save) are recorded. Then when you restore a saved game, the original game template is loaded and all cumulative changed values from the saved game are applied (correct me if I'm wrong).

I would also go with Format 2 as the extra length shouldn't be a big deal, but any confusion with Format 1's characters would be. How does the save string translate into all of the various changed Quest attribute values, and from all over the game? I know you can store a lot of data in all those combinations of characters, but I don't get how Quest could reorganize that data back to where it specifically belongs?


To make sure that it works when a game is changed and updated (so there might be more variables to save in a later revision), I started with a string that looks like player{alias="Bob";health=195;maxhealth=300} - including only the values that have changed since the game started.

Then I've streamlined it a little using a system that looks a bit like bencode. Instead of an = between the attribute name and value, I use a different symbol character for each possible type. So " indicates that the data is a string, = a boolean, # an int, and so on. In front of strings, I put a number (2 base64 digits) giving the string length. So it would look like stringattribute"05hello … the same length as stringattribute="hello", but faster to process and doesn't need any messing about if there are literal "s, \s, or any other character in the string. Faster to unpack :)

Virtually all of the data types can be efficiently packed into a string in this way. There's just some really horrible issues if your game deals heavily with creating, destroying, and cloning objects (which I usually do).

Example of a case that goes pretty troublesome: I have a crafting system in my game. I use it to create 3 hats, so the basic "hat" object gets cloned 3 times. I now have hat1, hat2, and hat3. Then I destroy the first two, so my saved game includes attributes for hat and hat3. On loading, the script sees that there are attributes for hat3, which doesn't exist. But it has a prototype attribute, so the load script knows that it's a clone. So it creates a new clone , which gets named hat1. So now I need to go through all the other objects (ones I've already loaded and ones I haven't) to make sure that any of them which had object attributes pointing to hat3 now point to the newly created hat1. This is very ugly, because it may well fall over on string attributes that hold expressions for use with eval, text processor functions which contain the object name, and a couple of other cases.

But I think I've got most of those to work neatly.

The only real problem is script attributes, which I can't create on the fly. But I have to accept that as a limitation of Quest.

(The strings don't have visible "name=value" type text in them for the player to examine because I'm passing them through a javascript implementation of the LZW (zip) algorithm first, and then encoding them as either base64 (6 bits per character) or fullrange UTF16)


Heh… I thought updating data blocks would be the hard part. So if you decide to add an extra stat to a game later on, or an extra question at character creation, you can still load an older save and it will do the calculations or ask the extra question before proceeding with the game.

Turns out it's easy. I even worked in a simplified version of my ShowMenu hacks, so that if you call ShowMenu in the update script to ask the player extra questions, it will wait for an answer before running the next update.

Nearly ready to test now; 704 lines of code, plus 60 in the javascript file.

fingers crossed


A challenge worthy of mrangel!


Oh! This sounds really amazing.

How is it different from Save/Load and could any game use it?

You could always go the "typewriter" route like in Resident Evil where player's are only allowed to save in certain locations? Just a thought :D

I super appreciate your hard work Mr.Angel. I wouldn't mind paying you for it either on behalf of everyone who uses Quest.

Anonynn.


Also, I would love to test it. If it works on my game it'll likely work on every other one lol xD So let me know when you're ready for some guinea pigs!

Anonynn


Thanks :)

It'll likely be a while still; I think I'm nearly there, but there's a lot of unit testing needed; especially when it comes to creating and destroying clones, and object attributes.


Ah okay!

So a couple of weeks you think? Or maybe less :D Maybe a month? Sorry, I'm super excited about this and can't wait to test it.

Anonynn.


I don't know how long it might be; depends on my health as well as how much time is taken up by ongoing legal issues. At some point I'll have to get a Windows machine set up for testing; and even though I'm somewhere near the end of the initial coding, I have no idea how many issues might show up when I come to test it in different environments.

I hope I'll have something in a couple of weeks, but I can't make any promises. Lately it seems that deadlines or people waiting on me cause too much anxiety and my short-term memory goes to pieces, which isn't so conducive to coding. It shouldn't take too long, so long as it's just me and the code; but whether I'll get that much time is another matter.

One possible issues is that because I'm saving the state of the game at startup, the memory footprint will increase. I'm hoping this won't be too large an issue.
Initially, I had the function that does this in an _initialise_ script, but I realise that in a library that will be a problem (because objects initialised after this one may have a different state). So now I'm setting up a room whose enter script triggers initialisation. This means a game creator can either start the player in that room, or move them there after any kind of intro which you want to show before giving the new/load prompt. Then there will be a couple of game attributes to determine which room the player should be sent to on starting a new game (which could either be the actual first room, or a room containing a proper intro).
I'm hoping that won't cause any problems, but it ended up needing a lot more planning than I expected. Hopefully there's no more of those unexpected issues. (I'm also considering a function SaveStateSafeInit which can be put into a game's UI Initialisation script, for anyone who wants to use SaveStateLib without making too many modifications to an existing game. It would run savestate initialisation, and wait for the player to choose "New game" before running the start scripts and enter scripts for their current location)

There's always new things standing in the way, but I think the goal is in sight now :S


Doing some unit testing now, and going really slow… my setup isn't good for something like this.

But getting there slowly :)


Sounds incredibly stressful! I'm sorry you're having so many issues IRL :/ To be honest, I wouldn't worry about deadlines or anything. You should always work at your own pace, especially since you're doing this for free and for other people. The only reason I offered to pay you is because I didn't want you to have to worry about money as well >.<

As for the Unit Testing --- HURRAH! Is there anything I can help test and get started?

Anonynn.


Any good news :D ?


One step forward, two steps back.

I was writing the code in Perl and translating it function-by-function into Quest. Mostly because Perl is very good for dealing with large strings. So, once I had the whole thing built in Perl, I could translate the functions into Quest, and then design my unit tests. Basically, a test harness script which runs every function with every possible type of input (for example testing EncodeNumber with positive, negative, zero, small numbers, and large numbers). It would then post the results of this to my Perl script on the server side, and confirm that they generate the same code.

Then I test it the other way; get the Perl code to generate an object model that includes all the quirks of the format, pass it to Quest, and then a Quest script that goes through the created objects and print all their attributes, including the contents of lists and dictionaries.

So far, I'd got to testing numbers, strings, booleans, and null. Done the list testing partly, but still with some bugs to work out.

Then the server lost a hard drive, and I found out that the automatic backups weren't running (and one of my mailservers had failed, meaning that I hadn't been getting daily emails to tell me the backup had failed).

So, I've lost all the server-side code. And probably more importantly for such a complex project, all the comments were in the Perl code. Replicating that code in Quest makes it so huge and unwieldy that it's hard to follow, so my method was mostly implementing each function in Perl and then converting it line-by-line to make a Quest function that does the same thing. And often, one line of Perl code is twenty lines in Quest, because of the horribly limited replace functionality.

So, ironing out the remaining bugs will be a real pain. I would have said that I was about two thirds of the way through the project before the hardware failure; maybe back to halfway now. But I also have a little less time to spend on this, because there's a lot of other things (like my webmail and tax returns) that I also need to do again.

So, I'll do my beast, and hope I should have something usable soon enough.

(one of the main issues here is that I'm building a robust data format; any features that need to be added in a future version would have to remain compatible, so that players using a newer version of a game will be able to load their existing saves. So I have to make sure there's places in the structure where additional features could be bolted on, even if I'm not implementing those features yet. Resilient code has to be carefully planned, and it's the loss of the notes that's ended up being the biggest problem)


Log in to post a reply.

Support

Forums