Difference between revisions of "Save Files Notes (Papyrus)"

From the CreationKit Wiki
Jump to navigation Jump to search
imported>Taleden
(added "Missing Plugin" under "FormLists")
(Removed assertion that FormList entries for removed Forms will be relocated based on testing in 1.5.97.)
 
(3 intermediate revisions by 2 users not shown)
Line 20: Line 20:


===Removing===
===Removing===
If you remove a property or variable after a save was made, that property or variable's value in the save game will be discarded, and a warning will be printed to the script log detailing what happened.
If you remove a property or variable after a save was made, that property or variable's value in the save game will be inaccessible to running scripts{{Verify|Are we sure that Papyrus scripts can't manually query the values of orphaned properties on other script instances?}}, and a warning will be printed to the script log detailing what happened.
 
The property will be removed from the saved data the next time the user saves. Papyrus warnings should not be generated again after that initial save.


===Changing===
===Changing===
Line 264: Line 266:


===Missing Plugin===
===Missing Plugin===
If a form which is provided by some plugin is added to a FormList by a script, and that plugin is removed or disabled between saving and reloading the game, then the affected entries in the FormList will not be removed, but will instead be relocated to the beginning of the list and changed to None. If the game is saved again at this point and the missing plugin is restored, the FormList entries which became None will nonetheless remain None and will not be removed or regain their original values and positions in the list. A FormList's Revert() method will still remove these missing entries, however, just as it removes all script-added entries.
If a form which is provided by some plugin is added to a FormList by a script, and that plugin is removed or disabled between saving and reloading the game, then the affected entries in the FormList will not be removed, but will instead be changed to None and may be moved within the list. If the game is saved again at this point and the missing plugin is restored, the FormList entries which became None will nonetheless remain None and will not be removed. A FormList's Revert() method will still remove these missing entries, however, just as it removes all script-added entries.
<source lang="papyrus">
<source lang="papyrus">
; FormList Property fl Auto
; FormList Property fl Auto

Latest revision as of 01:20, 22 January 2024

This page details information about how the save game system saves Papyrus scripts. Information on this page will be updated as things change! Known issues and targets for being fixed/changed will be marked.

When can the game save?[edit | edit source]

The game can save at any time. This can be between lines in your script, or even in the middle of running a single line. For the most part, you won't have to worry about it, as the game will successfully return your script to running when the game is loaded, assuming you don't change anything. But what if you do change something? Well, that's what this page will help you with.

Scripts[edit | edit source]

Adding to an Object[edit | edit source]

If you add a script to an object in the masterfile after a save was made, that script will exist on the object when the save is loaded, but will be completely untouched (all of its properties will be set to their masterfile values, and its OnInit block will be run as soon as the save finishes loading).

Removing From an Object[edit | edit source]

If you remove a script from an object in the masterfile after a save was made, that script will still exist on the object when the save is loaded. If the script is deleted entirely, oddities may result.

Properties and Script Variables[edit | edit source]

Adding[edit | edit source]

  • Variable with a default value: The variable will have its default value when the save is loaded. (This is the "int myVar = 5" syntax)
  • Variable with no default value: The variable will have no value when the save is loaded (0, empty string, None, or false)
  • Auto Property: The property will receive the value it was given in the master file, if any. (These are properties that end with the "auto" keyword, like so: "ObjectReference Property MyObject Auto"
  • Non-auto Property: The property will not receive the value it was given in the master file, if any. It will be blank.

Note: For obvious reasons, if the object's OnInit event has already run when the save was made, it will not be run again. Therefore, if you set up a new variable in the OnInit event, you cannot be guaranteed that that variable will have the proper value unless you are sure that object never existed in the save game before.

Removing[edit | edit source]

If you remove a property or variable after a save was made, that property or variable's value in the save game will be inaccessible to running scripts[verification needed], and a warning will be printed to the script log detailing what happened.

The property will be removed from the saved data the next time the user saves. Papyrus warnings should not be generated again after that initial save.

Changing[edit | edit source]

If you change the name of a property or variable, it will be treated as if it was deleted and a new one was added. If you change the type (but not the name), then any value it had in the save game will be discarded, and a warning will be printed to the script log detailing what happened.

Changing Masterfile Value[edit | edit source]

If you have an existing property and change its value in the masterfile, the script will only receive the new value if it doesn't exist in the save. In other words - changing the value will not overwrite the value it has in the save game.

Property Examples[edit | edit source]

; Version 1
Scriptname MyScript Extends ObjectReference

int Property MyAutoValue Auto

int internalValue
int Property MyNonAutoValue
  Function Set(int value)
    internalValue = value
  EndFunction
  int Function Get()
    return internalValue
  EndFunction
EndProperty

int MyVariableWithInit = 1
int MyVariableWithoutInit

Event OnInit()
  MyVariableWithoutInit = 1
EndEvent

Event OnActivate(ObjectReference akActivator)
  Debug.Trace("MyAutoValue = " + MyAutoValue)
  Debug.Trace("MyNonAutoValue = " + MyNonAutoValue)
  Debug.Trace("MyVariableWithInit = " + MyVariableWithInit)
  Debug.Trace("MyVariableWithoutInit = " + MyVariableWithoutInit)
EndEvent

In the masterfile:

  • MyAutoValue = 1
  • MyNonAutoValue = 1

In the log, after activation:

MyAutoValue = 1
MyNonAutoValue = 1
MyVariableWithInit = 1
MyVariableWithoutInit = 1
<save is made here>
; Version 2
Scriptname MyScript Extends ObjectReference

int Property MyAutoValue Auto
int Property MyAutoValue2 Auto

int internalValue
int Property MyNonAutoValue
  Function Set(int value)
    internalValue = value
  EndFunction
  int Function Get()
    return internalValue
  EndFunction
EndProperty

int internalValue2
int Property MyNonAutoValue2
  Function Set(int value)
    internalValue2 = value
  EndFunction
  int Function Get()
    return internalValue2
  EndFunction
EndProperty

int MyVariableWithInit = 2
int MyVariableWithoutInit

int MyVariableWithInit2 = 2
int MyVariableWithoutInit2

Event OnInit()
  MyVariableWithoutInit = 2
  MyVariableWithoutInit2 = 2
EndEvent

Event OnActivate(ObjectReference akActivator)
  Debug.Trace("MyAutoValue = " + MyAutoValue)
  Debug.Trace("MyNonAutoValue = " + MyNonAutoValue)
  Debug.Trace("MyVariableWithInit = " + MyVariableWithInit)
  Debug.Trace("MyVariableWithoutInit = " + MyVariableWithoutInit)
  Debug.Trace("MyAutoValue2 = " + MyAutoValue2)
  Debug.Trace("MyNonAutoValue2 = " + MyNonAutoValue2)
  Debug.Trace("MyVariableWithInit2 = " + MyVariableWithInit2)
  Debug.Trace("MyVariableWithoutInit2 = " + MyVariableWithoutInit2)
EndEvent

In the masterfile:

  • MyAutoValue = 2
  • MyNonAutoValue = 2
  • MyAutoValue2 = 2
  • MyNonAutoValue2 = 2

In the log, after activation and loading the previous save:

<save is loaded here>
MyAutoValue = 1
MyNonAutoValue = 1
MyVariableWithInit = 1
MyVariableWithoutInit = 1
MyAutoValue2 = 2
MyNonAutoValue2 = 0
MyVariableWithInit2 = 2
MyVariableWithoutInit2 = 0

Missing Plugin[edit | edit source]

If a variable, property or array element points to a Form (or any subclass) which is provided by some plugin, and that plugin is removed or disabled between saving and reloading the game, then the variable will not become None but will instead point to a "missing" placeholder form which has ID #0. If the game is saved again at this point and the missing plugin is restored, the variables which became "missing" will nonetheless remain "missing" and will not regain their original values.

Form f = Game.GetFormFromFile(0x123, "Plugin.esp")
Debug.Trace( f == None ) ; false
Debug.Trace( f.GetFormID() ) ; 0x--000123
Game.SaveGame("save1")
; Plugin.esp is disabled or removed, save1 is loaded, script execution resumes here
Debug.Trace( f == None ) ; false
Debug.Trace( f.GetFormID() ) ; 0
Debug.Trace( f.GetType() ) ; 0
Debug.Trace( f.GetName() ) ; ""
Game.SaveGame("save2")
; Plugin.esp is restored, save2 is loaded, script execution resumes here
Debug.Trace( f == None ) ; false
Debug.Trace( f.GetFormID() ) ; 0

If two Form variables both become "missing" in this way, they will then evaluate as equal, even if they originally pointed to different forms. Nonetheless, there is no way to set a Form variable to point to the "missing" form without actually removing a plugin; for example, Game.GetForm(0) returns None, not the "missing" placeholder form.

Functions[edit | edit source]

Notes[edit | edit source]

Function changes only matter if the save was made while a function was running. However, since a save can be made at any time, these following points need to be taken into account. Also, the following information assumes a build AND save made after 11/29/2010, earlier builds and saves (even if loaded with later builds) will throw out any running stacks if any of the functions in the thread changes.

Adding[edit | edit source]

There are no issues with adding new functions.

Removing[edit | edit source]

If you remove a function that was in the middle of running when a save was made, the old function will be loaded from the save and allowed to finish. A warning will be printed to the script log, and further calls to the removed function (usually because some other changed or removed function is calling it) will fail.

Changing[edit | edit source]

Changing Parameters or Return Type[edit | edit source]

The function will be noted as different and the old version of the function will be loaded from the save game and allowed to finish running. Further calls to the function will use the version in the archives/loose files, possibly issuing errors if there are parameter or return type mismatches (usually because some other changed or removed function is calling it).

Adding/Removing/Changing Function Variables[edit | edit source]

The function will be noted as different and the old version of the function will be loaded from the save game and allowed to finish running. Further calls to the function will use the version in the archives/loose files.

Changing Code[edit | edit source]

The function will be noted as different and the old version of the function will be loaded from the save game and allowed to finish running. Further calls to the function will use the version in the archives/loose files. Exception: If a function was native and is now scripted, the stack will be thrown out instead of resumed.

Function Examples[edit | edit source]

; Version 1
Scriptname MyScript extends ObjectReference

Event OnActivate(ObjectReference akActivator)
  MyFunction(1)
  MyFunction(2)
EndEvent

Function MyFunction(int aiMyValue)
  Debug.Trace("Entered my function version 1: aiMyValue = " + aiMyValue)
  ; Save is made *here*
  Debug.Trace("Left my function version 1: aiMyValue = " + aiMyValue)
EndFunction
; Version 2 - identical to version 1, except for the trace statements
Scriptname MyScript extends ObjectReference

Event OnActivate(ObjectReference akActivator)
  MyFunction(1)
  MyFunction(2)
EndEvent

Function MyFunction(int aiMyValue)
  Debug.Trace("Entered my function version 2: aiMyValue = " + aiMyValue)
  ; Save is made *here*
  Debug.Trace("Left my function version 2: aiMyValue = " + aiMyValue)
EndFunction
; Version 3
Scriptname MyScript extends ObjectReference

Event OnActivate(ObjectReference akActivator)
  Debug.Trace("OnActivate received!")
EndEvent

Save is made during MyFunction(1) called from OnActivate in version 1 of the script.

Script log before save:

Entered my function version 1: aiMyValue = 1

Script log after load with version 2:

warning: Function MyScript..MyFunction in stack frame 2 in stack 457 differs from the in-game resource files - using version from save
...
Left my function version 1: aiMyValue = 1
Entered my function version 2: aiMyValue = 2
Left my function version 2: aiMyValue = 2

Script log after load with version 3:

warning: Function Myscript..MyFunction in stack frame 2 in stack 457 doesn't exist in the in-game resource files - using version from save
warning: Function MyScript..OnActivate in stack frame 1 in stack 457 differs from the in-game resource files - using version from save
...
Left my function version 1: aiMyValue = 1
error: Method MyFunction not found on MyScript. Aborting call and returning None
... <later, after another activation>
OnActivate received!

FormLists[edit | edit source]

If entries are added to a FormList by a script, and the game is saved and reloaded, then the additional entries will be restored from the save file. If the FormList is modified in the Creation Kit between saving and loading, then those modifications will also take effect: after reloading the save, the FormList will contain all elements specified in the CK as of loading (not as of saving), followed by all elements which had previously been added by scripts (as of saving). If the FormList's length is changed in the CK, this implies that script-added entries will have different indecies after reloading than they did when saving. A FormList's Revert() method discards all script-added entries from the list, including entries added during a previous play session which were then restored from a save file.

; FormList Property fl Auto
; Form Property Dragonstone Auto
; CK defines fl = [Torch, Lockpick]
Debug.Trace( fl.GetSize() ) ; 2
Debug.Trace( fl.GetAt(0) ) ; Torch
Debug.Trace( fl.GetAt(1) ) ; Lockpick
Debug.Trace( fl.GetAt(2) ) ; None
fl.AddForm(Dragonstone)
Debug.Trace( fl.GetSize() ) ; 3
Debug.Trace( fl.GetAt(2) ) ; Dragonstone
Game.SaveGame("save1")
; CK redefines fl = [Lockpick, IronOre, IronIngot] ; save1 is loaded, script execution resumes here
Debug.Trace( fl.GetSize() ) ; 4
Debug.Trace( fl.GetAt(0) ) ; Lockpick
Debug.Trace( fl.GetAt(1) ) ; IronOre
Debug.Trace( fl.GetAt(2) ) ; IronIngot
Debug.Trace( fl.GetAt(3) ) ; Dragonstone

Missing Plugin[edit | edit source]

If a form which is provided by some plugin is added to a FormList by a script, and that plugin is removed or disabled between saving and reloading the game, then the affected entries in the FormList will not be removed, but will instead be changed to None and may be moved within the list. If the game is saved again at this point and the missing plugin is restored, the FormList entries which became None will nonetheless remain None and will not be removed. A FormList's Revert() method will still remove these missing entries, however, just as it removes all script-added entries.

; FormList Property fl Auto
; Form Property Dragonstone Auto
; CK defines fl = [Torch, Lockpick]
Debug.Trace( fl.GetSize() ) ; 2
fl.AddForm(Game.GetFormFromFile(0x123, "Plugin.esp"))
fl.AddForm(Dragonstone)
Debug.Trace( fl.GetSize() ) ; 4
Debug.Trace( fl.GetAt(0) ) ; Torch
Debug.Trace( fl.GetAt(1) ) ; Lockpick
Debug.Trace( fl.GetAt(2) ) ; PluginItem0x123
Debug.Trace( fl.GetAt(3) ) ; Dragonstone
Game.SaveGame("save1")
; Plugin.esp is disabled or removed, save1 is loaded, script execution resumes here
Debug.Trace( fl.GetSize() ) ; 4
Debug.Trace( fl.GetAt(0) ) ; None
Debug.Trace( fl.GetAt(1) ) ; Torch
Debug.Trace( fl.GetAt(2) ) ; Lockpick
Debug.Trace( fl.GetAt(3) ) ; Dragonstone
Game.SaveGame("save2")
; Plugin.esp is restored, save2 is loaded, script execution resumes here
Debug.Trace( fl.GetAt(0) ) ; None
Debug.Trace( fl.GetAt(1) ) ; Torch
Debug.Trace( fl.GetAt(2) ) ; Lockpick
Debug.Trace( fl.GetAt(3) ) ; Dragonstone

There is no way to add None to a FormList without actually removing a plugin; the CK does not offer None as a drag-and-drop option, and FormList.AddForm(None) has no effect. Similarly, FormList.HasForm(None) and FormList.Find(None) will always return False and -1, respectively, even if there are None-entries at the beginning of the list as a result of a missing plugin.


Language: English