<library>
<object name="saveloaddata">
<saveloadtext><![CDATA[
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script>
var secretPassphrase = 'UltraSecret Passphrase';
function loadGame2() {
s = prompt("Copy-and-paste your save game code here", "");
if (s) {
decode(s);
}
}
function decode(s) {
s = CryptoJS.AES.decrypt(s, secretPassphrase);
s = s.toString();
str = '';
for (i = 0; i < s.length; i += 2) {
s2 = s.charAt(i) + s.charAt(i + 1);
n = parseInt(s2, 16);
str += String.fromCharCode(n);
}
ASLEvent("LoadGame", str);
}
var loadDialog = $("#load-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
decode($('textarea#data').val());
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
var saveDialog = $("#save-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
$(this).dialog("close");
},
}
});
function loadGame() {
loadDialog.dialog("open");
}
function saveGame(s) {
str = CryptoJS.AES.encrypt(s, secretPassphrase);
$('textarea#savedata').val(str);
saveDialog.dialog("open");
}
</script>
<div id="load-dialog">
<p>Please paste your save game here:</p>
<textarea id="data" rows="13" cols="49"></textarea>
</div>
<div id="save-dialog">
<p>Please copy-and-paste your save game into a text file from here:</p>
<textarea id="savedata" rows="13" cols="49"></textarea>
</div>
]]></saveloadtext>
</object>
<command name="savecmd">
<pattern>save</pattern>
<script>
JS.eval ("saveGame('" + SaveGame() + "');")
</script>
</command>
<command name="loadcmd">
<pattern>load</pattern>
<script>
JS.eval ("loadGame();")
</script>
</command>
<function name="SaveLoadInit">
OutputTextNoBr (saveloaddata.saveloadtext)
</function>
</library>
SaveLoadInit
<function name="SaveGame" type="string">
return ("some text to be remembered")
</function>
<function name="LoadGame" parameters="s">
msg (s)
</function>
s = player.alias
s = s + ";" + player.intelligence
s = s + ";" + player.strength
etc...
return(s)
ary = Split(s, ";")
player.alias = StringListItem(ary, 0)
player.intelligence = ToInt(StringListItem(ary, 1))
player.strength= ToInt(StringListItem(ary, 2))
etc...
XanMag wrote:The problem with saving all the original commands the player entered and re-running them is that if the player enters a command that works but shouldn't [...] all other commands after that are moot.
dfisher wrote:Another possibility is to have checkpoints the player can fast forward to (which would only work for certain games). The author of the Inform 7 game Scroll Thief mentioned doing that in this post on the IF forum.
msg ("<br/>Would you like to <i>load</i> a previously saved game?<br/><br/><i>Yes</i> or <i>No</i><br/>")
get input {
switch (LCase(result)) {
case ("yes") {
msg ("Please <i>Paste</i> In Your Saved Code. Then Press <i>Enter</i>")
get input {
}
}
default {
ary = Split(s, ";")
player.alias = StringListItem(ary, 0)
player.intelligence = ToInt(StringListItem(ary, 1))
player.strength= ToInt(StringListItem(ary, 2))
etc...
<game name="xxx">
<pov type="object">player</pov>
<start type="script">
load_game_function (game.pov)
</start>
</game>
<function name="load_game_function" parameters="character_parameter">
msg ("<br/>Would you like to <i>load</i> a previously saved game?<br/><br/><i>Yes</i> or <i>No</i><br/>")
get input {
ClearScreen
switch (LCase(result)) {
case ("yes") {
msg ("Please <i>Paste</i> In Your Saved Code. Then Press <i>Enter</i>")
get input {
}
}
case ("no") {
character_creation_function (character_parameter)
}
default {
load_game_function (character_parameter)
}
}
}
</function>
<function name="character_creation_function" parameters="character_parameter">
msg ("Name?")
get input {
character_parameter.alias = result
}
// etc etc etc
</function>
<game name="xxx">
<pov type="object">player</pov>
<start type="script">
invoke (load_game_object.load_game_script_attribute)
</start>
</game>
<object name="load_game_object">
<attr name="load_game_script_attribute" type="script">
msg ("<br/>Would you like to <i>load</i> a previously saved game?<br/><br/><i>Yes</i> or <i>No</i><br/>")
get input {
ClearScreen
switch (LCase(result)) {
case ("yes") {
msg ("Please <i>Paste</i> In Your Saved Code. Then Press <i>Enter</i>")
get input {
}
}
case ("no") {
character_creation_function (game.pov)
}
default {
ClearScreen
invoke (load_game_object.load_game_script_attribute)
}
</object>
<function name="character_creation_function" parameters="character_parameter">
msg ("Name?")
get input {
character_parameter.alias = result
}
// etc etc etc
</function>
anonynn wrote:Also...I just want to make sure I understand this. On the LoadGame Function...
player.intelligence = ToInt(StringListItem(ary, 1))
player.strength= ToInt(StringListItem(ary, 2))
anonynn wrote:And are "Booleans" that need to be kept track off also considered "StringListItem"? Would they be written like..
player.blahblah = Boolean(StringListItem(ary, 1)
msg ("<br/>Would you like to <i>load</i> a previously saved game?<br/><br/><i>Yes</i> or <i>No</i><br/>")
get input {
switch (LCase(result)) {
case ("yes") {
JS.eval ("loadGame();")
}
default {
// do the normal stuff
}
}
}
Also...I just want to make sure I understand this. On the LoadGame Function...
ary = Split(s, ";")
player.alias = StringListItem(ary, 0)
player.intelligence = ToInt(StringListItem(ary, 1))
player.strength= ToInt(StringListItem(ary, 2))
etc...
What is this part supposed to entail?
(ary, 1))
(ary, 2))
And are "Booleans" that need to be kept track off also considered "StringListItem"? Would they be written like..
player.blahblah = Boolean(StringListItem(ary, 1))
Not sure why it happens as you say, but I suggest you try this and see what happens:
What is this part supposed to entail?
(ary, 1))
(ary, 2))
Anonynn wrote:I think it happens because if the game doesn't go through the character creation process, it doesn't receive any values for the player's health and therefore starts the game and assumes they have died because they have "0" health. That's my theory anyway. That's why I'm not sure what to do if the player goes to input their "game save code" and it fails somehow or isn't the right code, it'll probably go to instant game over.
I meant more like what are the numbers? Are they a sequence order like ....
(ary, 1))
(ary, 2))
(ary, 3))
(ary, 4))
player.name = StringListItem(ary, 0) // Mary
player.intelligence = ToInt(StringListItem(ary, 1)) //4
player.strength= ToInt(StringListItem(ary, 2)) //1
etc.
Also, what would the code be for like recording what the player has in their inventory? Or the values of their inventory, like the entries of a journal item?
// When saving
s = s + ";" + hat.parent.name
// When loading
hat.parent = GetObject(StringListItem(ary, 74))
// When saving
s = s + ";" + Join(journal.entries, "|")
// When loading
journal.entries = Split(StringListItem(ary, 104), "|")
hat.alias.string
player.parent.object
player.strength.int
player.flag.boolean
<function name="LoadGame" parameters="s">
pos = 0
foreach (val, Split(s, ";")) {
ary = Split(StringListItem(game.saveloadatts, pos), ".")
pos = pos + 1
obj = GetObject(StringListItem (ary, 0))
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
msg ("obj=" + obj)
msg ("att=" + att)
msg ("val=" + val)
if (type = "object") {
set (obj, att, GetObject(val))
}
else if (type = "list") {
set (obj, att, Split(val, "|"))
}
else if (type = "int" or type = "integer") {
set (obj, att, ToInt(val))
}
else if (type = "boolean" or type = "flag") {
set (obj, att, LCase(val) = "true")
}
else {
set (obj, att, Replace(val, "@@@semicolon@@@", ";"))
}
}
</function>
<function name="SaveGame" type="string">
data = NewStringList()
foreach (att, game.saveloadatts) {
ary = Split(att, ".")
obj = GetObject(StringListItem (ary, 0))
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
val = GetAttribute(obj, att)
msg ("obj=" + obj)
msg ("att=" + att)
msg ("val=" + val)
if (type = "object") {
list add (data, val.name)
}
else if (type = "list") {
list add (data, Join(val, "|"))
}
else if (type = "int" or type = "integer") {
list add (data, "" + val)
}
else if (type = "boolean" or type = "flag") {
list add (data, "" + val)
}
else {
list add (data, Replace(val, ";", "@@@semicolon@@@"))
}
}
return (Join(data, ";"))
</function>
Anonynn wrote:I meant more like what are the numbers? Are they a sequence order like ....
(ary, 1))
(ary, 2))
(ary, 3))
(ary, 4))
Or are they supposed to fit the values of integers out of 100? Like, for example...
player.intelligence = ToInt(StringListItem(ary, 100))
player.strength= ToInt(StringListItem(ary, 100))
anonynn wrote:Or are they supposed to fit the values of integers out of 100? Like, for example...
player.intelligence = ToInt(StringListItem(ary, 100))
player.strength= ToInt(StringListItem(ary, 100))
SaveLoadInit
hat.alias.string
player.parent.object
player.strength.int
player.flag.boolean
<library>
<object name="saveloaddata">
<saveloadtext><![CDATA[
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script>
var secretPassphrase = 'UltraSecret Passphrase';
function loadGame2() {
s = prompt("Copy-and-paste your save game code here", "");
if (s) {
decode(s);
}
}
function decode(s) {
s = CryptoJS.AES.decrypt(s, secretPassphrase);
s = s.toString();
str = '';
for (i = 0; i < s.length; i += 2) {
s2 = s.charAt(i) + s.charAt(i + 1);
n = parseInt(s2, 16);
str += String.fromCharCode(n);
}
ASLEvent("LoadGame", str);
}
var loadDialog = $("#load-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
decode($('textarea#data').val());
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
var saveDialog = $("#save-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
$(this).dialog("close");
},
}
});
function loadGame() {
loadDialog.dialog("open");
}
function saveGame(s) {
str = CryptoJS.AES.encrypt(s, secretPassphrase);
$('textarea#savedata').val(str);
saveDialog.dialog("open");
}
</script>
<div id="load-dialog">
<p>Please paste your save game here:</p>
<textarea id="data" rows="13" cols="49"></textarea>
</div>
<div id="save-dialog">
<p>Please copy-and-paste your save game into a text file from here:</p>
<textarea id="savedata" rows="13" cols="49"></textarea>
</div>
]]></saveloadtext>
</object>
<command name="savecmd">
<pattern>save</pattern>
<script>
JS.eval ("saveGame('" + SaveGame() + "');")
</script>
</command>
<command name="loadcmd">
<pattern>load</pattern>
<script>
JS.eval ("loadGame();")
</script>
</command>
<function name="SaveLoadInit">
OutputTextNoBr (saveloaddata.saveloadtext)
</function>
<function name="LoadGame" parameters="s">
pos = 0
foreach (val, Split(s, ";")) {
ary = Split(StringListItem(game.saveloadatts, pos), ".")
pos = pos + 1
obj = GetObject(StringListItem (ary, 0))
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
if (type = "object") {
set (obj, att, GetObject(val))
}
else if (type = "list") {
set (obj, att, Split(val, "|"))
}
else if (type = "int" or type = "integer") {
set (obj, att, ToInt(val))
}
else if (type = "boolean" or type = "flag") {
set (obj, att, LCase(val) = "true")
}
else {
set (obj, att, Replace(val, "@@@semicolon@@@", ";"))
}
}
</function>
<function name="SaveGame" type="string">
data = NewStringList()
foreach (att, game.saveloadatts) {
ary = Split(att, ".")
obj = GetObject(StringListItem (ary, 0))
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
val = GetAttribute(obj, att)
if (type = "object") {
list add (data, val.name)
}
else if (type = "list") {
list add (data, Join(val, "|"))
}
else if (type = "int" or type = "integer") {
list add (data, "" + val)
}
else if (type = "boolean" or type = "flag") {
list add (data, "" + val)
}
else {
list add (data, Replace(val, ";", "@@@semicolon@@@"))
}
}
return (Join(data, ";"))
</function>
</library>
This is an updated version of the library. All the comments in my post of 1st July still apply, so look there for how to use it.
This version is more robust; it will warn you if your attribute values do not make sense. Also, if the load game dialogue gets displayed, but no data is entered, the game will terminate.
Note that this version can handle the "once" text processor command, but you will need to save the data that handles it, so include this entry in your list of attributes:
game.textprocessor_seen.dictionaryoflists
Note that the firsttime
and otherwise
script commands cannot be handled, so if the player loads a game, Quest will assume it is the first time for them as though the player is playing from the start.
<library>
<object name="saveloaddata">
<saveloadtext><![CDATA[
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js">
</script>
<script>
var secretPassphrase = 'UltraSecret Passphrase';
function loadGame2() {
s = prompt("Copy-and-paste your save game code here", "");
if (s) {
decode(s);
}
}
function decode(s) {
s = CryptoJS.AES.decrypt(s, secretPassphrase);
s = s.toString();
str = '';
for (i = 0; i < s.length; i += 2) {
s2 = s.charAt(i) + s.charAt(i + 1);
n = parseInt(s2, 16);
str += String.fromCharCode(n);
}
ASLEvent("LoadGame", str);
}
var loadDialog = $("#load-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
decode($('textarea#data').val());
$(this).dialog("close");
}
}
});
var saveDialog = $("#save-dialog").dialog({
autoOpen: false,
width: 600,
height: 500,
buttons: {
Ok: function() {
$(this).dialog("close");
},
}
});
function loadGame() {
loadDialog.dialog("open");
}
function saveGame(s) {
str = CryptoJS.AES.encrypt(s, secretPassphrase);
$('textarea#savedata').val(str);
saveDialog.dialog("open");
}
</script>
<div id="load-dialog">
<p>Please paste your save game here:</p>
<textarea id="data" rows="13" cols="49"></textarea>
</div>
<div id="save-dialog">
<p>Please copy-and-paste your save game into a text file from here:</p>
<textarea id="savedata" rows="13" cols="49"></textarea>
</div>
]]></saveloadtext>
</object>
<command name="savecmd">
<pattern>save</pattern>
<script>
JS.eval ("saveGame('" + SaveGame() + "');")
</script>
</command>
<command name="loadcmd">
<pattern>load</pattern>
<script>
JS.eval ("loadGame();")
</script>
</command>
<function name="SaveLoadInit">
OutputTextNoBr (saveloaddata.saveloadtext)
</function>
<function name="LoadGame" parameters="s">
if (s = "") {
msg("No save game data found.")
finish
}
pos = 0
foreach (val, Split(s, ";")) {
ary = Split(StringListItem(game.saveloadatts, pos), ".")
obj = GetObject(StringListItem (ary, 0))
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
if (val = "" or TypeOf(val) = "null") {
// Do nothing, this attribute has not been set
}
else if (type = "object") {
set (obj, att, GetObject(val))
}
else if (type = "list") {
set (obj, att, Split(val, "|"))
}
else if (type = "int" or type = "integer") {
if (IsInt(val)) {
set (obj, att, ToInt(val))
}
else {
msg("Load error: Cannot convert \"" + val + "\" to an integer for " + StringListItem(game.saveloadatts, pos))
}
}
else if (type = "boolean" or type = "flag") {
set (obj, att, LCase(val) = "true")
}
else if (type = "dictionaryoflists") {
set (obj, att, DictSplit(val, 0))
}
else {
set (obj, att, Replace(val, "@@@semicolon@@@", ";"))
}
pos = pos + 1
}
game.loading = false
</function>
<function name="SaveGame" type="string">
data = NewStringList()
foreach (att, game.saveloadatts) {
ary = Split(att, ".")
obj = GetObject(StringListItem (ary, 0))
if (not ListCount(ary) = 3) {
msg("Save error: Badly formatted entry: " + att)
}
else if (obj = null) {
msg("Save error: Failed to find an object called: " + StringListItem (ary, 0))
}
else {
att = StringListItem (ary, 1)
type = LCase(StringListItem (ary, 2))
val = GetAttribute(obj, att)
if (TypeOf(val) = "null") {
list add (data, "")
}
else if (type = "object") {
list add (data, val.name)
}
else if (type = "list") {
list add (data, Join(val, "|"))
}
else if (type = "int" or type = "integer") {
list add (data, "" + val)
}
else if (type = "boolean" or type = "flag") {
list add (data, "" + val)
}
else if (type = "dictionaryoflists") {
list add (data, SuperJoin(val))
}
else if (type = "string") {
list add (data, Replace(val, ";", "@@@semicolon@@@"))
}
else {
msg("Save error: Invalid type for " + StringListItem (ary, 0) + ": " + type)
}
}
}
return (Join(data, ";"))
</function>
<!--
Will join a diction or list into a string. The dictionary or list can itself contain
further lists and dictionaries, which in turn can contain even more.
-->
<function name="SuperJoin" parameters="col" type="string">
return (_SuperJoin(col, 0))
</function>
<function name="_SuperJoin" parameters="col, n" type="string">
if (TypeOf(col) = "dictionary") {
sublist = NewStringList()
foreach (key, col) {
if (not TypeOf(key) = "string") {
msg("Error; key is a " + TypeOf(key))
msg(key)
}
val = DictionaryItem(col, key)
s2 = _SuperJoin(val, n + 1)
s = key + "~" + n + s2
list add (sublist, s)
}
return(Join(sublist, "|" + n))
}
else if (TypeOf(col) = "list") {
sublist = NewStringList()
foreach (s, col) {
list add(sublist, _SuperJoin(s, n + 1))
}
return(Join(sublist, "|" + n))
}
else if (TypeOf(col) = "stringlist") {
return(Join(col, "|" + n))
}
else {
return ("" + col)
}
</function>
<function name="DictSplit" parameters="str, n" type="dictionary">
dict = NewDictionary()
list = Split(str, "|" + n)
foreach (s, list) {
sublist = Split(s, "~" + n)
key = StringListItem(sublist, 0)
value = StringListItem(sublist, 1) // here
if (Instr(value, "|" + (n+1)) = 0) {
// Not a collection, just use the value
dictionary add(dict, key, value)
}
else if (Instr(s, "~" + (n+1)) = 0) {
// A list
dictionary add(dict, key, ListSplit(value, n + 1))
}
else {
// A dict
dictionary add(dict, key, DictSplit(value, n + 1))
}
}
return(dict)
</function>
<function name="ListSplit" parameters="str, n" type="list">
l = NewList()
list = Split(str, "|" + n)
foreach (s, list) {
if (Instr(s, "|" + (n+1)) = 0) {
// Not a collection, just use the value
list add(l, s)
}
else if (Instr(s, "~" + (n+1)) = 0) {
// A list
list add(l, ListSplit(value, n + 1))
}
else {
// A dict
list add(l, DictSplit(value, n + 1))
}
}
return(l)
</function>
</library>
Hey Pixie thanks for the save and load features really awesome, question though. I made the library, added it to game, call on Start game
SaveLoadInit
Then when I save i get a message box telling me to copy and paste a bunch of letters in the saveload library but also get a error
> save
Error running script: Cannot foreach over '' as it is not a list
Am I doing this right? Did I miss a step? And what function names do i call if I wanted to add a button for save and load?
Also does this save positions on map for objects? And do i make 'saveloadatts' Attribute in every object I want to save? Sorry for so many questions just really want to use this function :)
Thanks Again for everything Pixie really awesome stuff you do for us.
Mike