Persisting data (Civ5)
This page is a part of the Lua and UI Reference.
This article explains how to store data that are persisted when the user saves his game, or at anytime for mods settings.
Opening or creating a storage space
Mods can store data in the following containers:
- Global storage spaces accessible at any time. To store mods settings for example.
- One local storage space embedded in savegames. A zombie mod could for example use it to store the locations where units died in case they should be resurrected later.
Global storage spaces
You create/open such a space by using: ModData Modding.OpenUserData(string name, int version)
.
- Many modders specify their mod's GUID as a name. However this is not mandatory and it is not necessary the best practice for the end-user since it produces undecipherable files names. We suggest you just make sure to provide a unique name to avoid conflicts between mods.
- This storage space will be stored under a file in
My Games/Sid Meier's Civilization V/ModUserData/
. The file name will be the name you provided with the ".db" extension.
- Mods can use the same space if they share common settings for example. However it cannot be used as a communication channel it is not possible to listen for changes.
- One mod can create as much spaces as it wants.
Savegame's storage space
You can create/open the savegame's storage space by using: ModData Modding.OpenSaveData()
.
- This space is shared by all mods, so beware of keys names conflicts: when saving a property try to prepend its name with a unique prefix for your mod "IGE_" for In-game editor, "RED_" for R.E.D., etc.
- The content of this space is written directly in the savegame file whenever the user saves his game.
Using the storage space
Get or set value
Once you retrieve a ModData instance, this one exposes the following methods:
A few remarks:
- Data can be nil, bool, number or string.
- If you query a value never assigned before, it will be nil.
Dealing with default values
Since it is not possible to know whether the value you just did read already existed before, you need to deal with the default nil values you will encounter. Here are some examples:
local db = Modding.OpenSaveData()
local trueByDefault = db.GetValue("trueByDefault") ~= "false" -- False if "false", true otherwise
local falseByDefault = db.GetValue("falseByDefault") == "true" -- True if "true", false otherwise
local oneByDefault = db.GetValue("oneByDefault") or 1 -- 1 if nil or false, the value otherwise
Addressing performances problems
The ModData you get is implemented through a database table. This means that every value read/write will generate a request string, this one will have to be parsed, etc. If you frequently read or write values, this can greatly hinder the game's performances.
Using a local cache
Dealing with the reads is pretty easy, we only need to create a local copy of the properties, as demonstrated in the G&K scenarios. Instead of calling GetValue and SetValue directly, we will use the following wrappers:
g_SaveData = Modding.OpenSaveData()
g_Properties = {}
function GetPersistentProperty(name)
if not g_Properties[name] then
g_Properties[name] = g_SaveData.GetValue(name)
end
return g_Properties[name]
end
function SetPersistentProperty(name, value)
if GetPersistentProperty(name) == value then return end
g_SaveData.SetValue(name, value)
g_Properties[name] = value
end
Last-chance writes
The local described in the previous section unfortunately does not address the long write times. A good idea in theory would be to detect when the user is going to save his game so that you only write data at this point. Unfortunately there is no event for this circumstance and while it is possible to hijack the "quicksave" hotkey and the "save" menu, this creates a whole bunch of incompatibilities with other mods. Use it in last resort. If you want to see an implementation example, look at the R.E.D. WWII mod.
Another alternative is to use custom tables, as described in the next section.
Alternatives
Using a custom table
The problem with ModData as we said earlier is that it relies internally on a data table. As a result, every write generates a request, which is slow. However, writing one or thousands of values at once in the datatable makes small difference in the execution time. So if you need to save large batches of data at once, using a custom table can be a great alternative. This can be done through DB.Query(variant data).
.
Here is an example where we store magic points for some units:
-- Create table if not already done on mod loading
if not GetPersistentProperty("MyMod_initialized") then
DB.Query("CREATE TABLE UnitsMagic( id INTEGER PRIMARY KEY UNIQUE, value INTEGER);")
SetPersistentProperty("MyMod_initialized", true)
end
-- SaveAll takes a table[playerID][unitID] = value
function SaveAll(magicPoints)
local query = ""
for playerID, playerTable in ipairs(magicPoints) do
for unitID, magicPoints in ipairs(playerTable) do
-- Units' ID are player-specific only, so we compute a global unique identifier from the player's and unit's identifiers.
local ID = playerID * 10000 + unitID
query = query.."REPLACE INTO UnitsMagic (id, value) VALUES ("..ID..", "..magicPoints..");\n"
end
end
DB.Query(query)
end
Using the script data (mostly deprecated)