multiple Noode parents from Code

Announcements, support questions, and discussion for Quest Machine.
Post Reply
lordzeon
Posts: 12
Joined: Mon Feb 26, 2024 5:06 pm

multiple Noode parents from Code

Post by lordzeon »

Im making a procedural quest, but i don't find a way to build a node with more than 1 parent, is there a way to do that from code?
imagen_2024-03-25_182720550.png
imagen_2024-03-25_182720550.png (8.04 KiB) Viewed 1524 times
Here Alchemy Node should be a requirement for success node for example
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: multiple Noode parents from Code

Post by Tony Li »

Hi,

Quests generated using the QuestGeneratorEntity component are linear, such as:

AlchemyWorkshop --> ThroneRoom --> Success

or:

ThroneRoom --> AlchemyWorkshop --> Success

If you're making the quest yourself in the Quest Editor window, link both of those nodes to another node that has a Parent condition:

parentsTrue.png
parentsTrue.png (42.83 KiB) Viewed 1517 times

In the screenshot above, I set the Parent condition to require that all parent nodes (AlchemyWorkshop and ThroneRoom) are true. You could also configure it to require that any one node is true, or any specific number of nodes.

If you're doing this in code instead of using the Quest Editor window, you can do the same using the QuestBuilder class, which works similarly to .NET's StringBuilder class but for quests instead of strings.
lordzeon
Posts: 12
Joined: Mon Feb 26, 2024 5:06 pm

Re: multiple Noode parents from Code

Post by lordzeon »

Hi, thanks for answering, im using the QuestBuilder from code. Can you give me one example of hoow to make that Condition? so far i just use messageCondition but i don't see any parent condition or similar, how should i build it?
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: multiple Noode parents from Code

Post by Tony Li »

Hi,

Version 1.2.45 will have that method (QuestBuilder.AddParentCondition). Here's a patch for 1.2.44:

QM_QuestBuilderPatch_2024-03-28.unitypackage

Create a node that's linked from the node that you pass it (e.g., AlchemyWorkshop). Then add a link from the other node (e.g., ThroneRoom). Finally, call the new method to add the parent condition.
lordzeon
Posts: 12
Joined: Mon Feb 26, 2024 5:06 pm

Re: multiple Noode parents from Code

Post by lordzeon »

Hi Tony, today i tried this patch and im a bit lost on how to implement it.

Following the example image you posted.
1) Both True Node has Alchemy node as parent
2) Throne Node and Alchemy has Start as parent
3) i add a Parent Condition to Both True node

Code: Select all

var parentCondition = questBuilder.AddParentCondition(bothTrueNode , ConditionCountMode.All);
4) the builder doesn't include a list of parents, how is that link you mentioned created?
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: multiple Noode parents from Code

Post by Tony Li »

Hi,

I fixed a typo in the patch (above). Please download and import the updated version.

Here's an example script:

Code: Select all

using UnityEngine;

namespace PixelCrushers.QuestMachine.Demo
{

    [RequireComponent(typeof(QuestGiver))]
    public class TestQuestBuilder : MonoBehaviour
    {
        // Quest parameters:
        public string questId = "FindCoins";
        public string questTitle = "Find Coins";
        public Sprite questIcon;
        public string questOfferText = "Orcs stole my gold and hid it in containers around the village! Will you please find 3 coins so I can buy seeds next season and also beat up an orc to teach them a lesson?";

        // Counter parameters:
        public string counterName = "coins";
        public int counterInitial = 0;
        public int counterMin = 0;
        public int counterMax = 3;
        public bool counterRandomizeInitialValue = false;

        // Condition node parameters:
        public string node1Id = "FindCoins";
        public string node1Name = "Find Coins";
        public string node1ActDialogText = "Please find my money.";
        public string node1ActJournalText = "Find the farmer's coins hidden in containers.";
        public string node1HUDText = "{#coins}/3 Coins"; // {#coins} is a special tag representing the current value of the coins counter.

        // Message node parameters:
        public string node2Id = "KillOrc";
        public string node2Name = "Attack Orc";
        public string node2ActDialogText = "Teach an Orc a lesson.";
        public string node2ActJournalText = "Beat up an Orc to teach it a lesson about stealing.";
        public string node2HUDText = "Beat up Orc";

        // Parent node parameters:
        public string node3Id = "BothParents";
        public string node3Name = "Parents True";

        // Return to NPC node parameters:
        public string node4Id = "Talk to NPC";
        public string node4ActDialogText = "Thanks for finding my coins!";
        public string node4ActJournalText = "Return to the villager.";
        public string node4HUDText = "Return to villager";
        public string node4TrueJournalText = "You found the farmer's coins and saved the farm!";

        private void Start()
        {
            // Start our QuestBuilder: (It's like .NET's StringBuilder, but for quests.)
            var questBuilder = new QuestBuilder(name, questId, questTitle);

            // Set up some basic properties:
            questBuilder.quest.icon = questIcon;
            questBuilder.quest.isTrackable = true;
            questBuilder.quest.showInTrackHUD = true;
            // You could also set questBuilder.quest.group if you want to put this quest in a group.

            // Add offer text:
            questBuilder.AddOfferContents(questBuilder.CreateTitleContent(), questBuilder.CreateBodyContent(questOfferText));

            // Add quest title to the Active and Successful states' dialogue, journal, and HUD text:
            //
            // The first line below looks up the Active state, and the Dialogue content for the Active state. 
            // Then it adds title content, which is a HeadingTextQuestContent object with useQuestTitle set true.
            questBuilder.AddContents(questBuilder.quest.GetStateInfo(QuestState.Active).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateTitleContent());
            // The remaining lines do the same for Journal and HUD content in the Active and Successful states.
            questBuilder.AddContents(questBuilder.quest.GetStateInfo(QuestState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateTitleContent());
            questBuilder.AddContents(questBuilder.quest.GetStateInfo(QuestState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateTitleContent());
            questBuilder.AddContents(questBuilder.quest.GetStateInfo(QuestState.Successful).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateTitleContent());
            questBuilder.AddContents(questBuilder.quest.GetStateInfo(QuestState.Successful).GetContentList(QuestContentCategory.Journal), questBuilder.CreateTitleContent());


            //===== COUNTER CONDITION NODE =====
            // Add counter to keep track of number of coins picked up:
            var counter = questBuilder.AddCounter(counterName, counterInitial, counterMin, counterMax, counterRandomizeInitialValue, QuestCounterUpdateMode.Messages);
            // When picked up, coins send the message "Get" "Coin". Tell the counter to increment when this message is sent:
            var counterMessageEvent = new QuestCounterMessageEvent(null, new StringField("Get"), new StringField("Coin"), QuestCounterMessageEvent.Operation.ModifyByLiteralValue, 1);
            counter.messageEventList.Add(counterMessageEvent);

            // Add the counter condition node ([START] --> [Condition]):
            var conditionNode = questBuilder.AddConditionNode(questBuilder.GetStartNode(), node1Id, node1Name, ConditionCountMode.All);
            questBuilder.AddCounterCondition(conditionNode, counterName, CounterValueConditionMode.AtLeast, counterMax);
            // Add text to show when node is active:
            questBuilder.AddContents(conditionNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateBodyContent(node1ActDialogText));
            questBuilder.AddContents(conditionNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(node1ActJournalText));
            questBuilder.AddContents(conditionNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateBodyContent(node1HUDText));

            //===== MESSAGE CONDITION NODE =====
            var messageNode = questBuilder.AddNode(questBuilder.GetStartNode(), node2Id, node2Name, QuestNodeType.Condition);
            questBuilder.AddMessageCondition(messageNode, QuestMessageParticipant.Any, "", QuestMessageParticipant.Any, "", "Kill", "Orc");
            questBuilder.AddContents(messageNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateBodyContent(node2ActDialogText));
            questBuilder.AddContents(messageNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(node2ActJournalText));
            questBuilder.AddContents(messageNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateBodyContent(node2HUDText));

            //===== PARENT CONDITION NODE =====
            var bothParentsNode = questBuilder.AddNode(conditionNode, node3Id, node3Name, QuestNodeType.Condition);
            questBuilder.AddParentCondition(bothParentsNode, ConditionCountMode.All);
            messageNode.childIndexList.Add(questBuilder.quest.nodeList.Count - 1);

            //===== RETURN TO NPC NODE =====
            // Add a "return to quest giver" node ([START] --> [Condition] --> [Return]):
            var questGiver = GetComponent<QuestGiver>();
            var returnNode = questBuilder.AddDiscussQuestNode(bothParentsNode, QuestMessageParticipant.QuestGiver, questGiver.id);

            // Text when active:
            // Add text to show when node is active:
            questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateBodyContent(node4ActDialogText));
            questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(node4ActJournalText));
            questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateBodyContent(node4HUDText));

            // Add text to show when node is true:
            questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.True).GetContentList(QuestContentCategory.Dialogue), questBuilder.CreateBodyContent(node4ActDialogText));
            questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.True).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(node4TrueJournalText));

            // Add an alert action when this node becomes active:
            var actionList = returnNode.GetStateInfo(QuestNodeState.Active).actionList;
            var alertAction = questBuilder.CreateAlertAction(node4HUDText);
            actionList.Add(alertAction);

            // Set the quest indicator to Talk when active:
            var indicatorAction = questBuilder.CreateSetIndicatorAction(questBuilder.quest.id, questGiver.id, QuestIndicatorState.Talk);
            returnNode.GetStateInfo(QuestNodeState.Active).actionList.Add(indicatorAction);

            // Set the Indicator to None when true:
            indicatorAction = questBuilder.CreateSetIndicatorAction(questBuilder.quest.id, questGiver.id, QuestIndicatorState.None);
            returnNode.GetStateInfo(QuestNodeState.True).actionList.Add(indicatorAction);


            //===== SUCCESS NODE =====
            // Add a Success node: [START] --> [Condition] --> [Return] -- > [SUCCESS]
            questBuilder.AddSuccessNode(returnNode);


            //===== FINISH CREATION PROCESS =====
            // Finally, get the quest:
            Quest myQuest = questBuilder.ToQuest();
            // And add it to the QuestGiver:
            GetComponent<QuestGiver>().AddQuest(myQuest);
        }
    }
}
It's a copy of QuestBuilderExample.cs which is included in Quest Machine, but I added an "Attack Orc" node:

questBuilderAllParents.png
questBuilderAllParents.png (29.53 KiB) Viewed 1474 times

(A quick way to test the script is to add it to the Village in the Demo scene.)


The key code is:

Code: Select all

//===== PARENT CONDITION NODE =====
var bothParentsNode = questBuilder.AddNode(conditionNode, node3Id, node3Name, QuestNodeType.Condition);
questBuilder.AddParentCondition(bothParentsNode, ConditionCountMode.All);
messageNode.childIndexList.Add(questBuilder.quest.nodeList.Count - 1);
The first line of this code creates a new node linked from conditionNode.

The second line adds an "All Parents True" condition.

The third line links messageNode to this node, too.
lordzeon
Posts: 12
Joined: Mon Feb 26, 2024 5:06 pm

Re: multiple Noode parents from Code

Post by lordzeon »

Perfect, i make it work with this code, thanks!

Although im having a problem for my particular case. I want the nodes to become active only if the conditions are met, in this case, both parents should be in true state before making the parent condition node active, but with this code when one of the parent nodes is true, the child node is active even with ConditionCountMode.All . Im missing something or cannot avoid this?
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: multiple Noode parents from Code

Post by Tony Li »

The parent condition node must be active to monitor the states of its parents. If you want to delay something until after the parents are true, you could either move that into the parent condition node's True state or link the parent condition node to a passthrough node and put the stuff on the passthrough node.
lordzeon
Posts: 12
Joined: Mon Feb 26, 2024 5:06 pm

Re: multiple Noode parents from Code

Post by lordzeon »

Yes, i opted to do a Requirements node before the child node, not the cleanest solution to see but it does the trick. I understand the listening issue there. thanks for the help
User avatar
Tony Li
Posts: 22107
Joined: Thu Jul 18, 2013 1:27 pm

Re: multiple Noode parents from Code

Post by Tony Li »

Glad to help!
Post Reply