Author Topic: Load older game saves: Prototype solution  (Read 3051 times)

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Load older game saves: Prototype solution
« on: 22 Jul 2016, 21:25 »
I would like to post a a first prototype of the planned feature that would let games load older saved games with different (presumably less) content.
As pretty much known this was an unending trouble for game developers who prefer to patch and update their published games, because currently AGS is unable to load old saved game if some of the game content has changed since: almost any game object you see in your project tree, as well as number of script variables; the exceptions are very few.

The most recent related discussion was in this thread, posting here just for information:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=53504.0

Some time ago I wrote a pretty much detailed explanation of what we ideally need (from my point of view) on the new issue tracker here:
http://adventuregamestudio.myjetbrains.com/youtrack/issue/AGS-4

(IIRC you do not have to be registered on tracker and may just login as Guest to read the post. Just in case, there is an explanation of how you can also use some of your existing web accounts found in this post: http://www.adventuregamestudio.co.uk/forums/index.php?topic=52737.0)


What is written in that tracker ticket is not "set in stone" yet, just a first outline that could be corrected along with the development progress and future discussion. Also I found that some things I mentioned there will be very difficult to implement in the current AGS.

To make things simplier, I will briefly explain the general points below.

==================================================================

Problem

When it comes to loading saves from previous game version, it is not really a serious problem of how to load the different (amount of) data, provided save format properly describes itself.
The real problems come AFTER that, and these are:

Problem 1. Finding matching entities. Unless items have unique identifiers that do not depend on their order in the entity list, it is impossible to tell whether saved game contains same entities as current game. We can only rely on game developer's sanity and discipline here. To solve matching problem we need to ensure that every entity type in game has an ID, that can be number, or string, but regardless of that it should not depend on its order of arrangement inside project (nor change with one). We also need to make such ID being written to save game with every entity, obviously. In such case, when save is loaded, we do not blindly copy, for example, first saved character data into first game's character, but find the character with matching ID in the current game assets, and copy restored data in there.
Now, at this moment many (most, I think) entities in AGS has a distinct property that could be used as a unique ID -- it is their Script Name. Unfortunately, not every entity has this property written into the game file when game is compiled, and none write it to save.
So, the course of action should be this: find which game entities have a distinct ID and make use of it, then find which entities do not have acceptable property and implement such thing.

Problem 2. Deciding what to do when there is no match. So we have found matching characters in the older save, and copied data to existing ones in our game, but now there is a new character that is not even mentioned in the save. Naturally, it keeps all its state it had before running save restoration. Default action could be to reset such character to its initial state, as described in the game (AGS does not support such thing now, unfortunately, and it may require quite some work to make it possible without lots of redundant game file reading). But what if that does not work for game logic? Game developer can have other ideas to where this character should appear if you loaded pre-patched savegame.
Similar but even more awkward problem rises when the character was removed in the update. Default action could be to just discard such non-existing entity, but what if player was in the middle of solving some puzzle related to that character? Certainly there should be a way to fix the game state, which cannot be done automatically; this is something where only author of the game knows what to do.
This means that regardless of whether we have default means of dealing with situations like that or not, AGS should support special script function(s) that is/are called after game is loaded to let gamedev script proper reaction to game content changes.
This also means that there should probably be methods to copy one entity over another manually, for example, if there ID was changed, deliberately or by mistake, or simply because of unusual game plot update.


==================================================================

This is, in a nutshell, my general proposal. The aims and solutions I described above are ideal, which may not be easy to achieve at once, but some steps can be taken today.


Prototype

What do I have now, is a prototype version, that has following features:

1. It has a completely rewritten save format, which I was forced to do, because the existing one is a bit messy and also do not always let to know how many items are saved in there exactly (it sometimes relies on current game's content, and other assumptions). This version can still LOAD old save format, but it only write game saves in the new one.

2. The engine is taught to load saves containing LESS entities than the game. In such case it just restores what is available, and does not do anything with the rest.

3. The game scripts support new function with the predefined name of "resolve_restored_game". This function is called every time the save data was loaded, but before game actually starts to run again.
The function can be put into any script (similarily to game_start, etc), and defined like:
Code: Adventure Game Studio
  1. function resolve_restored_game(LoadedSaveInfo *info)
  2. {
  3. }
  4.  

4. LoadedSaveInfo is a new built-in script type, which purpose is to describe save contents and potential conflicts (non matching issues). At this time it has only several properties telling number of entities. Here's its definition:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. builtin managed struct LoadedSaveInfo {
  2.   /// Gets/sets whether save restoration should be cancelled
  3.   import attribute bool CancelRestore;
  4.   /// Gets whether information on save contents is currently valid
  5.   readonly import attribute bool Valid;
  6.   /// Gets number of audio types in save
  7.   readonly import attribute int AudioTypeCount;
  8.   /// Gets number of audio clips in save
  9.   readonly import attribute int AudioClipCount;
  10.   /// Gets number of characters in save
  11.   readonly import attribute int CharacterCount;
  12.   /// Gets number of dialogs in save
  13.   readonly import attribute int DialogCount;
  14.   /// Gets number of GUIs in save
  15.   readonly import attribute int GUICount;
  16.   /// Gets number of buttons in save
  17.   readonly import attribute int ButtonCount;
  18.   /// Gets number of labels in save
  19.   readonly import attribute int LabelCount;
  20.   /// Gets number of inventory windows in save
  21.   readonly import attribute int InvWindowCount;
  22.   /// Gets number of sliders in save
  23.   readonly import attribute int SliderCount;
  24.   /// Gets number of text boxes in save
  25.   readonly import attribute int TextBoxCount;
  26.   /// Gets number of list boxes in save
  27.   readonly import attribute int ListBoxCount;
  28.   /// Gets number of inventory items in save
  29.   readonly import attribute int InventoryCount;
  30.   /// Gets number of mouse cursors in save
  31.   readonly import attribute int MouseCursorCount;
  32.   /// Gets number of views in save
  33.   readonly import attribute int ViewCount;
  34. };
  35.  

Primitive example of use:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. function resolve_restored_game(LoadedSaveInfo *info)
  2. {  
  3.   File *f = File.Open("$SAVEGAMEDIR$/save.info", eFileWrite);
  4.   f.WriteRawLine("Just loaded saved game with the following contents:");
  5.   f.WriteRawLine(String.Format("info.AudioClipCount = %d", info.AudioClipCount));
  6.   f.WriteRawLine(String.Format("info.CharacterCount = %d", info.CharacterCount));
  7.   f.WriteRawLine(String.Format("info.DialogCount = %d", info.DialogCount));
  8.   f.WriteRawLine(String.Format("info.LabelCount = %d", info.LabelCount));
  9.   f.Close();
  10.  
  11.   if (info.CharacterCount != Game.CharacterCount) {
  12.     AbortGame("OMG, save character count is %d, game character count is %d", info.CharacterCount, Game.CharacterCount);
  13.   }
  14. }
  15.  

5. In the project settings there is now a switch called "Save games compatibility". It currently has two options:
- "Load saves with 100% matching content" (this is backwards compatible option, or for those game authors who do not want to bother supporting old saves)
- "Load saves with less or equal content" (this enables new behavior).

So, basically, what this allows you to do: you can, for example, check number of characters in LoadedSaveInfo and seeing that it is less than current number in your game, deduce which characters need to be fixed up (last X in the list). Or do something else, depending on the kind of your game update.

DOWNLOAD LINK: http://www.mediafire.com/download/ojc8g6557d8xdak/AGS-LoadOldSaves-Prototype1.zip
WIP source branch:
https://github.com/ivan-mogilko/ags-refactoring/commits/feature--loadoldgamesave

WARNING: this is an experimental version of AGS. It is created on top of latest 3.4.0, but DO NOT use it to create your actual games yet, because if something will change during further development, I will NOT support compatibility with this experimental version.


I would really love to know peoples opinion on this, mainly opinion on solution design.
« Last Edit: 22 Jul 2016, 21:52 by Crimson Wizard »

Peder 🚀

  • Been nearly 10 years, think it's time for a change
    • Lifetime Achievement Award Winner
    •  
Re: Load older game saves: Prototype solution
« Reply #1 on: 22 Jul 2016, 22:40 »
I've only skimmed through the post atm, but I'm really happy to see you've made a prototype for this!
I hope some AGS devs with more time on their hands than me will help test this and come with valuable feedback/opinions!

Glad to see you still making progress for AGS <3

Snarky

  • Global Moderator
  • Mittens Earl
  • Private Insultant
    • I can help with proof reading
    •  
    • I can help with translating
    •  
Re: Load older game saves: Prototype solution
« Reply #2 on: 24 Jul 2016, 10:01 »
This sounds like a very reasonable approach. Good job on the rewritten file format! One thing, though:

If I understand it correctly, when loading a game, the current game state is not discarded, but simply overwritten with the data from the save. So that means that anything not overwritten (because of incompatibility) will retain whatever state it currently has. I wonder if that's really the best way to do it. Would it not be safer to discard all the current game state and start from a "blank slate" before loading the save? That way a load would be deterministic: the resumed game would never vary depending on the state before load. Otherwise I can imagine a lot of very strange and hard-to-debug bugs.

Of course, if a load can fail and you want to be able to cancel out of it and go back to the game you were playing, that might complicate things. But if that's something you can now do even in the user script inside resolve_restored_game() – because that's what CancelRestore is for, right? – I assume you do have to back up the game state somewhere else first anyway.

Another point: Is it possible to get the game version of the savegame that is being loaded? I can well imagine that the resolve_restored_game() function might need to have different cases to deal with different past versions.

One thing I don't see in the LoadedSaveInfo is RoomCount. Now, I can totally understand if adding a new room (or removing a room – particularly if it's the room the player was in when the game was saved!) is such a big change that there's no way to reconcile it, but I just thought I'd ask.

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #3 on: 24 Jul 2016, 15:11 »
Would it not be safer to discard all the current game state and start from a "blank slate" before loading the save? That way a load would be deterministic: the resumed game would never vary depending on the state before load. Otherwise I can imagine a lot of very strange and hard-to-debug bugs.
That is true, and I will be looking for a way to do this. Although honestly, it won't be that easy. A lot of objects in AGS are completely static - they are never deleted or recreated during course of the program execution - just overwritten, and there is no existing mechanism of loading initial state of them from the game data. (Initial game data is loaded from game file only once, in a bulk).

Of course, if a load can fail and you want to be able to cancel out of it and go back to the game you were playing, that might complicate things. But if that's something you can now do even in the user script inside resolve_restored_game() – because that's what CancelRestore is for, right? – I assume you do have to back up the game state somewhere else first anyway.
AGS currently cannot survive cancelling save restoration at all, precisely because loaded data is written right over existing objects in most cases, and also some parts of the data is simply deleted beforehand and recreated with the use of loaded data.
So it just quits the game if something failed.
I was considering saving a special save slot before restoration, and restoring it in such case as possible solution.

Another point: Is it possible to get the game version of the savegame that is being loaded?
Yes, this is something I was considering as well. There is a "game version" field in the settings related to "Windows Vista integration", I guess we could move it out to more general section to make it more explicit.

One thing I don't see in the LoadedSaveInfo is RoomCount. Now, I can totally understand if adding a new room (or removing a room – particularly if it's the room the player was in when the game was saved!) is such a big change that there's no way to reconcile it, but I just thought I'd ask.
Hmm, no, it is not exactly that. First thing is that RoomCount is simply not a property of the game. Interestingly enough, there is no such variable anywhere. The rooms are stored in separate files (packed in the game data) and never fully kept in memory. When told to load a room, AGS does not refer to loaded game data, but tries to find a file in package.
There is a special cache for limited number of persistent properties of "already visited rooms", which works in extendable way, so it cannot be broken by adding rooms (and by removing them too, I think - that would just produce redundant data that is never used anymore).
So, quite opposite, I think it's possible to actually update your game by adding more rooms and even removing some (so far as player has not saved in one of them), and that will work even in older versions of AGS.
« Last Edit: 24 Jul 2016, 15:19 by Crimson Wizard »

cat

  • Mittens Baronet
  • AGS Baker
  • Angelica Crosley
    • cat worked on a game that was nominated for an AGS Award!
      cat worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #4 on: 24 Jul 2016, 20:00 »
Wow, great news!

Does this mean that it will be possible in the future to access room objects from other scripts or dialogs by Id and not only by index? This was quite error-prone, so far.

Another point: Is it possible to get the game version of the savegame that is being loaded?
Yes, this is something I was considering as well. There is a "game version" field in the settings related to "Windows Vista integration", I guess we could move it out to more general section to make it more explicit.

This would be great! While coding my last game I was looking for a way to access the version number and put it on a GUI label (to make it easier getting accurate error reports incase there are bugs and several versions). I had to resort to using hard-coded text on the label.

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #5 on: 24 Jul 2016, 20:38 »
Does this mean that it will be possible in the future to access room objects from other scripts or dialogs by Id and not only by index?
I am not quite certain what of I said above made you think so... room cache perhaps?
I guess that could be theoretically possible to do, but that would be a separate big task.
« Last Edit: 24 Jul 2016, 20:48 by Crimson Wizard »

cat

  • Mittens Baronet
  • AGS Baker
  • Angelica Crosley
    • cat worked on a game that was nominated for an AGS Award!
      cat worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #6 on: 24 Jul 2016, 21:50 »
Now, at this moment many (most, I think) entities in AGS has a distinct property that could be used as a unique ID -- it is their Script Name. Unfortunately, not every entity has this property written into the game file when game is compiled, and none write it to save.
So, the course of action should be this: find which game entities have a distinct ID and make use of it, then find which entities do not have acceptable property and implement such thing.
Mostly this and the fact that resolve_restored_game will need to be able to fix room objects. Or can I have one in every room script?

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #7 on: 24 Jul 2016, 23:12 »
Now, at this moment many (most, I think) entities in AGS has a distinct property that could be used as a unique ID -- it is their Script Name. Unfortunately, not every entity has this property written into the game file when game is compiled, and none write it to save.
So, the course of action should be this: find which game entities have a distinct ID and make use of it, then find which entities do not have acceptable property and implement such thing.
Mostly this and the fact that resolve_restored_game will need to be able to fix room objects. Or can I have one in every room script?
Well, yes, if the room object and region ids will be copied to the room cache, it is theoretically possible to let user address them somehow, although with current cache system you will only be able to change limited number of properties. There are still number of serious issues that has to be solved here, for example object names cannot be in same namespace, because object names of two rooms may conflict with each other, so they have to be called something like Room[5].cObject, or in similar fashion.

resolve_restored_game can be added to any script, including room script, similarily to functions like game_start, on_key_press, and so on. They are called in the order: custom scripts -> global script -> room script, with ClaimEvent() interrupting this chain (at least that is supposed to work).
« Last Edit: 24 Jul 2016, 23:29 by Crimson Wizard »

Cassiebsg

  • Cavefish
  • Fleeing the Cylon tyrrany...
    • Cassiebsg worked on a game that was nominated for an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #8 on: 24 Jul 2016, 23:55 »
so they have to be called something like Room[5].cObject, or in similar fashion.

Oh, I'm already in love with this idea! (nod)
Currently I need to put an "if room==x" type of condition to determine events and make sure things happen to the right object. Also much easier to remember than object[3]... ("wait, what was object 3 again?"... "oh, right the left door!" ... "adding a // right door comment so I'll remember it in the future..." (roll) )
There are those who believe that life here began out there...

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #9 on: 25 Jul 2016, 00:33 »
Also much easier to remember than object[3]... ("wait, what was object 3 again?"... "oh, right the left door!" ... "adding a // right door comment so I'll remember it in the future..." (roll) )
In such situations it is advised to use enums, because they can be given names:
Code: Adventure Game Studio
  1. enum RoomsInGame
  2. {
  3.   eRoomMainMenu = 300,
  4.   eRoomAppartment = 10
  5. }
  6.  
  7. enum RoomAppartmentObjects
  8. {
  9.    eDoor = 0,
  10.    eTable = 1,
  11.    eCup = 2
  12. };
  13.  
  14. // later...
  15. if (player.CurrentRoom == eRoomAppartment)
  16. {
  17.    object[eCup].Visible = true;
  18. }
  19.  
« Last Edit: 25 Jul 2016, 00:34 by Crimson Wizard »

cat

  • Mittens Baronet
  • AGS Baker
  • Angelica Crosley
    • cat worked on a game that was nominated for an AGS Award!
      cat worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #10 on: 25 Jul 2016, 09:50 »
Nonetheless, this is rather fragile if you add or remove objects.

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #11 on: 27 Jul 2016, 03:47 »
I suddenly have got what seems to be a good idea on how to reset unread objects to initial state.
Instead of trying to find and read initial data from game file, we could create a special save (outside of common 999 user saves) with initial game state (made even before 1st room is loaded) and use that. This would be 1000 times easier, because there will be file that contains only data we actually need in a convenient format.

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #12 on: 13 Sep 2016, 15:54 »
I suddenly have got what seems to be a good idea on how to reset unread objects to initial state.
Instead of trying to find and read initial data from game file, we could create a special save (outside of common 999 user saves) with initial game state (made even before 1st room is loaded) and use that. This would be 1000 times easier, because there will be file that contains only data we actually need in a convenient format.

Hrm, just to put a comment on my own comment, I later found that this is not really a perfect solution, because it involves creating a new temporary file at startup that must not be deleted while the game is running. I mean, it is unlikely that it will be deleted, but still the assumption that an important feature will fully depend on file that can be created only at certain point in runtime, and cannot be restored at any random time, sounded terribly to me.
Secondly, there is no way to know whether the save is outdated beforehand, so either engine would have to read this special file before loading ANY save (extra work and time for something that may only be needed once in a while), or engine will have to load this temp file after loading savegame and find a way to extract only data it needs. The latter is the same problem I was trying to avoid at first place.
So in the end I decided that implementing a function which would read only required data from base game file seem to be the only correct way to solve this.

Snarky

  • Global Moderator
  • Mittens Earl
  • Private Insultant
    • I can help with proof reading
    •  
    • I can help with translating
    •  
Re: Load older game saves: Prototype solution
« Reply #13 on: 26 Oct 2017, 13:26 »
I just want to say I think this was a great initiative and I'm sorry it was abandoned. Relying on a save file doesn't seem like such a big problem to me (you could generate it on compilation or at least the first time a game build is launched and treat it like any other essential game file). And wouldn't it be possible to first of all check the version of any save game file you're loading, and then either start by loading the "default" save or not, depending on whether the value matches?

Re: Load older game saves: Prototype solution
« Reply #14 on: 26 Oct 2017, 18:20 »
I don't understand why not restart game and once it starts it checks if it's expected to load a save or just go on.

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #15 on: 26 Oct 2017, 20:20 »
And wouldn't it be possible to first of all check the version of any save game file you're loading, and then either start by loading the "default" save or not, depending on whether the value matches?
But which field exactly is this "version", who or what is reponsible to update it and under which conditions?
Perhaps that would require an incrementing unique number that updates with every game rebuild... on other hand, not evey game rebuild means broken saves.


I don't understand why not restart game and once it starts it checks if it's expected to load a save or just go on.
Because "restart game" save can be overwritten by "SetRestartPoint" script command. Also, what happens if file gets deleted while you are in the game? I do not think it's okay for a loading old game feature to depend on the presence of the extra file on disk.
Actually, to think of it, it may possible to re-run the game literally, unloading and loading it again, like with RunAGSGame command. That never came to my mind earlier for some reason.

(If engine's data in AGS will be fully decoupled from game data, this should even be possible without restarting executable, just delete old "game" object and load anew. Right now this is too hard to do because much data in AGS is still saved in global static variables, that would take big effort to track and reset)

The question is still how and when to decide that such action is required. (OTOH, if game is going to be fully unloaded anyway, it can be switched to this measure even in the middle of restoration process)

EDIT: To be honest, I am always worrying about speed of this action: exit game, launch engine again, load game, load save... this may be much slower than usual load save.
This is why I tried to find another way that would require only resetting limited data.




UPD: In general, you should understand that the feature proposed here is still a hack, that has very limited capabilities and many restrictions. Alan was right when he said that many people could misuse, it not understanding how it works.

Without truly unique object identifiers, its easy to get weird results e.g. if you remove an object from the middle of the list, or somehow insert one in the middle. In such case this feature won't help even a bit. Even though it may report save conflict to user by doing script callback, there is no good way to fix it.

And while game entities are at least relatively distinct, the script data is even worse, because the variables are not self described. That is so easy to insert another variable in the middle of declarations, just for the sake of good ordering and code-style, but that will be enough to break saves, and very hard to track.
Even worse may be the situation with the custom managed objects, which also do not have any type information, only size of struct.
« Last Edit: 26 Oct 2017, 21:02 by Crimson Wizard »

Snarky

  • Global Moderator
  • Mittens Earl
  • Private Insultant
    • I can help with proof reading
    •  
    • I can help with translating
    •  
Re: Load older game saves: Prototype solution
« Reply #16 on: 26 Oct 2017, 23:05 »
And wouldn't it be possible to first of all check the version of any save game file you're loading, and then either start by loading the "default" save or not, depending on whether the value matches?
But which field exactly is this "version", who or what is reponsible to update it and under which conditions?
Perhaps that would require an incrementing unique number that updates with every game rebuild... on other hand, not evey game rebuild means broken saves.

There's a version field in the general properties (under Windows Vista Game Explorer), but regardless of exactly what we use, it seems obvious to me that in order to handle inconsistencies between different game versions, we will need some way to keep track of the game version.

I think it's the game developer's responsibility to keep track of game versions and what needs to be done to convert between them. As this is a feature that is only really relevant to commercial devs and very ambitious/professional-level games, that doesn't seem unreasonable. (Having the editor keep track of build numbers is risky when you might have people reverting projects to backups, builds happening on multiple machines, etc.)

Alan was right when he said that many people could misuse, it not understanding how it works.

I don't think Alan's objections were legitimate. Just because you can't come up with a fully automatic way to convert saves from any game version to any other game version, regardless of what kind of changes were made to the game, doesn't mean that you can't (or that you shouldn't) provide useful features that will allow game devs to load saves when the game has changed a little bit.

Whether or not there are issues with the implementation here is another matter.

Without truly unique object identifiers, its easy to get weird results e.g. if you remove an object from the middle of the list, or somehow insert one in the middle. In such case this feature won't help even a bit. Even though it may report save conflict to user by doing script callback, there is no good way to fix it.

Sure, that's a limitation, but it's still better than nothing!

I'm not sure exactly how the objects (I assume we're talking programming objects here, not AGS Objects?) are stored? I thought part of this work was a new savegame format that does keep better track of that?

Eventually, I was hoping you'd be able to have a callback, where if for example there was a mismatch with Characters (or even if there wasn't), you could provide the logic to map it onto the current characters yourself. Something like:

Code: Adventure Game Studio
  1. function resolve_restored_game(LoadedSaveInfo *info)
  2. {
  3.   if(info.GameVersion < 7) // In version 7 we added another character in slot 32
  4.   {
  5.     if(info.CharacterCount == Game.CharacterCount - 1)
  6.     {
  7.       for(int i=0; i<info.CharacterCount; i++)
  8.       {
  9.         if(i<32)
  10.           info.LoadCharacter(characters[i], i); // Load character[i] with data from saved character i
  11.         else
  12.           info.LoadCharacter(characters[i+1], i); // Load character[i+1] with data from saved character i
  13.       }
  14.       // TODO: Initialize character 32
  15.     }
  16.     else // Unexpected savegame format
  17.       AbortGame("Restore failed");
  18.   }
  19. }

And while game entities are at least relatively distinct, the script data is even worse, because the variables are not self described. That is so easy to insert another variable in the middle of declarations, just for the sake of good ordering and code-style, but that will be enough to break saves, and very hard to track.
Even worse may be the situation with the custom managed objects, which also do not have any type information, only size of struct.

Yes, this is problematic, but that's already the case currently, right? Ideally variables should be stored by name, but that might require too big of a change to the compiler/bytecode format, I suppose. Would it at least be possible to isolate it on a script-by-script level, so that any consequences would be restricted to that script? Something similar to the sample above might then be used to rectify the savegame.

For changes to custom structs... yeah, I don't see how you can resolve that, given the data that is stored and the structure of AGS Script. Perhaps just a datablob, with functions to read the various data types? Something like (for restoring a bunch of instances of an Airplane custom struct that has changed format since the save):

Code: Adventure Game Studio
  1. Airplane* parseAirplane9(LoadErrorData* error, PlaneModel model, String ID)
  2. {
  3.   // Old format:
  4.   // int Wingspan
  5.   // String Name
  6.   // int PilotCount (no longer used)
  7.   // int Capacity
  8.  
  9.   Airplane* a = new Airplane;
  10.  
  11.   a.Wingspan = error.NextInt();
  12.   a.Name = error.NextString();
  13.   error.NextInt(); // ignore PilotCount
  14.   a.Capacity = error.NextInt();
  15.   a.Model = model; // Added in version 9
  16.   a.ID = ID; // Added in version 13
  17.  
  18.   return a;
  19. }
  20.  
  21. Airplane* parseAirplane13(LoadErrorData* error, String ID)
  22. {
  23.   // TODO
  24. }
  25.  
  26. Airplane* parseAirplane(LoadErrorData* error, PlaneModel model, String ID)
  27. {
  28.   if(error.GameVersion < 9) // In version 7 we changed the Airplane* struct
  29.     return parseAirplane9(error, model, ID);
  30.   else if(error.GameVersion < 13) // In version 13 we changed it again
  31.     return parseAirplane13(error, ID);
  32.   else return null; // Bad!
  33. }
  34.  
  35. int structCount;
  36. function on_restore_game_datatype_error(LoadErrorData* error)
  37. {
  38.   // Ideally we should know what data type it failed on, but we'll assume it only fails on Airplane
  39.   if(structCount<AIRPLANES_SIZE)
  40.   {
  41.     String ID = String.Format("BXG%0d", i);
  42.     airplanes[i] = parseAirplane(error, eBoeing, ID);
  43.   }
  44.   switch(structCount - AIRPLANES_SIZE)
  45.   {
  46.     case 0:
  47.       myAirplane = parseAirplane(error, eAirbus, "DFE 123");
  48.       break;
  49.     case 2:
  50.       enemyAirplane = parseAirplane(error, eBoeing, "AAA 52");
  51.       break;
  52.     case 3:
  53.       tempPlane = parseAirplane(error, eOtter, "XYZ 1");
  54.       break;
  55.   }
  56. }

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #17 on: 27 Oct 2017, 00:17 »
Without truly unique object identifiers, its easy to get weird results e.g. if you remove an object from the middle of the list, or somehow insert one in the middle. In such case this feature won't help even a bit. Even though it may report save conflict to user by doing script callback, there is no good way to fix it.

<...>
I'm not sure exactly how the objects (I assume we're talking programming objects here, not AGS Objects?) are stored? I thought part of this work was a new savegame format that does keep better track of that?

New savegame format was mainly for cleaning and breaking savegame into comprehensible parts ("components") which are easier to work with.
I was meaning AGS Objects above. Well, game entities, in broad sense. At this point it does not really matter how to store them in the engine; we could put them into a map instead of array, making the keys up somehow, but that still won't solve the issue, because the unique id must originate from the game project, be a necessary part of entity properties - all way along from the game source to game data in the engine's memory.

And that would probably also require updating script API too, e.g. providing UID (replacing array index, or along with one), then provide lookup functions to get object by key along with array index.
(I am making many ORs here, because it's a matter of separate consideration - what to do with array indices; I remember that people may still use them to reference objects as array elements in particular situations)
« Last Edit: 27 Oct 2017, 00:28 by Crimson Wizard »

Re: Load older game saves: Prototype solution
« Reply #18 on: 27 Oct 2017, 10:19 »
I remember RPGMaker2000 having a big table of numbers and you would take stuff from this predefined table, so everything, even variables, had a fixed id.

Maybe if ints, floats and Strings also had a saveable_int type for instance, where you pass an additional index on creation to use as id? So only specified variables would be saved. I know this is maybe a bad idea, but I am thinking that maybe there would be a way to autofill this table with ids and not use previously used id even if variable is removed from script.

Dave Gilbert

  • Mittens Vassal
  • AGS Baker
  • Hi. Our names are FRIGGING ADORABLE.
    • Lifetime Achievement Award Winner
    •  
    • Dave Gilbert worked on a game that was nominated for an AGS Award!
      Dave Gilbert worked on a game that won an AGS Award!
Re: Load older game saves: Prototype solution
« Reply #19 on: 30 Oct 2017, 15:47 »
A bit off topic, but would it be possible to add some kind of "lock" toggle to the general settings? A toggle that, if activated, will prevent you from doing anything that will break saves? It's often fairly easy to break them by accident even when being super careful.

This wouldn't fix the problem, but it would make dealing with it slightly easier. :)