Controlling which quest fields are serialized

Announcements, support questions, and discussion for the Dialogue System.
effalumper
Posts: 11
Joined: Wed Dec 11, 2024 9:09 am

Controlling which quest fields are serialized

Post by effalumper »

Hi there,

I have a question about related to serializing quest fields. I think that currently all quest fields get serialized, but I would like to avoid that for two reasons:

1. Many of my quest fields are not intended to change during the lifetime of the game, so serializing them bloats the save files unnecessarily
2. Some quest fields are changing in the course of development, and we would like existing saves to reflect the latest value we have given these fields. For example, I just changed the description of a quest, but any old saves still show the old description. I also use quest fields to define the number of times a certain action must be performed (which I mention to illustrate the fact that this issue relates both to built in fields and to custom fields).

I guess for custom fields, one approach is to not store that data as quest fields, but to store it externally somehow. But I'd like to keep the data with the quest if possible. Is there another approach you can recommend to handle these kinds of situations? Thanks in advance,

Andy
User avatar
Tony Li
Posts: 23376
Joined: Thu Jul 18, 2013 1:27 pm

Re: Controlling which quest fields are serialized

Post by Tony Li »

Hi,

By default, the Dialogue Manager's Persistent Data Settings section provides two options:
  • Include All Item & Quest Data = UNticked: Only saves quest states and quest entry states ("State" field and "Entry # State" fields).
  • Include All Item & Quest Data = ticked: Saves all quest fields.
If you want to save specific quest fields, UNtick Include All Item & Quest Data and either write a custom saver or assign a method to PersistentDataManager.GetCustomSaveData. If you do the latter, the method should return a string containing Lua code, such as:

Code: Select all

Quest["MyQuest"].TimeLeft = 42; Quest["AnotherQuest"].XP = 50
effalumper
Posts: 11
Joined: Wed Dec 11, 2024 9:09 am

Re: Controlling which quest fields are serialized

Post by effalumper »

Hi Tony - thanks for your reply. I'm a bit confused now though. In my case, I don't have that option ticked:
Screenshot 2025-07-04 at 01.08.25.png
Screenshot 2025-07-04 at 01.08.25.png (113.58 KiB) Viewed 190 times
Yet the persistent data (retrieved using PersistentDataManager.GetSaveData() ) seems to include all of the fields, for example this:

Code: Select all

Item["find_crystal"]={Name="find_crystal", Pictures="[]", Description="Find crystals ([var=CompletedTaskCount]/[var=RequiredTaskCount] found!).", Success_Description="", Failure_Description="", State="returnToNPC", Is_Item=false, StartCode="", CompletedTaskCount=0, Trackable=true, Track=true, AllowSpawnInSub=true, Ambience="default", MustBringProfessor=true, RequiredTaskCount=4, };
I'm not sure if I misunderstood something here, or if this is a bug, or if I'm doing something else wrong? Thanks again for your help, regards,

Andy
User avatar
Tony Li
Posts: 23376
Joined: Thu Jul 18, 2013 1:27 pm

Re: Controlling which quest fields are serialized

Post by Tony Li »

Hi,

Have you added a (deprecated) GameSaver component to your scene? If so, you can probably remove it. If for some reason you can't remove it, make sure its Include All Item Data checkbox isn't ticked.

If you play DemoScene1 in the Unity editor and tick the Dialogue Manager's PlayerPrefsSavedGameDataStorer > Debug checkbox, then when you save a game you'll see something like this in the Console:

Save System: Storing in PlayerPrefs key Save1: {"m_version":0,"m_sceneName":"DemoScene1","m_list":[{"key":"ds","sceneIndex":-1,"data":"Variable={Alert=\"\", enemiesKilled=0, hasLaunchCodes=false, password=\"\", Actor=\"\", Conversant=\"\", ActorIndex=\"\", ConversantIndex=\"\"}; Item[\"Get_the_Launch_Codes\"].State=\"unassigned\"; Item[\"Get_the_Launch_Codes\"].Track=true; Item[\"Enemy_Attack\"].State=\"unassigned\"; Item[\"Enemy_Attack\"].Track=true; Item[\"Enemy_Attack\"].Entry_1_State=\"active\"; Actor={Player={Name=\"Player\", Pictures=\"[../../Art/Portraits/Player.png]\", Description=\"The Player controls a soldier sent to intercept the evil space emperor's launch codes to prevent him from attacking your planet.\", IsPlayer=true}, Private_Hart={Name=\"Private Hart\", Pictures=\"[../../Art/Portraits/Private Hart.png]\", Description=\"Private Hart is a kind-hearted soldier who offers the quest to hack the emperor's computer to get the launch codes.\", IsPlayer=false}, Sergeant_Graves={Name=\"Sergeant Graves\", Pictures=\"[../../Art/Portraits/Sergeant Graves.png]\", Description=\"Sergeant Graves is a warlike soldier who offers the quest to hold back enemy forces.\", IsPlayer=false}, Terminal={Name=\"Terminal\", Pictures=\"[]\", Description=\"The Terminal is a computer interface through which the player can get the launch codes.\", IsPlayer=false}, Dead_Enemy={Name=\"Dead Enemy\", Pictures=\"[]\", Description=\"The evil emperor's guards. They have a note with the terminal password.\", IsPlayer=false}, Enemy={Name=\"Enemy\", Pictures=\"[]\", Description=\"The Enemy actor is used for enemies to bark menacing one-off gameplay lines that threaten the Player.\", IsPlayer=false}}; StatusTable = \"\"; RelationshipTable = \"\"; "},{"key":"Player","sceneIndex":0,"data":"{\"scene\":0,\"position\":{\"x\":-5.7772321701049809,\"y\":0.07999980449676514,\"z\":-3.7402756214141847},\"rotation\":{\"x\":0.0,\"y\":0.55939120054245,\"z\":0.0,\"w\":0.8289037942886353}}"}]}

I bolded the part of item/quest data that gets saved when the Include All Item Data checkbox is UNticked, which you can see is only the runtime quest info.
effalumper
Posts: 11
Joined: Wed Dec 11, 2024 9:09 am

Re: Controlling which quest fields are serialized

Post by effalumper »

Aha - I'm not using the GameSaver component, but I do see what happened now. Although I had the "Include All Item Data " field unchecked, this had been added in code at some point:

Code: Select all

PersistentDataManager.includeAllItemData = true
... and it seems both this static value and the value set on the component get checked. So that explains why everything was being saved. And now I have fixed that, I managed to get it to just save the fields I want by setting PersistentDataManager.GetCustomSaveData, as you suggest. Thanks very much for your help! Kind regards,

Andy
User avatar
Tony Li
Posts: 23376
Joined: Thu Jul 18, 2013 1:27 pm

Re: Controlling which quest fields are serialized

Post by Tony Li »

Glad you found the issue!
effalumper
Posts: 11
Joined: Wed Dec 11, 2024 9:09 am

Re: Controlling which quest fields are serialized

Post by effalumper »

Oh - maybe I spoke too soon about having it working. It worked in as much as only the fields I wanted to save are being serialised now. However, I now face an issue that it seems like in the Lua world, not all the fields are initialised. The quest log is complaining "[Warning] Dialogue System: A quest name (item name in Item[] table) is null or empty", and when I use the debugger to inspect the items it has received like this:

Code: Select all

LuaTableWrapper itemTable = Lua.Run("return Item").asTable;
... I find they only contain the fields that I saved via my PersistentDataManager.GetCustomSaveData. However, I see that in during ApplySaveData, in a call to PersistentDataManager.EnsureQuestsExist, the following code seems to get all the fields into itemTable

Code: Select all

var itemTable = Lua.Environment.GetValue("Item") as Language.Lua.LuaTable;
I think the other fields should get set up on the Lua side, but I'm not sure why they are not... did I do something else wrong? Again, thanks in advance for any advice...
User avatar
Tony Li
Posts: 23376
Joined: Thu Jul 18, 2013 1:27 pm

Re: Controlling which quest fields are serialized

Post by Tony Li »

Hi,

Here's an example.

I added field named "XP" to quests:

xpField.png
xpField.png (98.47 KiB) Viewed 105 times

Then I added this script to the Dialogue Manager in DemoScene1:

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;

public class ExtraSaveData : MonoBehaviour
{
    void Start()
    {
        PersistentDataManager.GetCustomSaveData = GetCustomSaveData;
    }

    private string GetCustomSaveData()
    {
        var s = string.Empty;
        foreach (var quest in DialogueManager.masterDatabase.items)
        {
            if (quest.IsItem) continue;
            var questName = DialogueLua.StringToTableIndex(quest.Name);
            var xp = quest.LookupInt("XP");
            s += $"Item[\"{questName}\"].XP = {xp}; ";
        }
        return s;
    }
}
When I save the game, it logs:

Save System: Storing in PlayerPrefs key Save1: {"m_version":0,"m_sceneName":"DemoScene1","m_list":[{"key":"ds","sceneIndex":-1,"data":"Variable={Alert=\"\", enemiesKilled=0, hasLaunchCodes=false, password=\"\", Actor=\"\", Conversant=\"\", ActorIndex=\"\", ConversantIndex=\"\"}; Item[\"Get_the_Launch_Codes\"].State=\"unassigned\"; Item[\"Get_the_Launch_Codes\"].Track=true; Item[\"Enemy_Attack\"].State=\"unassigned\"; Item[\"Enemy_Attack\"].Track=true; Item[\"Enemy_Attack\"].Entry_1_State=\"active\"; Actor={Player={Name=\"Player\", Pictures=\"[../../Art/Portraits/Player.png]\", Description=\"The Player controls a soldier sent to intercept the evil space emperor's launch codes to prevent him from attacking your planet.\", IsPlayer=true}, Private_Hart={Name=\"Private Hart\", Pictures=\"[../../Art/Portraits/Private Hart.png]\", Description=\"Private Hart is a kind-hearted soldier who offers the quest to hack the emperor's computer to get the launch codes.\", IsPlayer=false}, Sergeant_Graves={Name=\"Sergeant Graves\", Pictures=\"[../../Art/Portraits/Sergeant Graves.png]\", Description=\"Sergeant Graves is a warlike soldier who offers the quest to hold back enemy forces.\", IsPlayer=false}, Terminal={Name=\"Terminal\", Pictures=\"[]\", Description=\"The Terminal is a computer interface through which the player can get the launch codes.\", IsPlayer=false}, Dead_Enemy={Name=\"Dead Enemy\", Pictures=\"[]\", Description=\"The evil emperor's guards. They have a note with the terminal password.\", IsPlayer=false}, Enemy={Name=\"Enemy\", Pictures=\"[]\", Description=\"The Enemy actor is used for enemies to bark menacing one-off gameplay lines that threaten the Player.\", IsPlayer=false}}; StatusTable = \"\"; RelationshipTable = \"\"; Item[\"Get_the_Launch_Codes\"].XP = 50; Item[\"Enemy_Attack\"].XP = 25; "}

I bolded the extra data that the script added.

When I load the saved game, it restores these values.
effalumper
Posts: 11
Joined: Wed Dec 11, 2024 9:09 am

Re: Controlling which quest fields are serialized

Post by effalumper »

Hi again,

Right - that much is working for me too. But what seems to *not* work is the quest log. From what I can see that seems to be because the other values (including the description etc) have not been restored on the Lua side (though I can see that they are set on in the C# objects read from the database). So I see warnings like this in the logs:

Code: Select all

[Warning] Dialogue System: A quest name (item name in Item[] table) is null or empty
QuestLog.GetAllQuests() at /Plugins/Pixel Crushers/Dialogue System/Scripts/Quests/QuestLog.cs:1189

QuestLog.GetAllQuests() at /Plugins/Pixel Crushers/Dialogue System/Scripts/Quests/QuestLog.cs:1117

StandardUIQuestTracker/<RefreshAtEndOfFrame>d__28.MoveNext() at /Plugins/Pixel Crushers/Dialogue System/Scripts/UI/Standard/Quest/StandardUIQuestTracker.cs:212

GUIUtility.ProcessEvent() at /Users/bokken/build/output/unity/unity/Modules/IMGUI/GUIUtility.cs:219
... and the quest log is not displaying any data for me. In the example you set up, did you have the quest log working?
User avatar
Tony Li
Posts: 23376
Joined: Thu Jul 18, 2013 1:27 pm

Re: Controlling which quest fields are serialized

Post by Tony Li »

Hi,

Yes, it works correctly. Are you resetting the entire Item[]/Quest[] table somehow? (Quest[] is an alias for Item[].) Assume in your GetCustomSaveData method that the Item[] table is already created and populated with the data from your dialogue database and the quest states from the saved game. Your GetCustomSaveData method should return the Lua code that assigns values to any extra fields. It shouldn't wipe the entire table. The code from my example that does this is:

Code: Select all

var s = string.Empty;
foreach (var quest in DialogueManager.masterDatabase.items)
{
    if (quest.IsItem) continue;
    var questName = DialogueLua.StringToTableIndex(quest.Name);
    var xp = quest.LookupInt("XP");
    s += $"Item[\"{questName}\"].XP = {xp}; ";
}
return s;
Post Reply