Saving additional data from dialog entry field

Announcements, support questions, and discussion for the Dialogue System.
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving additional data from dialog entry field

Post by Tony Li »

Glad to help!
mrcsbmr
Posts: 31
Joined: Mon Mar 21, 2016 5:26 am

Re: Saving additional data from dialog entry field

Post by mrcsbmr »

Hi,

I'm sorry I have to come back to this, but we're experiencing a problem. When adding new conversations or dialog lines within Articy and make a new export, old saves that save an extra field using the script don't work with the new dialog database anymore.
This is happening because the SaveExtraField script adds the values of our extra field by referencing to the index of the dialog and the conversation in the arrays in the database. When we add dialogs or entries, those indexes might change and the references break.
Any smart idea how we could solve this?

Thanks,
Marcus
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving additional data from dialog entry field

Post by Tony Li »

Hi Marcus,

Every dialogue entry has an "Articy Id" field that's a unique, static string. Could you use this instead of the id?
mrcsbmr
Posts: 31
Joined: Mon Mar 21, 2016 5:26 am

Re: Saving additional data from dialog entry field

Post by mrcsbmr »

Hi Tony,

I thought of that, but the problem is... right now the entries are refered to by the conversation index and dialogue entry index of the tables in the Lua environment. We have about 700 lines that are affected. In the whole dialog database however, we have about 7000 lines. If I'd store the Articy Id instead and attempt to refer to the entries just with that ID, I'd have to iterate through all conversations and dialogue entries to find the entries which would definitely cause the game to lag.
Right now we're directly targeting the 700 entries using the indexes.
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving additional data from dialog entry field

Post by Tony Li »

What if you were to create a dictionary once, and use it from then on to look up dialogue entries by Articy Id? Something like:

Code: Select all

Dictionary<string, DialogueEntry> entriesByArticyId;

void Start()
{
    entriesByArticyId = new Dictionary<string, DialogueEntry>();
    foreach (var conversation in DialogueManager.MasterDatabase.conversations)
    {
        foreach (var entry in conversation.dialogueEntries)
        {
            var articyId = Field.LookupValue(entry.fields, "Articy Id");
            entriesByArticyId.Add(articyId, entry);
        }
    }
}
...
    var someEntry = entriesByArticyId[someArticyId];
If you wanted to make the dictionary smaller, you could store only the 700 entries that are affected.
mrcsbmr
Posts: 31
Joined: Mon Mar 21, 2016 5:26 am

Re: Saving additional data from dialog entry field

Post by mrcsbmr »

Thanks for your input. I added a dictionary as you suggested, and I think I'm nearly there, but I'm getting an error from the Lua system when loading now.

I modified the code as follows:

In the AddCurrentValueFieldsToLua method, I also add the Articy ID to the Lua environment. Within this method, I'm also filling the dictionary with all the entries that have to be saved and loaded:

Code: Select all

foreach (var dialogueEntry in conversation.dialogueEntries)
            {
                if (Field.LookupBool(dialogueEntry.fields, "IsSequence") || Field.LookupBool(dialogueEntry.fields, "IsLoop"))
                {
                    var currentValue = Field.LookupInt(dialogueEntry.fields, "CurrentValue");
                    bool isLoop = Field.LookupBool(dialogueEntry.fields, "IsLoop");
                    bool isSequence = Field.LookupBool(dialogueEntry.fields, "IsSequence");
                    string articyID = Field.LookupValue(dialogueEntry.fields, "Articy Id");
                    sb.AppendFormat("[{0}] = {{ CurrentValue = {1} ,  IsLoop = '{2}' ,  IsSequence = '{3}', ArticyId = '{4}' }}, ", 
                        dialogueEntry.id, currentValue, isLoop, isSequence, articyID);


                    // Add affected entry to dialog entry dictionary
                    entriesByArticyId.Add(articyID, dialogueEntry);
                }
            }
In GetMyCustomSaveData, I'm not saving "conversationId, entryId, currentValue;" anymore, but instead "articyId; currentValue":

Code: Select all

 foreach(KeyValuePair<string, DialogueEntry> entry in entriesByArticyId)
        {
            // Get the values stored in CurrentValue from the Lua environment
            sb.AppendFormat(Lua.Run("return Conversation[" + entry.Value.conversationID + "].Dialog[" + entry.Value.id + "].ArticyId").AsString + ";");
            sb.AppendFormat(Lua.Run("return Conversation[" + entry.Value.conversationID + "].Dialog[" + entry.Value.id + "].CurrentValue").AsString + ";");
        }
The two modifications above seem to work as intended. When I look into the Lua console while running the game (before loading), the fields have been added. The new string for the save system looks correct as well.

But loading doesn't work yet. I modified the RestoreCurrentValues method as follows: The method is looking through the string that has the Articy IDs and values and uses the Dictionary to find the entries in the database. The Lua command is supposed to assign the values to the database in the Lua environment.

Code: Select all

while (i < s.Length)
        {
            // Get the conversation index in the dialogue database:
            string articyID = s[i++];
            var value = Tools.StringToInt(s[i++]);
            DialogueEntry currentEntry = entriesByArticyId[articyID];

            var conversation = conversations[currentEntry.conversationID];


            // Conversation ID
            if (currentEntry.conversationID != lastConversationId)
            {
                sb.AppendFormat("Conversation[{0}].Dialog = {{", conversation.id);
                lastConversationId = currentEntry.conversationID;
            }

            // Dialogue Entires
            sb.AppendFormat("[{0}] = {{ CurrentValue = {1},  IsLoop = '{2}' ,  IsSequence = '{3}', ArticyId = '{4}' }}, ", 
                currentEntry.id, value, Field.LookupBool(currentEntry.fields, "IsLoop"), Field.LookupBool(currentEntry.fields, "IsSequence"),
                Field.LookupValue(currentEntry.fields, "Articy Id"));
        }
This is just the easiest modification from the version we've had before. I was wondering if I even need to assign IsLoop, IsSequence and ArticyId again or if those values would be kept since they didn't change anyways. This would allow me to write the method this way:

Code: Select all

while (i < s.Length)
        {
            // Get the conversation index in the dialogue database:
            string articyID = s[i++];
            var value = Tools.StringToInt(s[i++]);
            DialogueEntry currentEntry = entriesByArticyId[articyID];

            sb.Append("Conversation[" + currentEntry.conversationID + "].Dialog[" + currentEntry.id + "].CurrentValue = " + value);

        }
For some reason, neither of these work. I get an error saying:
Dialogue System: Lua code 'Variable={Alert="", ["C1_Morton.talkedTo"]=false, [....all the other variables...]; RestoreCurrentValues("0x0100000100001625;0;0x010000010000C3CD; [....all the other values...];")' threw exception 'Exception has been thrown by the target of an invocation.'

The log doesn't provide more information to get behind the problem as far as I see it. I guess I'm making something wrong in the loading method. After loading a saved game, the Lua environment loses all the added fields again (IsLoop, IsSequence, CurrentValue, ...).

Here's the whole code for SaveExtraField.cs as I have it now:

Code: Select all

using UnityEngine;
using System.Collections;
using System.Text;
using PixelCrushers.DialogueSystem;
using System.Collections.Generic;


public class SaveExtraField : MonoBehaviour
{

    public bool debug;
    private Dictionary<string, DialogueEntry> entriesByArticyId;


	public void RunStartFromOutside()
    {
		StartCoroutine (Start ());
	}

    private IEnumerator Start()
    {
        // Let Dialogue Manager load database first:
        yield return new WaitForEndOfFrame();

        // Then add in the custom CurrentValue fields to the Lua environment:
        AddCurrentValueFieldsToLua();

        // Register a Lua function to parse a string that compactly
        // records CurrentValue values:
        Lua.RegisterFunction("RestoreCurrentValues", this, SymbolExtensions.GetMethodInfo(() => RestoreCurrentValues(string.Empty)));

        // Tell PersistentDataManager to add our custom save data,
        // which will be a call to our custom Lua function RestoreCurrentValues,
        // passing in a compact record of CurrentValue values.
        PersistentDataManager.GetCustomSaveData = GetMyCustomSaveData;

    }

    private void AddCurrentValueFieldsToLua()
    {
        // Add CurrentValue fields to the runtime Lua environment in the form
        // Conversation[#].Dialog[#].CurrentValue.

        // Prepare dictionary
        entriesByArticyId = new Dictionary<string, DialogueEntry>();

        foreach (var conversation in DialogueManager.MasterDatabase.conversations)
        {
            var sb = new StringBuilder();
            sb.AppendFormat("Conversation[{0}].Dialog = {{", conversation.id);
            foreach (var dialogueEntry in conversation.dialogueEntries)
            {
                if (Field.LookupBool(dialogueEntry.fields, "IsSequence") || Field.LookupBool(dialogueEntry.fields, "IsLoop"))
                {
                    var currentValue = Field.LookupInt(dialogueEntry.fields, "CurrentValue");
                    bool isLoop = Field.LookupBool(dialogueEntry.fields, "IsLoop");
                    bool isSequence = Field.LookupBool(dialogueEntry.fields, "IsSequence");
                    string articyID = Field.LookupValue(dialogueEntry.fields, "Articy Id");
                    sb.AppendFormat("[{0}] = {{ CurrentValue = {1} ,  IsLoop = '{2}' ,  IsSequence = '{3}', ArticyId = '{4}' }}, ", 
                        dialogueEntry.id, currentValue, isLoop, isSequence, articyID);


                    // Add affected entry to dialog entry dictionary
                    entriesByArticyId.Add(articyID, dialogueEntry);
                    //Debug.Log("added id " + articyID + ", dialogue conv id: " + dialogueEntry.conversationID + ", d id: " + dialogueEntry.id);
                }
            }
            sb.Append("}; ");
            Lua.Run(sb.ToString(), debug);
        }
    }

    private string GetMyCustomSaveData()
    {
        // Return a string that calls our custom Lua function, passing it the data
        // necessary to set the CurrentValue fields of specific dialogue entries.

        var sb = new StringBuilder();
        sb.Append("RestoreCurrentValues(\"");

        foreach(KeyValuePair<string, DialogueEntry> entry in entriesByArticyId)
        {
            // Get the values stored in CurrentValue from the Lua environment
            sb.AppendFormat(Lua.Run("return Conversation[" + entry.Value.conversationID + "].Dialog[" + entry.Value.id + "].ArticyId").AsString + ";");
            sb.AppendFormat(Lua.Run("return Conversation[" + entry.Value.conversationID + "].Dialog[" + entry.Value.id + "].CurrentValue").AsString + ";");
        }

        sb.Append("\")");

        if (debug) 
            Debug.Log("GetMyCustomSaveData: " + sb);
        
        return sb.ToString();
    }



    private void RestoreCurrentValues(string data)
    {
        if (string.IsNullOrEmpty(data))
            return;
        
        var conversations = DialogueManager.MasterDatabase.conversations;
        string[] s = data.Split(';');

        var i = 0;
        int lastConversationId = -1;


        // Start a Lua command that will set the conversation's CurrentValue fields:
        var sb = new StringBuilder();

        while (i < s.Length)
        {
            // Get the conversation index in the dialogue database:
            string articyID = s[i++];
            var value = Tools.StringToInt(s[i++]);
            DialogueEntry currentEntry = entriesByArticyId[articyID];


            // Version 1:
                var conversation = conversations[currentEntry.conversationID];

                // Conversation ID
                if (currentEntry.conversationID != lastConversationId)
                {
                    sb.AppendFormat("Conversation[{0}].Dialog = {{", conversation.id);
                    lastConversationId = currentEntry.conversationID;
                }

                // Dialogue Entires
                sb.AppendFormat("[{0}] = {{ CurrentValue = {1},  IsLoop = '{2}' ,  IsSequence = '{3}', ArticyId = '{4}' }}, ", 
                    currentEntry.id, value, Field.LookupBool(currentEntry.fields, "IsLoop"), Field.LookupBool(currentEntry.fields, "IsSequence"),
                    Field.LookupValue(currentEntry.fields, "Articy Id"));



            // Version 2: Shouldn't this be enough?
               // sb.Append("Conversation[" + currentEntry.conversationID + "].Dialog[" + currentEntry.id + "].CurrentValue = " + value);

        }


        /* 
         * ORIGINAL WHILE LOOP:
        while (i < s.Length)
        {
            // Get the conversation index in the dialogue database:
            var c = Tools.StringToInt(s[i++]);
            if (!(0 <= c && c < conversations.Count)) return; // Sanity check.
            var conversation = conversations[c];
            
            // Start a Lua command that will set the conversation's CurrentValue fields:
            var sb = new StringBuilder();
            sb.AppendFormat("Conversation[{0}].Dialog = {{", conversation.id);

            // Process all recorded dialogue entries' CurrentValues:
            var done = false;
            while (!done && i < s.Length)
            {
                var d = Tools.StringToInt(s[i++]);
                if (d == -1) // Our special signifier that this conversation's list of values is done.
                {
                    done = true;
                }
                else
                {
                    var dialogueEntry = conversation.dialogueEntries[d];
                    var currentValue = Tools.StringToInt(s[i++]);
                    
                    sb.AppendFormat("[{0}] = {{ CurrentValue = {1},  IsLoop = '{2}' ,  IsSequence = '{3}', ArticyId = '{4}' }}, ", 
                        dialogueEntry.id, currentValue, Field.LookupBool(dialogueEntry.fields, "IsLoop"), Field.LookupBool(dialogueEntry.fields, "IsSequence"),
                        Field.LookupValue(dialogueEntry.fields, "Articy Id"));

                    //entriesByArticyId[Field.LookupValue(dialogueEntry.fields, "Articy Id")].id

                }
            }
            */



            // Run the Lua command to set the conversation's CurrentValue fields:
            sb.Append("}; ");
            Lua.Run(sb.ToString(), debug);

    }

}
Thank you for your help!
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving additional data from dialog entry field

Post by Tony Li »

Try changing this line:

Code: Select all

while (i < s.Length)
to this:

Code: Select all

while ((i + 1) < s.Length)
Your code generates this string, which gets passed to RestoreCurrentValues: "ABC;42;DEF;69;"

That semicolon at the end makes RestoreCurrentValues think there's more data coming. Alternatively, you could adjust the save code to not put the semicolon at the end. But changing the while loop in RestoreCurrentValues works just as well.
mrcsbmr
Posts: 31
Joined: Mon Mar 21, 2016 5:26 am

Re: Saving additional data from dialog entry field

Post by mrcsbmr »

Thank you for the hint! It was exactly that and some syntax error, but I managed to make it work. Everything seems fine now. Thanks.
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: Saving additional data from dialog entry field

Post by Tony Li »

Glad to help!
Post Reply