Manually saving and loading game states Inform 10.2 / 6M62

This request may be a bit of a pain, but I don’t have the I6 abilities for it.

I’m looking to add use of a saved game state to my Guide Mode extension. This will get around an online issue where Parchment doesn’t save the UNDO stack. So atm, if you return to a game that was in guide mode via Parchment’s autosave, you’ll find the mode’s broken. @kamineko pointed this out to me. I’ll change the extension so it maintain’s the one required snapback point itself as a saved file.

I have a pile of code for manual saving/loading that I think I originally got from Kerkerkruip eons ago, and I assume it’s now as old as 6G60. I also assume it was derived from the general save/load routines. Basically it uses four custom i6 functions. I’ll paste the functions and their code at the bottom.

  • I’m looking for an Inform 10.x version of the whole thing to fix the extension. This is the higher priority.

  • Separately, I’m looking for 6M62 versions of the i6 functions, because I derived my extension from my WIP, which is in 6M62, so I need it working there as well.

  • Also, I’m hazy on the use of the ‘To set BLAH as a save file’ function?

Thanks for taking a look.

-Wade

Summary
A save slot is a kind of thing. slot 1 is a save slot.

A save slot has an external file called the savedata. 

The binary file of slot 1 is called "manual-save1". The savedata of slot 1 is the file of slot 1.

An external file can be a save file.

the file of slot 1 is a save file.

To save manually:
	write_savedata to savedata of slot 1;

To load manually:
	load_savedata savedata of slot 1;

To load_savedata (filename - save file external file):
	(- FileIO_LoadSavedGame({filename}); -).
	
To write_savedata to (filename - save file external file):
	(- FileIO_WriteSavedGame({filename}); -).

To set (filename - external file) as a save file:
	(- FileIO_SetSaveFile( {filename} ); -).

To decide if save (filename - external file) exists:
	(- (FileIO_SavedGameExists({filename}, false)) -).




Include (-
	[ FileIO_LoadSavedGame extf struc fref res;
		if ((extf < 1) || (extf > NO_EXTERNAL_FILES))
			return FileIO_Error(extf, "tried to access a non-file");
		struc = TableOfExternalFiles-->extf;
		fref = glk_fileref_create_by_name(fileusage_SavedGame + fileusage_BinaryMode, Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0);
		if (fref == 0) jump RFailed;
		gg_savestr = glk_stream_open_file(fref, $02, GG_SAVESTR_ROCK);
		glk_fileref_destroy(fref);
		if (gg_savestr == 0) jump RFailed;
		@restore gg_savestr res;
		glk_stream_close(gg_savestr, 0);
		gg_savestr = 0;
		rtrue;
		.RFailed;
		CarryOutActivity( (+ failing to restore from a saved game +) );
	]; 
	
	[ FileIO_WriteSavedGame extf struc fref res;
		if (actor ~= player) rfalse;
		if ((extf < 1) || (extf > NO_EXTERNAL_FILES))
			return FileIO_Error(extf, "tried to access a non-file");
		struc = TableOfExternalFiles-->extf;
		fref = glk_fileref_create_by_name(fileusage_SavedGame + fileusage_BinaryMode, Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0);
		if (fref == 0) jump SFailed;
		gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK);
		glk_fileref_destroy(fref);
		if (gg_savestr == 0) jump SFailed;
		@save gg_savestr res;
		if (res == -1) {
			! The player actually just typed "restore". We have to recover all the Glk objects;
			! the values in our global variables are all wrong.
			GGRecoverObjects();
			glk_stream_close(gg_savestr, 0); ! stream_close
			gg_savestr = 0;
			CarryOutActivity( (+ restoring from a saved game +) );
			rtrue;
		}
		glk_stream_close(gg_savestr, 0); ! stream_close
		gg_savestr = 0;
		if (res == 0) CarryOutActivity( (+ automatically saving the game  +) ); rtrue;
		.SFailed;
		CarryOutActivity( (+ failing to automatically save the game  +) );
	];
	
[ FileIO_SavedGameExists extf  fref struc rv usage;
	if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) rfalse;
	struc = TableOfExternalFiles-->extf;
	if ((struc == 0) || (struc-->AUXF_MAGIC ~= AUXF_MAGIC_VALUE)) rfalse;
	if (struc-->AUXF_BINARY) usage = fileusage_BinaryMode;
	else usage = fileusage_TextMode;
	fref = glk_fileref_create_by_name(fileusage_SavedGame + usage,
		Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0);
	rv = glk_fileref_does_file_exist(fref);
	glk_fileref_destroy(fref);
	return rv;
];

[ FileIO_SetSaveFile extf struc;
	if ((extf < 1) || (extf > NO_EXTERNAL_FILES))
		return FileIO_Error(extf, "tried to access a non-file");
	struc = TableOfExternalFiles-->extf;
	struc-->AUXF_BINARY = struc-->AUXF_BINARY | 2;
];

-).

I’d never thought of including the undo stack in autosaves. It would be possible, but it would so massively increase the size of an autosave, for something that would only occasionally be needed. Manually save and loading is probably the best solution.

I doubt much would have changed since those functions were written. Did you actually get errors when trying it, or have you not tried it yet?

1 Like

Sounds like you want Autosave by Daniel Stelzer! Updated for 10.x for ECTOCOMP purposes, I forget if I uploaded the latest version to Github yet or not.

Note to self to upload it tomorrow, since it’s past midnight here.

3 Likes

I haven’t tried it yet. Last time I was using old code for file i/o, I got burned. So my bias was that it probably wouldn’t work. So I’ll try it if -

  • Daniel’s thing doesn’t end up doing the trick :slight_smile:

-Wade

In terms of file size, I don’t know what things take or how they work (story of my life!), but guide/story mode should only ever need to restore one turn across autosaves: it uses the “disable saving of undo state” feature of the Undo Output Control extension.

E: As a philosophical question, is it better for the interpreter or the game to handle this function? I’d personally like for authors of all capability levels be able to use and support this feature (I feel it has accessibility benefits), and the vagaries of file systems, browsers, and operating systems may be a lot to think about, game to game. Just my two cents, since I lack the knowhow to do anything about it either way.

It feels too game-involved to be handled entirely on the interpreter side. However, I haven’t plotted through exactly what it would take.

Here’s the latest, v10-compatible version of Autosave, which I’ll be putting on Github shortly.

Autosave-v2.i7x (5.1 KB)

The goal is to make this process as simple for the author as possible, so it only provides a handful of phrases, which are mostly self-explanatory:

autosave the game[, only restoring once]
autorestore the game
if we restored from an autosave
delete the autosave file

The “if we restored” one should be checked right after “autosave the game”, if you want that, because that’s where execution will resume after restoring.

Oh, and here’s how Labyrinthine Library uses it:

Instead of attacking the gong:
	[...]
	autosave the game; [Make a checkpoint]
	if we restored from an autosave:
		say "[bracket]Reset successful. You're back at the moment before you struck the gong.[close bracket]";
		try looking;
		stop the action;
	say "You strike the gong with a truly tremendous [i]CRASH[/i]...";
	[...]

Resetting is an action out of world applying to nothing. Understand "reset" and "rewind" as resetting.
Carry out resetting (this is the reset the game state rule):
	autorestore the game;
	say "[bracket]Reset failed. Sorry![close bracket][p]". [We'll only reach this if the previous line didn't jump execution back]
1 Like

snip

moved to new thread

I updated the extension to V4, making use of Autosave. Details over in its main thread: New extension: Guide Mode (for Inform 10) - #14 by severedhand
-Wade