Variables only change the compatibiity of save-games?

Started by bx83, Sun 04/04/2021 13:41:24

Previous topic - Next topic

bx83

I've noticed that if I add or delete a variable, save-games made before that change are no longer compatible with the new .exe file.
Some experimentation has shown that it's the number of variables, not the name or type. Is this true?
What, other than deleting essential rooms or characters, will make a save-game incompatible?

Crimson Wizard

#1
Yes, changing number of any game objects (except for audio clips, I think), as well as total size of bytes in script variables, will break saves.

Adding completely new rooms does not break saves.
Changing managed objects (created with new), such as dynamic arrays, does not break saves as they are fully stored within a save, which makes them good choice for supporting extensions in your game if you need this.
There are few notes on this here: https://github.com/adventuregamestudio/ags-manual/issues/62

bx83

Total size bytes in script vars? Like changing an int to 2048 from 1024?

Crimson Wizard

#3
Quote from: bx83 on Sun 04/04/2021 14:14:37
Total size bytes in script vars? Like changing an int to 2048 from 1024?

Not changing values, but changing total size, like adding extra int, or removing an old int. Changing array size.

Replacing one object or variable with another will let load old save, but may shift values if you put it into different order. Because builtin save system in AGS does not identify objects, it just saves and loads everything in the order.


This all was discussed number of times on forums, there may be other threads with this and more information already.

bx83

When you say object, I’m assuming you only mean variables with graphical entities in game and are written beginning with an ‘o’ ie. oBird.Visible=true? Or you mean a programmatic object?o

Crimson Wizard

#5
Quote from: bx83 on Sun 04/04/2021 15:21:47
When you say object, I’m assuming you only mean variables with graphical entities in game and are written beginning with an ‘o’ ie. oBird.Visible=true? Or you mean a programmatic object?o

Any "static" game objects that you create in the editor and see in the project tree: Characters, GUI, Views, all of them. Room objects too.
Script module number also matters.
The exceptions are resources like AudioClips and Sprites - these don't matter as they don't have anything changed at runtime.
Another exception is anything created at runtime in script, such as DynamicSprites or Overlays - these also don't matter.


Ok, I need to write the article for the manual explaining all this.

bx83

How might I define all my variables in global.h, making them truly global and extensible without causing save game incompatibility? Is it even possible to achieve the same without the Global Variables section?

Crimson Wizard

#7
Quote from: bx83 on Tue 06/04/2021 15:00:13
How might I define all my variables in global.h, making them truly global and extensible without causing save game incompatibility? Is it even possible to achieve the same without the Global Variables section?

It seems to me that there's still misunderstanding. Changing global variables does break the saves. Declaring them in any header does not make them "extensible".
Any variables declared in script, does not matter where, even Global Variables panel (they are also normal global variables) contribute to the saves problem.

Only changing contents of dynamically created objects does not break saves. For example, changing dynamic array's size, extending "managed" structs, Dictionary and Set types (these two since AGS 3.5.0).


There are two main known methods to make extensible set of variables:
1) Create a regular global array of ints (or other type) bigger than you need right now. Then later you may use extra elements from that array without breaking saves (unless you change array size).
2) Use dynamic structs or arrays. In the reply above I linked a page with few tips on how to create extendable data using dynamic arrays and Dictionaries: https://github.com/adventuregamestudio/ags-manual/issues/62

Dave Gilbert once said that in Wadjet Eye they create multiple extra dummy characters, guis and other objects, as well as extra array of many ints in script (as described above) just in case they have to patch their game and would need extra game entities for that. This is an ugly workaround, but kind of works.


Alternatively you may script your own save system and save/restore only data you want. Depending on what and how much do you want to put into saves - this may be relatively simple or very complicated.

Crimson Wizard

#8
Actually, I just realized that changing number of room objects does not break old saves, in the sense that they load. But this may still lead to glitches, as rooms will "think" they have different number of objects.

Also, apparently, changing number of custom properties also does not break saves. If you remove existing custom properties and they were in savegame, their values will load, but will be useless (yet don't break anything).

PS. I'm writing an article for the manual about this now, will post a link here later.


UPDATE:
https://github.com/adventuregamestudio/ags-manual/wiki/GameSavesCompatibility

Tried to explain everything I know, may adjust later if remember anything else, or find mistakes.

bx83

Quote
There are two main known methods to make extensible set of variables:
1) Create a regular global array of ints (or other type) bigger than you need right now. Then later you may use extra elements from that array without breaking saves (unless you change array size).

If I were to do this, how would I write this array so it’s global? In global.asc? Just as:

globalint=new dynamic array[999];
int globalvtar[]=, globalvar[]=, etc?

Crimson Wizard

#10
Quote from: bx83 on Fri 09/04/2021 06:32:08
Quote
There are two main known methods to make extensible set of variables:
1) Create a regular global array of ints (or other type) bigger than you need right now. Then later you may use extra elements from that array without breaking saves (unless you change array size).

If I were to do this, how would I write this array so it’s global? In global.asc? Just as:


If you want the array to be accessible in all the scripts in game, then you have to put it in the topmost script, because in AGS global variables and functions can be only accessed in the script they are declared and scripts below.
You may even create a new script specially for this array and other global variables, and move it to the top of the script list - then any variables declared there will be accessible in any other script.
You just need to declare exports/imports properly.


Quote from: bx83 on Fri 09/04/2021 06:32:08
globalint=new dynamic array[999];
int globalvtar[]=, globalvar[]=, etc?

Regular arrays are allocated as
Code: ags

int globalvar[999];


Dynamic arrays are first declared as a pointer to array:
Code: ags

int dynarr[];

And then you have to allocate it with "new" command in some function, for example in game_start:
Code: ags

function game_start() {
    dynarr = new int[999];
}



bx83

Okay, I've come up with this in a new script which always appears at the top:

GlobalVars.asc:
Code: ags
int                           GVInt[];
String                        GVStr[];
float                         GVFloat[];
bool                          GVBool[];
AudioChannel                  GVAChan[];
AudioClip                     GVAClip[];
Button                        GVButton[];
Camera                        GVCam[];
Character                     GVCharacter[];
DateTime                      GVDateTime[];
Dialog                        GVDialog[];
DialogOptionsRenderingInfo    GVDialogRenderInfo[];
Dictionary                    GVDict[];
DrawingSurface                GVDrawingSurface[];
DynamicSprite                 GVDynamicSpr[];
File                          GVFile[];
GUI                           GVGui[];
GUIControl                    GVGuiControl[]; 
Hotspot                       GVHotspot[];
InventoryItem                 GVInvItem[];
InvWindow                     GVInvWindow[];
Label                         GVLabel[];
ListBox                       GVListBox[];
Object                        GVObj[];
Overlay                       GVOverlay[];
Point                         GVPoint[];
Region                        GVRegion[];
Set                           GVSet[];
Slider                        GVSlider[]
TextBox                       GVTextBox
TextWindowGUI                 GVexWindowGUI[];
ViewFrame                     GVViewFrame[];
Viewport                      GVViewport[];

function game_start()
{
  GVInt = new int[999];
  GVStr = new String[999];
  GVFloat = new float[999];
  GVBool = new bool[999];
  GVAChan = new AudioChannel[999];
  GVAClip = new AudioClip[999];
  GVButton = new Button[999];
  GVCam = new Camera[999];
  GVCharacter = new Character[999];
  GVDateTime = new DateTime[999];
  GVDialog = new Dialog[999];
  GVDialogRenderInfo = new DialogOptionsRenderingInfo[999];
  GVDict = new Dictionary[999];
  GVDrawingSurface = new DrawingSurface[999];
  GVDynamicSpr = new DynamicSprite[999];
  GVFile = new File[999];
  GVGui = new GUI[999];
  GVGuiControl = new GUIControl[999];
  GVHotspot = new Hotspot[999];
  GVInvItem = new InventoryItem[999];
  GVInvWindow = new InvWindow[999];
  GVLabel = new Label[999];
  GVListBox = new ListBox[999];
  GVObj = new Object[999];
  GVOverlay = new Overlay[999];
  GVPoint = new Point[999];
  GVRegion = new Region[999];
  GVSet = new Set[999];
  GVSlider = new Slider[999];
  GVTextBox = new TextBox[999];
  GVexWindowGUI = new TextWindowGUI[999];
  GVViewFrame = new ViewFrame[999];
  GVViewport = new Viewport[999];
  
  export GVInt[];
  
  GVInt[NewGlobalIntVar]=5;  
}



GlobalVars.ash:
Code: ags
import int NewGlobalIntVar;


NewGlobalIntVar on the last line of GlobalVars.asc is me making a new int, which will be global and accessible by all other scripts. I can create as many of these as I like without breaking save-game compatibility. Is this correct?

However, it gives an error of:
GlobalVars.asc(7): Error (line 7): Cannot declare local instance of managed type

for line 7 of GlobalVar.asc:
AudioChannel                  GVAChan[];

I try putting
AudioChannel*                  GVAChan[];
but no cigar.

What am I doing wrong?


Crimson Wizard

#12
1.
"export" statement should be done outside of all functions and does not include "[]". Maybe manual still is not clear about this.

2.
"AudioChannel* GVAChan[];" would be correct. Same for all other objects such as Characters, Overlays etc.
I don't know what "no cigar" means. Was there an error and what it was?

3.
Some of these arrays are probably not necessary.
DialogOptionsRenderingInfo is especially not needed, because there's literally 1 object of that kind that is passed to dialog option callbacks, and you should not be storing these.
DrawingSurface - is not recommended to store outside a function... same for File.

Also, to clarify, if you are preparing this in case you'd have to add these objects later, then these arrays are not enough, you need to actually create dummy objects in the editor.

Crimson Wizard

Quote from: bx83 on Sun 11/04/2021 22:57:11
NewGlobalIntVar on the last line of GlobalVars.asc is me making a new int, which will be global and accessible by all other scripts. I can create as many of these as I like without breaking save-game compatibility. Is this correct?

I don't understand this question. Could you give an example of what you intend to do?

bx83

'no cigar' is en English phrase which means "you tried, but unfortunately it didn't go that way. Try an alternate stratagem."

Basically I just want this:

anyType GlobalVars[] = new Dictionary;

GlobalVars[VariableName]=5;
GlobalVars[aFloatVar]=7.2;
GlobalVars[StringVar]="hello";

export GlobalVars;

New, VariableName (or GlobalVars[VariableName], etc.) is a global variable, capable of being accessed by everyone. Adding new indexes to GlobalVars does not break save-game compatibility. That's it. Not sure how to do this - have been experimenting for an hour.

I have this now:

script:
Code: ags
// new module script

int                           GVInt[];
String                        GVStr[];
float                         GVFloat[];
bool                          GVBool[];
AudioChannel                  *GVAChan[];
AudioClip                     *GVAClip[];
Button                        *GVButton[];
Camera                        *GVCam[];
Character                     *GVCharacter[];
DateTime                      *GVDateTime[];
Dialog                        *GVDialog[];
Dictionary                    *GVDict;
DynamicSprite                 *GVDynamicSpr[];
File                          *GVFile[];
GUI                           *GVGui[];
GUIControl                    *GVGuiControl[]; 
Hotspot                       *GVHotspot[];
InventoryItem                 *GVInvItem[];
InvWindow                     *GVInvWindow[];
Label                         *GVLabel[];
ListBox                       *GVListBox[];
Object                        *GVObj[];
Overlay                       *GVOverlay[];
Point                         *GVPoint[];
Region                        *GVRegion[];
Set                           *GVSet[];
Slider                        *GVSlider[];
TextBox                       *GVTextBox[];
TextWindowGUI                 *GVexWindowGUI[];
ViewFrame                     *GVViewFrame[];
Viewport                      *GVViewport[];


function game_start()
{
  GVInt = new int[999];
  GVStr = new String[999];
  GVFloat = new float[999];
  GVBool = new bool[999];
  GVAChan = new AudioChannel[999];
  GVAClip = new AudioClip[999];
  GVButton = new Button[999];
  GVCam = new Camera[999];
  GVCharacter = new Character[999];
  GVDateTime = new DateTime[999];
  GVDialog = new Dialog[999];
  GVDict =  Dictionary.Create();
  GVDynamicSpr = new DynamicSprite[999];
  GVFile = new File[999];
  GVGui = new GUI[999];
  GVGuiControl = new GUIControl[999];
  GVHotspot = new Hotspot[999];
  GVInvItem = new InventoryItem[999];
  GVInvWindow = new InvWindow[999];
  GVLabel = new Label[999];
  GVListBox = new ListBox[999];
  GVObj = new Object[999];
  GVOverlay = new Overlay[999];
  GVPoint = new Point[999];
  GVRegion = new Region[999];
  GVSet = new Set[999];
  GVSlider = new Slider[999];
  GVTextBox = new TextBox[999];
  GVexWindowGUI = new TextWindowGUI[999];
  GVViewFrame = new ViewFrame[999];
  GVViewport = new Viewport[999];
  
  GVDict[TestVariable]="hello";
  GVDict[AnotherVariable]=5;
  
  int NewGlobalIntVar=5;
  GVInt[1]=NewGlobalIntVar;
  
  int SomeOtherVariable=7;
  GVInt[2]=SomeOtherVariable;
  
  
}


export GVInt;
export GVDict;


header:
Code: ags
import int GVInt[];
import Dictionary GVDict[];


error:
GlobalVars.asc(14): Error (line 14): Attributes of identifier do not match prototype
on this line in script:
Dictionary                    *GVDict;

????

I give up :/

Crimson Wizard

#15
Quote from: bx83 on Sun 11/04/2021 23:38:43
error:
GlobalVars.asc(14): Error (line 14): Attributes of identifier do not match prototype
on this line in script:
Dictionary                    *GVDict;


When doing import you must give same type... you changed it in variable but not in import
https://adventuregamestudio.github.io/ags-manual/ImportingFunctionsAndVariables.html

I was going to link this manual article in previous comments, but assumed you might have read it...


Quote
anyType GlobalVars[] = new Dictionary;

GlobalVars[VariableName]=5;
GlobalVars[aFloatVar]=7.2;
GlobalVars[StringVar]="hello";

This is not exactly how Dictionary works...
https://adventuregamestudio.github.io/ags-manual/Dictionary.html

Regarding whole idea, not completely sure about details, but the general direction seem right.

bx83

Is there anything that will save an array of 'anyType' or you have to have an array for each individual type?
How can I more simply set global variables array up?

bx83

Rather than:
Code: ags
int NewGlobalIntVar=5;    GVInt[1]=NewGlobalIntVar;
int SomeOtherVariable=7;  GVInt[2]=SomeOtherVariable;

Crimson Wizard

Quote from: bx83 on Mon 12/04/2021 00:26:33
Is there anything that will save an array of 'anyType' or you have to have an array for each individual type?

No, AGS only supports variables and arrays of exact types.

Quote from: bx83 on Mon 12/04/2021 00:26:33
How can I more simply set global variables array up?

Rather than:
Code: ags
int NewGlobalIntVar=5;    GVInt[1]=NewGlobalIntVar;
int SomeOtherVariable=7;  GVInt[2]=SomeOtherVariable;


I'm confused by this question, or rather by script example. Maybe I'm missing something. Is there a reason you are using these NewGlobalIntVar variables, and not just setting array element directly?
Code: ags

GVInt[1] = 5;
GVInt[2] = 7;

bx83

I just want a named variable - as in, create ThisIsAVar, then use it later. indexes are a bit impersonal and I'll likely make a mistake.

Is there any way to have named global variables? (other than named arrays with just pure indices)

Crimson Wizard

#20
Quote from: bx83 on Fri 16/04/2021 00:41:22
I just want a named variable - as in, create ThisIsAVar, then use it later. indexes are a bit impersonal and I'll likely make a mistake.

Is there any way to have named global variables? (other than named arrays with just pure indices)

If the problem is only in using some name to access an element of array, then there are multiple solutions to this.

First of all, as I mentioned earlier, you may create regular arrays too (not dynamic). After you do that, each time you need extra variable you add one right before that array, and subtract one element from array. This way total size of variables and their order stays the same.
For example, you have this:
Code: ags

int GVInt[999];

then you have this:
Code: ags

int myNewVariable;
int GVInt[998];

and then
Code: ags

int myNewVariable;
int myNewVariable2;
int GVInt[997];



Second, you may declare enum and use it as named indexes. Note that new enums do not break saves, because they are just named constants so don't take any program memory.
Code: ags

enum MyVariables {
    eVarNewGlobalInt = 0,
    eVarSecondGlobalInt = 1
};

and the use them to access array as:
Code: ags

GVInt[eVarNewGlobalInt]



Third, you may create attributes... But that's part of advanced scripting, and I am not sure if I should suggest that here.
If you're suddenly interested, there's where they are mentioned in the manual: https://adventuregamestudio.github.io/ags-manual/OOProgramming.html#defining-attributes

bx83

Thankyou for this, just what I was looking for :)


As usual, having trouble defining things...

.asc
Code: ags
enum eGVInt {
  eFirst=0, 
  eSecond=1,
  eThird=2
};


function game_start()
{
  GVInt = new int[999];
........
}

export eGVInt;


.ash
Code: ags
import int eGVInt;


error: "eGVInt is already defined" (referring to line 'enum eGVInt {', first line in .asc)

Khris

Header:
Code: ags
enum eGVInt {
  eFirst=0,
  eSecond=1,
  eThird=2
};

import int GVInt[]; // after import this is the exact same as the declaration


Main script:
Code: ags
int GVInt[];
export GVInt; // just the name

function game_start() {
  GVInt = new int[999];
}

Crimson Wizard

Yes, you should export array, not enum. Array is the real variable, enum is not a variable, it's just a named constant, and of course you need to have it in the header, otherwise no other script would know what names are there.

Cassiebsg

Sure, just create a bunch of variables in Global variables and call them"Dummy1", "Dummy2", etc... Once you need to use a dummy to patch the game, just grab one of them and rename it and it's ready to use.  ;)
There are those who believe that life here began out there...

bx83

Thank you, works :)

One more bit of fun; I've found https://www.adventuregamestudio.co.uk/forums/index.php?topic=55545.0, which is a great Timer script to replace standard timers in AGS.
To define a global timer (with a name), how do I do it?

.ash
Code: ags
Timer              *GVTimers[20];
export GVTimers;

.asc
Code: ags
import Timer              *GVTimers[];


error: .ash, expected variable or functions after import, not 'Timer'

ps. only 20 indices because that's the maximum amount of timers that can run simultaneously.

Crimson Wizard

#26
Quote from: bx83 on Fri 16/04/2021 20:57:02
error: .ash, expected variable or functions after import, not 'Timer'
Make sure you have Timer module above the script where you declare these arrays.

Also, when you are importing regular arrays you must include their size.

I will link same article about import/export again:
https://github.com/adventuregamestudio/ags-manual/wiki/ImportingFunctionsAndVariables

Quote from: bx83 on Fri 16/04/2021 20:57:02
ps. only 20 indices because that's the maximum amount of timers that can run simultaneously.

For the Timer module you can increase that amount to whatever you want, by changing MAX_RUNNING_TIMERS in the module's header.
Then, you can create much more timer variables. It's just that only so much of them would be able to run at the same time. You may have timers that run only sometimes.

SMF spam blocked by CleanTalk