Save files persisting through updates?
Shadecerule
15 Mar 2019, 02:27As far as I understand (correct me if I'm wrong), save files are locked to the version of the game they were saved on. So, you can't use a save file to pick up from a later version of the game. This makes sense to me but I'm looking for a way around it.
To me, I'm mostly concerned with character setup. It'd be great if my players didn't have to start from scratch whenever I release a new update, since I give quite a few options. Would it be possible to export some variables into a txt or csv so that players can upload it to future versions?

DarkLizerd
15 Mar 2019, 02:48One way that has been proposed before...
Add a command called "savegame"
have the character and story setting printed to the screen.
tell the player to copy the full screen (the text box area),
and save that to a text file "Save.txt"
Add another command "loadgame" that will open the Save.txt file and load the character info.
Your save info and load info must line up for it to work.
One idea, encode the data so that the player would not know what to edit.
Shadecerule
15 Mar 2019, 05:39How do you load files into the game, though? I can't find any instruction on that.

DarkLizerd
15 Mar 2019, 07:45GetFileData (string file name) --- I think this one will read the file
GetFileURL(string filename) --- I think will identify the file
I haven't played with the commands (yet), but you can find a little bit more info in the documentation, under Resources below.
The Pixie
15 Mar 2019, 08:05When you save a game in Quest, it saves everything. The save game IS the game, in whatever state. You do not even need the original game to play the saved game.
I have a library here which will save to "localStorage" if the game is played in the browser.
https://github.com/ThePix/quest/blob/master/SaveLoad.aslx
https://github.com/ThePix/quest/wiki/Library:-Save-and-Load
localStorage is a special section of your harddrive the browser controls and keeps separate for security. However, that is not available in the desktop version.
An earlier version uses DarkLizerd's idea of puttting the data to screen for the player to copy-and-paste to file, which is the only way with the desktop. I will at some point modify my library to allow both methods.
mrangel
15 Mar 2019, 08:52It shouldn't be that hard to allow localStorage to work in the desktop version of Quest.
We seem to currently have:
' KV added this next line of code to actually set CefSharp's path to the temp fold AppData\Local\Temp
' CefSharp writes a debug.log to the current directory, so set it to the Temp folder
settings.CachePath = Path.GetTempPath()
I believe this is also the directory where Cef saves a file for JS localstorage; if it's set to a temp directory, the browser runs in incognito mode and prevents saving.
You should really be setting the cachepath to a sane directory (possibly based on the game's ID?).
If you want to stop it filling the disk with logfiles, that's what settings.LogSeverity = LogSeverity.Disable
is for. Or set settings.LogFile
to a path under the temp directory while leaving CachePath somewhere sane.

Anonynn
15 Mar 2019, 09:14coughSaveLoadcoughUpdatecough :P I love you guys.

Richard Headkid
16 Mar 2019, 13:48@mrangel & Pixie
If you want to stop it filling the disk with logfiles, that's what
settings.LogSeverity = LogSeverity.Disable
is for. Or setsettings.LogFile
to a path under the temp directory while leaving CachePath somewhere sane.
That was my purpose, and the change to the code doesn't even always stop debug.log from being created in the game's directory.
Now I'm wondering if my added code to set settings.CachePath
could have introduced any little bugs into the system . . .
I'm going to change it back and commit the changes on GitHub.
I believe this is also the directory where Cef saves a file for JS localstorage; if it's set to a temp directory, the browser runs in incognito mode and prevents saving.
I could never figure out how the heck that works, but I seem to recall finding numerous posts in the Visual Studio forums which mentioned how that (old) version of Chromium simply won't allow saves to localStorage
.
This may or may not help, but here's code I added to that same file to save a txt file to the hard drive during play:
Private Sub WriteToLog(data As String)
Dim logPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\Quest Logs"
Dim gameName = Split(CurrentGame.Filename, "\")(Split(CurrentGame.Filename, "\").Length - 1)
gameName = gameName.Replace(".aslx", "")
If Not System.IO.Directory.Exists(logPath) = True Then
System.IO.Directory.CreateDirectory(logPath)
End If
If Not System.IO.File.Exists(logPath + "\" + gameName + "-log.txt") = True Then
Dim file As System.IO.FileStream
file = System.IO.File.Create(logPath + "\" + gameName + "-log.txt")
file.Close()
End If
My.Computer.FileSystem.WriteAllText(logPath + "\" + gameName + "-log.txt", data + Environment.NewLine, True)
End Sub
It works along with JS.saveLog()
.
I initially had it coded so that the author could save whatever text he or she wanted to a text file, but we ended up leaving that out.
You do not even need the original game to play the saved game.
I thought the saved games check for the existence of the original game and refuse to load if not found?
UPDATE
Yep. Just checked. If the original game is not on the disk, you get:
FAILED TO LOAD GAME
The following errors occurred:
Error: Could not find file 'C:\Users\KV\Documents\Quest Games\Downloaded Games\Xanadu - In the Compound - Revenge.quest'.
Shadecerule
19 Mar 2019, 00:30I'm glad my thread has stimulated some conversation~
I'm still lost, though. The documentation on GetFileData and GetFileURL is pretty sparse and I'm not sure what to do with them. Pixie's SaveLoad library is also too much for me to implement at this point, as I have way too many variables that could change and this would just be too much overhead for me to deal with. (Though I appreciate the suggestion!)
At this point, I'd be happy with only save/loading the character creation variables. Is there a simple function I can use to quickly export a string to a text file, and then another function to retrieve that string? Would I be able to utilize Log() for this to export some kind of cheat-code-like string the player can paste into the input box?

DarkLizerd
19 Mar 2019, 03:21It would have been nice if the save/load was as simple as it is in Basic:
(open file for saving)
open "MyFile.txt" for output as #1
(save the data you want saved)
print #1, player.name, player.HP, player.Str….
(close file)
close #1
(open file for reading)
open "MyFile.txt" for input as #1
(read the contents)
Input #1, player.name, player.HP, player.Str….
(close file)
close #1
The Pixie
19 Mar 2019, 09:31RH, I had a look at your log/transcript system, and as far as I can see it only works for the web player, not the desktop. Is that right?
The issue with the desktop version is the version of Chrome is so old, as someone said. Anyone fancy looking at how to update it? We have had issues in the past uploading updates to the web (and manowar is consequently reluctant to make changes!), but this should be only changing the desktop, so that should not be a problem.

Richard Headkid
20 Mar 2019, 01:12I had a look at your log/transcript system, and as far as I can see it only works for the web player, not the desktop. Is that right?
I thought it was the other way around. (I may be misremembering. In fact, it's highly probable.)
https://github.com/textadventures/quest/issues/1054#issuecomment-429636361
The issue with the desktop version is the version of Chrome is so old, as someone said. Anyone fancy looking at how to update it?
I tried and tried (and tried) to update Chromium, but I don't know enough about anything involved.
Mewmewmew423
24 Mar 2019, 19:26I'm interested in this too but I don't understand what is going on in this thread. How can I implement this in my game? I only want to save the character creation variables. GetFileData and GetFileURL don't have a lot of documentation at all. Is there a tutorial on this somewhere? I assume that these let you "load" the file, but what is the Quest function for creating/saving an external file?
mrangel
24 Mar 2019, 19:41what is the Quest function for creating/saving an external file?
There isn't one. GetFileData
lets you load a file which is zipped up inside the game file.
On the web version, you can get around this by storing your game data in cookies, which are a function of the web browser rather than Quest itself, or in a LocalStorage (which is like a next-generation cookie). Unfortunately, neither of these work in the desktop version of Quest.
Shadecerule
25 Mar 2019, 08:29I'm working on a way around this. I'm basically conjoining everything as a list and exporting that as a code, which the player can copy/paste into later versions of the game. I'm using Split() to put the code into a NewList() and assign the values using ListItem(). Like:
code = NewList()
code = Split (result)
player.alias = ListItem (code, 0)
player.test = ListItem (code, 1)
However, this doesn't play well with booleans... It's just assigning them as strings. Is there a way around this instead of checking if each string is a "true" or "false"? Is there a function for converting strings into booleans?
mrangel
25 Mar 2019, 10:15Is there a way around this instead of checking if each string is a "true" or "false"?
player.someattribute = (ListItem (code, 5) = "true")
is a quick way to convert to a boolean. You can use (x = y)
as an expression that returns true
if the values are equal, and false
otherwise.
Alternatively, if you know the value in the string is going to be either "true" or "false", you could do player.someattribute = eval (ListItem (code, 5))
. Eval treats a string as an expression, and works equally well for booleans or integers.
Shadecerule
26 Mar 2019, 04:18Ah, that's very helpful! Thank you.
Shadecerule
26 Mar 2019, 04:26Hmm, actually, I get the following error when I use player.test = eval (ListItem (code, 1))
:
Error running script: Error compiling expression 'eval (ListItem (code, 1))': FunctionCallElement: Could find not function 'eval(Object)'
For reference, player.test is a boolean that is set to "true". Is this because the player is pasting the code in as a string and so the value in the list is a string?
mrangel
26 Mar 2019, 09:51No. Eval expects a string.
Your error message suggests that the string in result
doesn't contain a ;
. Split returns a list with a single item, which is number 0. So when you access ListItem (code, 1)
you get null
, and eval complains because it only works on strings, and null
is (technically) an object.
Shadecerule
28 Mar 2019, 04:33No, the way I'm printing them is player.alias + ";" + player.test + ";"
Also, they're assigned just fine using ListItem() so long as eval() and ToInt() aren't there; they're just being assigned as strings instead of the variable types that I want.
EDIT: It seems like this works fine when I separate the functions. (For instance, player.test = ListItem (code, 1)
and THEN player.test = eval (player.test)
) Does Quest have a problem with embedded functions like eval (ListItem (code, 1))
?
mrangel
28 Mar 2019, 11:01Sorry, I always forget that.
With the built-in functions, Quest does strict type checking. So it causes an error because ListItem
can return an object.
That's why there is a separate function StringListItem
which only works with stringlists. player.test = eval (StringListItem (code, 1))
should work, because Split
returns a stringlist.
I was building a similar system, but designed to account for the fact that you might want to add more variables to the system later. So it would generate a string like: player=42:alias=6:"John"strength=2:34someflag=4:true
. In that case, because the string values have quotes around them, you can just use eval
for all data types. (in this version I ditched the ;
between the different values, and instead put =6:
or similar before each data value: the number of characters which need to be eval'ed. This means that you don't need to worry about strings which contain a "
or ;
when splitting it up, and also produces a string which is slightly more compressible using the javascript version of the gzip algorithm.
There is one big problem with systems like this: script attributes. You can't create or modify script attributes on the fly. Your system cannot check whether any firsttime
blocks have already been run; so you need to avoid using those. And if you have scripts which assign script attributes using the =>
operator, you won't be able to easily restore that either; unless you have a set of special scripts designed to set those script attributes to the correct values, which is quite a bit of extra work.