Dialogue Sim: New Unexplored Subtree

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Dialogue Sim: New Unexplored Subtree

Post by VoodooDetective »

I was just wondering if there's any built in facility for un-graying out a dialogue option that was previously completely explored, but that now has new dialogue options in sub-trees based on changes in game state?

I guess it would require you to walk the tree before starting the conversation? Or is there maybe a better way?

If it's not written, do you have any advice on what NOT to do, and what might make sense?
User avatar
Tony Li
Posts: 21984
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by Tony Li »

Hi,

I think you'll need to walk the tree. There isn't any built-in utility method for that.

You could handle it semi-manually by keeping a list of IDs. When some event happens, set the SimStatus of all of those IDs back to Untouched.

However, it's probably better in the long run to manually walk the tree and look for available options. If there are any available options, set the parents' SimStatuses back to Untouched. As you're walking the tree, remember that you links can loop back. So maintain a list of nodes that you've already visited so you don't end up in an infinite loop.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by VoodooDetective »

Alrighty, thanks for the information. I'll have to decide what makes the most sense for us. We may only have a few of these, and evaluating all the lua repeatedly is probably expensive. It might be easiest just to walk to tree for children that haven't been visited without evaluating the lua. The parent dialogue choice would be de-greyed until all children were visited. That'd probably be fine.

Thanks again for the help!
User avatar
Tony Li
Posts: 21984
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by Tony Li »

You could add a custom Boolean dialogue entry field to indicate that a subtree has nodes that could potentially de-grey the subtree. Then you could quickly loop through the dialogue entries to identify any nodes whose Boolean field is true. If the conversation has any, you can evaluate the Lua of those subtrees.

Code: Select all

var entries = new List<DialogueEntry>();
foreach (DialogueEntry entry in currentConversation.dialogueEntries)
{
    if (Field.LookupBool(entry.fields, "Your Field Name") == true)
    {
        entries.Add(entry);
    }
}
// Now you only need to evaluate the subtrees (if any) starting from the elements in entries.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by VoodooDetective »

I decided to try to hack something together this weekend. This code traverses the conversations breadth first, and at each decision node descends to the bottom of the tree to get a list of all the responses children.

My definition of "decision node" is very much imperfect because I'm not evaluating the lua conditions and even if I were, I don't have to chops to do the static analysis to decide if two nodes are actually mutually exclusive. So there are a few instances where I'm manually having to correct things.

With this list of responses and all response children, I can, see if all the children of a response have been visited to decide if the node is greyed out.

Code: Select all

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

namespace Dialogue
{
    public class ResponseTreeCache : MonoBehaviour
    {
        // Constants
        private const int DefaultListSize = 512;

        // Singleton 
        private static ResponseTreeCache instance;

        // Properties
        private DialogueDatabase database;
        // Temporary Data Structures
        private List<DialogueEntry> unvisited;
        private HashSet<DialogueEntry> visited;
        private Dictionary<int, List<DecisionNode>> conversationIdToDecisions;
        // private Dictionary<int, HashSet<int>> globalResponseIdCache;
        // Cache
        private Dictionary<int, ConversationCache> conversationIdToCache;

        void Awake()
        {
            instance = this;
            // Initialize Temporary Data Structures 
            visited = new HashSet<DialogueEntry>();
            unvisited = new List<DialogueEntry>(DefaultListSize);
            conversationIdToDecisions = new Dictionary<int, List<DecisionNode>>();
            // globalResponseIdCache = new Dictionary<int, HashSet<int>>();

            // Initialize Cache
            conversationIdToCache = new Dictionary<int, ConversationCache>();

            // Gather References
            database = DialogueManager.databaseManager.masterDatabase;

            // Initialize Cache
            Action handler = null;
            DialogueManager.instance.initializationComplete += handler = () =>
            {
                DialogueManager.instance.initializationComplete -= handler;
                // Build Pre-Cache
                BuildPreCache();
                // CacheConversation(database.GetConversation(120), unvisited, visited); // Helpful for testing

                // Build Cache 
                BuildCache();

                // Clear Temporary Data
                unvisited = null;
                visited = null;
                conversationIdToDecisions = null;
                // globalResponseIdCache = null;

                // Helpful for testing
                // string tmp = "";
                // foreach (KeyValuePair<int, ConversationCache> entry in conversationIdToCache)
                // {
                //     ConversationCache cache = entry.Value;
                //     foreach (KeyValuePair<int, List<int>> cacheEntry in cache.responseIdToChildResponses)
                //     {
                //         tmp += $"Response ID: {cacheEntry.Key} - ({  string.Join(",", cacheEntry.Value)})\n";
                //     }
                // }
                // Debug.Log(tmp);
            };
        }

        public static bool IsResponseExhausted(int conversationId, int responseId)
        {
            ConversationCache cache = instance.conversationIdToCache[conversationId];
            return cache.IsResponseExhausted(responseId);
        }

        private void BuildCache()
        {
            // Iterate over all conversations
            foreach (KeyValuePair<int, List<DecisionNode>> entry in conversationIdToDecisions)
            {
                // Create Conversation Cache
                ConversationCache cache = new ConversationCache(entry.Key); // Key is conversation id
                conversationIdToCache[entry.Key] = cache;

                // Conversation Global Response ID Cache
                // HashSet<int> responseIdCache = globalResponseIdCache[entry.Key];

                // Store All Responses
                foreach (DecisionNode decisionNode in entry.Value)
                {
                    foreach (ResponseNode responseNode in decisionNode.responses)
                    {
                        cache.AddResponseChildren(responseNode.entry.id, responseNode.childEntryIds);
                        // foreach (int responseChildId in responseNode.childEntryIds)
                        // {
                        //     // Response Child Node is a Response
                        //     // Note: I no longer only list the response children.  There are cases where
                        //     //       we have multiple paths beneath a response without another decision.
                        //     //       We'd end up marking those as grey inadvertantly.
                        //     // if (responseIdCache.Contains(responseChildId))
                        //     // {
                        //     cache.AddResponseChild(responseNode.entry.id, responseChildId);
                        //     // }
                        // }
                    }
                }
            }
        }

        private void BuildPreCache()
        {
            // Build Dialogue Choice Trees
            foreach (Conversation conversation in database.conversations)
            {
                visited.Clear();
                unvisited.Clear();
                CacheConversation(conversation, unvisited, visited);
            }
        }

        private void CacheConversation(Conversation conversation, List<DialogueEntry> localUnvisited, HashSet<DialogueEntry> localVisited)
        {
            // Grab Conversation Response ID Cache
            // HashSet<int> globalResponseIdSet;
            // globalResponseIdCache.TryGetValue(conversation.id, out globalResponseIdSet);
            // if (globalResponseIdSet == null)
            // {
            //     globalResponseIdSet = new HashSet<int>();
            //     globalResponseIdCache[conversation.id] = globalResponseIdSet;
            // }

            // Initialize for Traversal
            localUnvisited.Add(conversation.GetFirstDialogueEntry());
            while (localUnvisited.Count > 0)
            {
                // Get Current Node
                int unvisitedIndex = localUnvisited.Count - 1;
                DialogueEntry currentNode = unvisited[unvisitedIndex];
                localUnvisited.RemoveAt(unvisitedIndex);

                // Set Current Node Visited
                localVisited.Add(currentNode);

                // No Links, Continue
                if (currentNode.outgoingLinks.Count == 0) continue;

                // Gather the Frontier
                int pcNodes = 0;
                int npcNodes = 0;
                int frontierStartIndex = localUnvisited.Count;
                GatherFrontier(conversation, currentNode, localUnvisited, localVisited);
                for (int i = frontierStartIndex; i < localUnvisited.Count; i++)
                {
                    DialogueEntry frontierNode = localUnvisited[i];
                    if (IsPCNode(frontierNode)) pcNodes++;
                    else npcNodes++;
                }

                // This is a decision
                bool isDecisionNode = pcNodes > 1 && npcNodes == 0;
                if (isDecisionNode) CacheDecisionNode(currentNode, conversation, frontierStartIndex, localUnvisited.Count);//, globalResponseIdSet);
            }
        }

        private void GatherFrontier(Conversation conversation, DialogueEntry node, List<DialogueEntry> localUnvisited, HashSet<DialogueEntry> localVisited)
        {
            for (int i = 0; i < node.outgoingLinks.Count; i++)
            {
                Link link = node.outgoingLinks[i];
                // Skip links to other conversations
                if (link.destinationConversationID != conversation.id) continue;
                DialogueEntry childNode = conversation.GetDialogueEntry(link.destinationDialogueID);
                if (!localVisited.Contains(childNode))
                {
                    if (childNode.isGroup)
                    {
                        // Recurse if group or NPC node
                        localVisited.Add(childNode);
                        GatherFrontier(conversation, childNode, localUnvisited, localVisited);
                    }
                    else localUnvisited.Add(childNode);
                }
            }
        }

        private void CacheDecisionNode(DialogueEntry decisionNode, Conversation conversation, int responsesStart, int responsesEnd)//, HashSet<int> globalResponseIdSet)
        {
            // Add Node to Cache
            List<DecisionNode> conversationDecisions;
            conversationIdToDecisions.TryGetValue(conversation.id, out conversationDecisions);
            if (conversationDecisions == null)
            {
                conversationDecisions = new List<DecisionNode>(DefaultListSize);
                conversationIdToDecisions[conversation.id] = conversationDecisions;
            }

            // Create Decision Node
            HashSet<DialogueEntry> localVisited = new HashSet<DialogueEntry>(); // All visited and unvisited for master BFS
            foreach (DialogueEntry entry in visited) localVisited.Add(entry);
            foreach (DialogueEntry entry in unvisited) localVisited.Add(entry);
            DecisionNode newNode = new DecisionNode(decisionNode);
            for (int i = responsesStart; i < responsesEnd; i++)
            {
                // Create Response
                DialogueEntry frontierNode = unvisited[i];
                ResponseNode response = new ResponseNode(frontierNode, conversation);
                PopulateResponseChildren(response, frontierNode, localVisited);
                newNode.responses.Add(response);
                // Cache Response
                // globalResponseIdSet.Add(frontierNode.id);
            }

            // Add Node
            conversationDecisions.Add(newNode);
        }

        /// <summary>
        /// Recursively find all children of this response.
        /// </summary>
        private void PopulateResponseChildren(ResponseNode response, DialogueEntry currentNode, HashSet<DialogueEntry> localVisited)
        {
            for (int i = 0; i < currentNode.outgoingLinks.Count; i++)
            {
                // Grab Child
                Link link = currentNode.outgoingLinks[i];
                // Skip links to other conversations
                if (link.destinationConversationID != currentNode.conversationID) continue;
                DialogueEntry childNode = response.conversation.GetDialogueEntry(link.destinationDialogueID);
                if (!localVisited.Contains(childNode))
                {
                    // Add Child (if it's not a group and has a sequence or dialogue)
                    if (!childNode.isGroup && (!string.IsNullOrEmpty(childNode.DialogueText) || !string.IsNullOrEmpty(childNode.Sequence)))
                    {
                        response.childEntryIds.Add(childNode.id);
                    }

                    // Set Visited
                    localVisited.Add(childNode);

                    // Recurse
                    PopulateResponseChildren(response, childNode, localVisited);
                }
            }
        }

        private bool IsPCNode(DialogueEntry entry) => database.GetCharacterType(entry.ActorID) == CharacterType.PC;

        private class DecisionNode
        {
            public DialogueEntry entry;
            public List<ResponseNode> responses;
            public DecisionNode(DialogueEntry entry)
            {
                this.entry = entry;
                this.responses = new List<ResponseNode>(DefaultListSize);
            }
        }

        private class ResponseNode
        {
            public DialogueEntry entry;
            public Conversation conversation;
            public HashSet<int> childEntryIds;
            public ResponseNode(DialogueEntry entry, Conversation conversation)
            {
                this.entry = entry;
                this.conversation = conversation;
                this.childEntryIds = new HashSet<int>();
            }
        }

        private class ConversationCache
        {
            public int conversationId;
            public Dictionary<int, List<int>> responseIdToChildren;

            public ConversationCache(int conversationId)
            {
                this.conversationId = conversationId;
                this.responseIdToChildren = new Dictionary<int, List<int>>();
            }

            public void AddResponseChildren(int responseId, ICollection<int> responseChildren)
            {
                List<int> responseChildrenList = null;
                responseIdToChildren.TryGetValue(responseId, out responseChildrenList);
                if (responseChildrenList == null)
                {
                    responseChildrenList = new List<int>(responseChildren.Count);
                    responseIdToChildren[responseId] = responseChildrenList;
                }
                responseChildrenList.AddRange(responseChildren);
            }

            // public void AddResponseChild(int responseId, int responseChildId)
            // {
            //     // Ensure Initialized
            //     List<int> responseChildren = null;
            //     responseIdToChildren.TryGetValue(responseId, out responseChildren);
            //     if (responseChildren == null)
            //     {
            //         responseChildren = new List<int>(DefaultListSize);
            //         responseIdToChildren[responseId] = responseChildren;
            //     }
            //     responseChildren.Add(responseChildId);
            // }

            public bool IsResponseExhausted(int responseEntryId)
            {
                // Check the response itself first
                if (!DialogueChoiceMemory.ContainsEntry(conversationId, responseEntryId)) return false;
                // Check children if the response itself is exhausted
                bool childrenExhausted = true;
                List<int> responseChildren;
                responseIdToChildren.TryGetValue(responseEntryId, out responseChildren);
                if (responseChildren != null)
                {
                    foreach (int responseChildId in responseChildren)
                    {
                        if (!DialogueChoiceMemory.ContainsEntry(conversationId, responseChildId))
                        {
                            childrenExhausted = false;
                            break;
                        }
                    }
                }
                return childrenExhausted;
            }
        }
    }
}

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

Re: Dialogue Sim: New Unexplored Subtree

Post by Tony Li »

Neat!

One note: The Dialogue Manager's DialogueSystemController sets up the masterDatabase property in Awake(). DialogueSystemController has a script execution order of -7, which means its Awake will run before other scripts (such as yours) that don't specify an execution order, so it should be all good. Just FYI in case you decide to manually set your script's execution order in the future.
VoodooDetective
Posts: 222
Joined: Wed Jan 22, 2020 10:48 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by VoodooDetective »

I rewrote this so that it evaluates the condition statements as it's deciding which responses are exhausted. I just figured I'd share the updated version in case that's useful to anyone.

Code: Select all

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

namespace Dialogue
{
    public class ResponseTreeCache : MonoBehaviour
    {
        // Singleton 
        private static ResponseTreeCache instance;

        // Properties
        private DialogueDatabase database;
        // Temporary Data Structures
        private Dictionary<int, List<Node>> conversationIdToDecisions;
        private Dictionary<int, ConversationTree> conversationIdToConversationTree;

        void Awake()
        {
            instance = this;
            // Initialize Temporary Data Structures 
            conversationIdToDecisions = new Dictionary<int, List<Node>>();
            conversationIdToConversationTree = new Dictionary<int, ConversationTree>();

            // Gather References
            database = DialogueManager.databaseManager.masterDatabase;

            // Initialize Cache
            Action handler = null;
            DialogueManager.instance.initializationComplete += handler = () =>
            {
                DialogueManager.instance.initializationComplete -= handler;
                // Build List of Decisions and Their Response Children
                BuildDecisionsAndResponseTree();

                // Clear Temporary Data
                conversationIdToDecisions = null;
            };
        }

        public static bool IsResponseExhausted(int conversationId, int responseId)
        {
            ConversationTree conversationTree = instance.conversationIdToConversationTree[conversationId];
            bool exhausted = conversationTree.IsResponseExhausted(responseId);
            return exhausted;
        }

        private void BuildDecisionsAndResponseTree()
        {
            // Build Dialogue Choice Trees

            foreach (Conversation conversation in database.conversations)
            {
                CacheConversation(conversation);
                // Now that we have a list of all decisions and their responses,
                // we can go back and find all the children of all the responses 
                // that are decisions
                ConversationTree conversationTree = new ConversationTree();
                PopulateResponseNodes(conversation, conversationTree);
                conversationTree.CleanTemporaryStructures();

                // Cache Tree
                conversationIdToConversationTree[conversation.id] = conversationTree;
            }
        }

        private void CacheConversation(Conversation conversation)
        {
            // Initialize for Traversal
            // Dictionary<DialogueEntry, HashSet<DialogueEntry>> visitedSetCache = new Dictionary<DialogueEntry, HashSet<DialogueEntry>>();
            Queue<HashSet<DialogueEntry>> visitedSetQueue = new Queue<HashSet<DialogueEntry>>();
            Queue<DialogueEntry> unvisited = new Queue<DialogueEntry>();
            HashSet<DialogueEntry> visited = new HashSet<DialogueEntry>();
            unvisited.Enqueue(conversation.GetFirstDialogueEntry());
            visitedSetQueue.Enqueue(new HashSet<DialogueEntry>());
            while (unvisited.Count > 0)
            {
                // Get Current Node
                DialogueEntry currentNode = unvisited.Dequeue();
                HashSet<DialogueEntry> previousVisitedSet = visitedSetQueue.Dequeue();

                // Set Current Node Visited
                visited.Add(currentNode);

                // No Links, Continue
                if (currentNode.outgoingLinks.Count == 0) continue;

                // Add Previous Frontier Visited Set
                foreach (DialogueEntry visitedEntry in visited) previousVisitedSet.Add(visitedEntry);

                // Create New Frontier List
                List<DialogueEntry> frontier = new List<DialogueEntry>(currentNode.outgoingLinks.Count);

                // Gather the Frontier
                GatherFrontier(conversation, currentNode, unvisited, previousVisitedSet, frontier, visitedSetQueue);//, visitedSetCache);

                // Is Decision
                if (frontier.Count > 1)
                {
                    CacheDecisionNode(conversation, currentNode, frontier, unvisited, visited);
                }
            }
        }

        private void GatherFrontier(Conversation conversation, DialogueEntry node, Queue<DialogueEntry> unvisited, HashSet<DialogueEntry> visited, List<DialogueEntry> frontier, Queue<HashSet<DialogueEntry>> visitedSetQueue)
        {
            for (int i = 0; i < node.outgoingLinks.Count; i++)
            {
                Link link = node.outgoingLinks[i];
                // Skip links to other conversations
                if (link.destinationConversationID != conversation.id) continue;
                DialogueEntry childNode = conversation.GetDialogueEntry(link.destinationDialogueID);

                // Skip Groups
                if (childNode.isGroup)
                {
                    if (!visited.Contains(childNode))
                    {
                        // Recurse if group
                        visited.Add(childNode);
                        GatherFrontier(conversation, childNode, unvisited, visited, frontier, visitedSetQueue);
                    }
                    continue;
                }

                // Not Group, Add to Frontier (we don't check visited because we want to see all children regardless to get all decision nodes)
                frontier.Add(childNode);

                // Add to Unvisited
                if (!visited.Contains(childNode))
                {
                    // visitedSetCache[childNode] = new HashSet<DialogueEntry>(visited);
                    visitedSetQueue.Enqueue(new HashSet<DialogueEntry>(visited));
                    unvisited.Enqueue(childNode);
                }
            }
        }

        private void CacheDecisionNode(Conversation conversation, DialogueEntry decisionEntry, List<DialogueEntry> responses, Queue<DialogueEntry> unvisited, HashSet<DialogueEntry> visited)//, HashSet<int> globalResponseIdSet)
        {
            // Add Node to Cache
            List<Node> conversationDecisions;
            conversationIdToDecisions.TryGetValue(conversation.id, out conversationDecisions);
            if (conversationDecisions == null)
            {
                conversationDecisions = new List<Node>();
                conversationIdToDecisions[conversation.id] = conversationDecisions;
            }

            // Create Response Local Visited Set
            HashSet<DialogueEntry> localVisitedForResponse = new HashSet<DialogueEntry>(); // All visited and unvisited for master BFS
            foreach (DialogueEntry entry in visited) localVisitedForResponse.Add(entry);
            foreach (DialogueEntry entry in unvisited) localVisitedForResponse.Add(entry);

            // Create Decision Local Visited Set
            HashSet<DialogueEntry> localVisitedForDecision = new HashSet<DialogueEntry>(localVisitedForResponse);
            localVisitedForDecision.Remove(decisionEntry);

            // Create Decision Node
            Node decisionNode = new Node(conversation, decisionEntry, localVisitedForDecision);
            foreach (DialogueEntry responseEntry in responses)
            {
                // Create Response
                Node responseNode = new Node(conversation, responseEntry, localVisitedForResponse);
                decisionNode.immediateChildren.Add(responseNode);
            }

            // Add Node
            conversationDecisions.Add(decisionNode);
        }

        private void PopulateResponseNodes(Conversation conversation, ConversationTree conversationTree)
        {
            // Get Decisions
            List<Node> conversationDecisions;
            conversationIdToDecisions.TryGetValue(conversation.id, out conversationDecisions);
            if (conversationDecisions == null) return; // Some conversations don't have decisions

            // Conversation Decision Map
            foreach (Node decision in conversationDecisions)
            {
                conversationTree.AddDecision(decision);
            }

            // Iterate Over Responses
            foreach (Node decision in conversationDecisions)
            {
                for (int i = 0; i < decision.immediateChildren.Count; i++)
                {
                    // Deduplicate Response Nodes That Are Also Decisions
                    Node hydratedResponse;
                    Node response = decision.immediateChildren[i];
                    conversationTree.decisionNodeMap.TryGetValue(response.entry.id, out hydratedResponse);
                    if (hydratedResponse != null)
                    {
                        // Response's Children Already Hydrated
                        decision.immediateChildren[i] = hydratedResponse;
                        response = hydratedResponse;
                    }
                    else
                    {
                        // Populate Response's Children
                        HydrateResponseChildren(response, response.entry, response.localVisited, conversationTree);
                    }

                    conversationTree.AddResponse(response);
                }
            }
        }

        private void HydrateResponseChildren(Node response, DialogueEntry currentNode, HashSet<DialogueEntry> localVisited, ConversationTree conversationTree)
        {
            for (int i = 0; i < currentNode.outgoingLinks.Count; i++)
            {
                // Grab Child
                Link link = currentNode.outgoingLinks[i];
                if (link.destinationConversationID != currentNode.conversationID) continue; // Skip links to other conversations
                DialogueEntry childNode = response.conversation.GetDialogueEntry(link.destinationDialogueID);

                // Ensure No Looping and Stop at Decision Nodes
                if (!localVisited.Contains(childNode))
                {
                    // Is Decision
                    Node childDecision;
                    conversationTree.decisionNodeMap.TryGetValue(childNode.id, out childDecision);
                    if (childDecision != null)
                    {
                        // Add All Decision Nodes
                        response.immediateChildren.Add(childDecision);
                        continue;
                    }
                    // Is Response
                    Node childResponse;
                    conversationTree.responseNodeMap.TryGetValue(childNode.id, out childResponse);
                    if (childResponse != null)
                    {
                        response.immediateChildren.Add(childResponse);
                        continue;
                    }
                    // Is Normal Node, Recurse
                    HydrateResponseChildren(response, childNode, localVisited, conversationTree);
                }
            }
        }

        private bool IsPCNode(DialogueEntry entry) => database.GetCharacterType(entry.ActorID) == CharacterType.PC;

        private class ConversationTree
        {
            public Dictionary<int, Node> responseNodeMap;
            public Dictionary<int, Node> decisionNodeMap;

            public ConversationTree()
            {
                this.decisionNodeMap = new Dictionary<int, Node>();
                this.responseNodeMap = new Dictionary<int, Node>();
            }

            public void AddDecision(Node decision) => this.decisionNodeMap[decision.entry.id] = decision;
            public void AddResponse(Node response) => this.responseNodeMap[response.entry.id] = response;
            public void CleanTemporaryStructures()
            {
                this.decisionNodeMap = null;
                foreach (KeyValuePair<int, Node> entry in this.responseNodeMap)
                {
                    entry.Value.localVisited = null;
                }
            }

            public bool IsResponseExhausted(int responseId)
            {
                // Grab Response Node
                Node responseNode = responseNodeMap[responseId];
                // Recursively Check
                return IsResponseExhausted(responseNode);
            }

            private bool IsResponseExhausted(Node responseNode)
            {
                // Check the response itself first
                if (!IsConditionMet(responseNode.entry)) return true;
                if (!DialogueChoiceMemory.ContainsEntry(responseNode.conversation.id, responseNode.entry.id)) return false;

                // Check Decisions
                foreach (Node child in responseNode.immediateChildren)
                {
                    if (!IsResponseExhausted(child))
                    {
                        return false;
                    }
                }
                return true;
            }

            private bool IsConditionMet(DialogueEntry entry)
            {
                bool isConditionMet = true;
                if (!string.IsNullOrEmpty(entry.conditionsString))
                {
                    isConditionMet = Lua.IsTrue(entry.conditionsString);
                }
                return isConditionMet;
            }
        }

        private class Node
        {
            public Conversation conversation;
            public DialogueEntry entry;
            public List<Node> immediateChildren;
            public HashSet<DialogueEntry> localVisited; // parents

            public Node(Conversation conversation, DialogueEntry entry, HashSet<DialogueEntry> localVisited)
            {
                this.conversation = conversation;
                this.entry = entry;
                this.immediateChildren = new List<Node>();
                this.localVisited = localVisited;
            }
        }
    }
}
User avatar
Tony Li
Posts: 21984
Joined: Thu Jul 18, 2013 1:27 pm

Re: Dialogue Sim: New Unexplored Subtree

Post by Tony Li »

Thanks!
Post Reply