How to persist values across saves

wltk

Chieftain
Joined
Feb 10, 2025
Messages
48
I'm working on some mod that needs to set and get variables across saves. Here is a working mechanism I found:
JavaScript:
GameTutorial.setProperty(key, value);
GameTutorial.getProperty(key);

Yes it is abusing the GameTutorial, but it is also used by the game itself in Base\modules\core\ui\utilities\utility-serialize.js. In the code comment:
* @description Uses property chunking to persist values into and across saves.
* Abuses the Tutorial read/write store.
* A top level "catalog" object tracks all the objects in the store.
* Each object can have multiple properties read/writen
However, this file suggests us to use the Catalog interface in this class, which is also used by Base\modules\base-standard\ui\quest-tracker\quest-tracker.js for tracking quests. Based on the usage, it seems like it will store many key-value pairs for a single map object. Technically you can also JSON.stringify a map object and store under a single key, then do JSON.parse after retrieving it. I'm not sure which option is more performant.

I plan to leverage the Catalog interface in my mod for now, but let me know if there are better ways :).
JavaScript:
const catalog = new Catalog("MyStuff");
const obj = catalog.getObject("foo");
const old = obj.read("someKey");
obj.write("someKey", "New data");


Update: The above only works for settings or values within a game. To see how to store settings globally, see comment below: https://forums.civfanatics.com/threads/how-to-persist-values-across-saves.696187/post-16793464.
 
Last edited:
I've seen there is even Configuration.getUser().setValue(key, value) and its corrispective Configuration.getUser().getValue(key), which allows to store a configuration string. It's used only once, but I've managed to use it to store values across saves Edit: nope, it doesn't work. See following post to see how to persist data. This post is still valid to see how to show options in game menu. Remember to load these UIScript even in "shell" target other than "game" if you want to see it in Main menu too.

Furthermore it's easy this way to add a configuration option to the Game tab in the Options panel. Not sure about side-effects any way

JavaScript:
import { Options, OptionType } from '/core/ui/options/model-options.js';
import { CategoryType } from '/core/ui/options/options-helpers.js';

// We add a dependency on the Options module to ensure default options are loaded before we add our own
import '/core/ui/options/options.js';

const onOptionColorfulInit = (optionInfo) => {
    optionInfo.currentValue = Configuration.getUser().getValue("LFPolicyYields_Colorful") === "true";
};
const onOptionColorfulUpdate = (optionInfo, value) => {
    Configuration.getUser().setValue("LFPolicyYields_Colorful", value ? "true" : "false");
}

Options.addInitCallback(() => {
    Options.addOption({
        category: CategoryType.Game,
        // @ts-ignore
        group: 'lf_yields',
        type: OptionType.Checkbox,
        id: "lf-yields-colorful",
        initListener: onOptionColorfulInit,
        updateListener: onOptionColorfulUpdate,
        label: "LOC_MOD_LF_YIELDS_OPTION_COLORFUL",
        description: "LOC_MOD_LF_YIELDS_OPTION_COLORFUL_DESC"
    });
});
 
Last edited:
Configuration.getUser().SetValue dont persist when you exit game . Just store value during until Civ closed

Catalog not accessible before game start
got wierd behavior with localstorage .....

JavaScript:
localStorage.setItem("STORAGE_KEY1",  "value1");
console.error("stored 1: "+ localStorage.getItem("STORAGE_KEY1"));
localStorage.setItem("STORAGE_KEY2", "value2");
console.error("stored 2: "+ localStorage.getItem("STORAGE_KEY2"));
console.error("stored 1: "+ localStorage.getItem("STORAGE_KEY1"));
console.error("stored any: "+ localStorage.getItem("any"));


[2025-03-06 10:44:40] stored 1: value1
[2025-03-06 10:44:40] stored 2: value1
[2025-03-06 10:44:40] stored 1: value1
[2025-03-06 10:44:40] stored any: value1

seem that localStorage as unique item allowed by file and that Key is overrid by caller_file_name when using get or Set ...

i finished with all settings in a single object:

JavaScript:
    loadSettings() {
        try {
            const settings = localStorage.getItem("MattMomentoSettings");
            if (settings) {
                this.settings = JSON.parse(settings);
            } else {
                console.warn("[WARNING] No settings found, using default value");
            }
        } catch (e) {
            console.error('Failed to load mementos settings:', e);
        }
    }
    
    saveSettings() {
        localStorage.setItem("MattMomentoSettings", JSON.stringify(this.settings));
    }
 
Last edited:
I got the feeling, but not enough coding experience to check, that Configuration.getUser().setValue is saved to the savegame, instead of to some form of user setting. I've tested the newest update of Leugi's Diplomacy Banner Tweaks, and those mod options do persist if I save, restart the game and reload that save, but not if I restart the game and start a new game (or load an old save that doesn't have those mod options).
 
@leonardify seem there is bad interaction having multiple mod on localStorage. and all mod ahre the same storage.

look at logs since i DL last version of your mod:
[2025-03-07 17:13:15] save : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_CORPUS_JURIS_CIVILIS","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"]}
[2025-03-07 17:13:17] save : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"]}
[2025-03-07 17:13:18] load : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"]}
[2025-03-07 17:13:18] save : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"]}
[2025-03-07 17:13:49] [LFPolicyYieldsSettings] saving.. {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_CORPUS_JURIS_CIVILIS","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"],"IsColorful":true}
[2025-03-07 17:14:23] load : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_CORPUS_JURIS_CIVILIS","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"],"IsColorful":true}
[2025-03-07 17:14:23] save : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_CORPUS_JURIS_CIVILIS","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"],"IsColorful":true}
[2025-03-07 17:14:25] save : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"],"IsColorful":true}
[2025-03-07 17:14:27] load : {"showOnlyUnlocked":false,"showFavorites":true,"favoritesMomento":["MEMENTO_FOUNDATION_COLADA_TIZONA","MEMENTO_FOUNDATION_CORPUS_JURIS_CIVILIS","MEMENTO_FOUNDATION_EA_NASIR","MEMENTO_FOUNDATION_TRAVELS_MARCO_POLO"],"IsColorful":true}
 
Last edited:
k. Here a version that ALL mod using local storage must use.

i update mine: https://forums.civfanatics.com/resources/mattifuss-memento-filter.32028/updates

JavaScript:
loadSettings() {
        const modSettings = localStorage.getItem("modSettings");
        try {
            if (modSettings) {
                const modSetting = JSON.parse(modSettings)["YOUY_MOD_NAME"]
                if(modSetting){
                    this.settings = modSetting;
                }
            }
            else {
                console.warn("[YOUY_MOD_NAME] No settings found, using defaults");
            }
        }
        catch (e) {
            console.error("[YOUY_MOD_NAME] Error loading settings", e);
        }
    }
   
    saveSettings() {
        const modSettings = JSON.parse(localStorage.getItem("modSettings") || '{}');
        modSettings["YOUY_MOD_NAME"] = this.settings
        localStorage.setItem("modSettings", JSON.stringify(modSettings));
    }


For those using ClassSettings:


JavaScript:
export const PolicyYieldsSettings = new class {
    _data = {
        IsColorful: false
    };

    constructor() {
        const modSettings = localStorage.getItem("modSettings");
        try {
            if (modSettings) {
                const modSetting = JSON.parse(modSettings)["YOUR_MOD_NAME"]
                if(modSetting){
                    this._data = modSetting;
                }
            }
            else {
                console.warn("[YOUR_MOD_NAME] No settings found, using defaults");
            }
        }
        catch (e) {
            console.error("[YOUR_MOD_NAME] Error loading settings", e);
        }
    }

    save() {
        const modSettings = JSON.parse(localStorage.getItem("modSettings") || '{}');
        modSettings["YOUR_MOD_NAME"] = this._data
        localStorage.setItem("modSettings", JSON.stringify(modSettings));
    }

    get IsColorful() {
        return this._data.IsColorful;
    }

    set IsColorful(value) {
        this._data.IsColorful = value;
        this.save();
    }
}
 
Last edited:
Ha @Mattifus very unfortunate. I thought it was only one for file, not one global localStorage.
What about AppOptions? I don't like the idea, but something like

JavaScript:
UI.setApplicationOption("Mods", "LFPolicyYields_Colorful", ...);
UI.commitApplicationOptions(); // this should be necessary in Game Options screen, already called

UI.getApplicationOption(...)

Nope, it doesn't seem to work.
 
PS: The strangest thing is that there effectively _multiple_ saved keys

I think I've found the issue; if you've got already _one_ key saved, even if `.setItem` will add another one, `.getItem` will still get the first. Gosh
1741396075389.png
 

Attachments

  • 1741391884859.png
    1741391884859.png
    30 KB · Views: 22
Ok, it's a bit harsh, but clearing the storage if it has more than one key, seems to make it work. If we use all the following manager it could be doable (until Firaxis starts using the local storage, or until a modder skips this thread :D)

JavaScript:
/**
 * Please, always use ModSettingsManager to save and read settings in your mod.
 * Right now if you try to use **multiple** keys in localStorage, it will break reading
 * from localStorage for **every mod**. This is a workaround to avoid this issue, while
 * keeping a namespace to give each mod its own settings.
 */
const ModSettingsManager = {
    save(key, data) {
        if (localStorage.length > 1) {
            console.warn("[ModSettingsManager] erasing previous storage..", localStorage.length);
            localStorage.clear();
        }
        const modSettings = JSON.parse(localStorage.getItem("modSettings") || '{}');
        modSettings[key] = data;
        localStorage.setItem("modSettings", JSON.stringify(modSettings));
    },
    read(key) {
        const modSettings = localStorage.getItem("modSettings");
        try {
            if (modSettings) {
                const data = JSON.parse(modSettings || '{}')[key];
                if (data) {
                    return data;
                }
            }
            return null;
        }
        catch (e) {
            console.error(`[ModSettingsManager][${key}] Error loading settings`, e);
        }
        return null;
    }
}

I'm adding this to Policy Yield Previews, hoping for the best
 
k, so the fix i made was ok, but not work having other mod not using storagelocal before us. Thx to wrap it in a ManagerClass.

@wltk : can you add link in HUGE size to that post on Top message plz ?
 
Last edited:
k, so the fix i made was ok, but not work having other mod not using storagelocal before us. Thx to wrap it in a ManagerClass.

@wltk : can you add link in HUGE size to that post on Top message plz ?
Done. I'd say I'll stick with my Catalog option since I don't have the need for cross games settings yet lol. There wasn't an option to saving settings cross games in Civ6 if I remember correctly. I'd hope Firaxis can actually provide a better interface.
 
I was playing around with the localStorage solution for adding settings to mods and I think it's workable if we coordinate around a single key and write to different namespaces (stringified uniquely keyed sub-objects) for each mod. I wrote up an api mod that hides this functionality behind Options, hoping to limit the surface for mods accidentally obliterating another's settings. It also has some other nice features, such as easy access to a setting's values and changes, placement in the ui on declaration, and it automatically resets cancelled menu changes just like normal game options. Here's the mod, and a simple example mod using the api.

Mod Options API mod
Choose Starting Layers mod
 
I was playing around with the localStorage solution for adding settings to mods and I think it's workable if we coordinate around a single key and write to different namespaces (stringified uniquely keyed sub-objects) for each mod. I wrote up an api mod that hides this functionality behind Options, hoping to limit the surface for mods accidentally obliterating another's settings. It also has some other nice features, such as easy access to a setting's values and changes, placement in the ui on declaration, and it automatically resets cancelled menu changes just like normal game options. Here's the mod, and a simple example mod using the api.

Mod Options API mod
Choose Starting Layers mod
It's what i wrote last week and Leo wrap in a the ModSettingsManager already.

It's a good thing to have a true mod for doing it all.
What missing: Some sort of lock just before read before writing to avoid any overwrites.
 
Last edited:
doing mod option is a step, the real one would be a ModCustomizerManagerMod
- register cutomizable setting like:

JSON:
"name": "optionName"
"type": "integer"
"allowed_value": [2-4]
"title": "gggg"
"description": "fgfgf"
"default_value": 2

- the ModCustomizerManagerMod will have a panel to edit all registrer settings. a comboox to switch between mods.

Doing , that all mod can have customizable settings without easily. No more 36 mod to change colors of yield, a simgle one customizable

i will try to make that, i think, it would be nice.
 
Last edited:
I wrote up an api mod that hides this functionality behind Options, hoping to limit the surface for mods accidentally obliterating another's settings. It also has some other nice features, such as easy access to a setting's values and changes, placement in the ui on declaration, and it automatically resets cancelled menu changes just like normal game options.
What happens if a mod user downloads the main mod but not the Mod Options API mod? Will the main mod still fully function with default settings, or will it fail to load/activate? If the latter, then this might not be a good solution for button-clicking-without-reading mod users, and there are many. This site doesn't have a way to handle dependency mods on download afaik, and the link in your example mod is easy to miss even for people that do scan the mod description.
 
Back
Top Bottom