Creating a counter and assigning it to a condition node through script.

Announcements, support questions, and discussion for Quest Machine.
Post Reply
DiegoGary25
Posts: 5
Joined: Wed Mar 05, 2025 11:42 am

Creating a counter and assigning it to a condition node through script.

Post by DiegoGary25 »

Hello everyone,

I'm working on a custom random script generator for my project. I've made good progress but am encountering two issues:

I need to create a counter with the value mode set to "message" that I can modify.
I want to add this counter to the condition node, but despite including it in my code, it does not appear.

My goal is to have a player interact with a quest giver, who then assigns a random quest to the player. Below is my current code implementation:

Code: Select all

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

public class RandomQuestCreator : MonoBehaviour
{
    [Header("Item Pool")]
    public List<Item> possibleItems;

    [Header("Quantity Range")]
    public int minAmount = 1;
    public int maxAmount = 10;

    [Header("Reward Settings")]
    public int coinReward = 50;

    QuestGiver questGiver;

    private void Awake()
    {
        questGiver = GetComponent<QuestGiver>();
        if (questGiver == null)
        {
            Debug.LogWarning("RandomQuestCreator: QuestGiver component not found on this GameObject.");
        }
    }

    public void AssignRandomCollectQuest()
    {
        Debug.Log("AssignRandomCollectQuest: Started.");
        if (questGiver == null)
        {
            Debug.LogWarning("No QuestGiver assigned!");
            return;
        }

        if (questGiver.questList == null)
        {
            Debug.LogWarning("QuestGiver.questList is null!");
            return;
        }

        if (questGiver.questList.Count > 0)
        {
            Debug.Log("QuestGiver already has a quest. Starting dialogue.");
            questGiver.StartDialogueWithPlayer();
            return;
        }

        if (possibleItems == null || possibleItems.Count == 0)
        {
            Debug.LogWarning("No items assigned!");
            return;
        }

        // Pick random item & quantity
        Item randomItem = possibleItems[Random.Range(0, possibleItems.Count)];
        if (randomItem == null)
        {
            Debug.LogWarning("RandomQuestCreator: Selected randomItem is null!");
            return;
        }
        int requiredAmount = Random.Range(minAmount, maxAmount + 1);
        Debug.Log($"RandomQuestCreator: Selected item {randomItem.itemName}, requiredAmount = {requiredAmount}");

        // Create a new Quest using ScriptableObject.CreateInstance
        Quest newQuest = ScriptableObject.CreateInstance<Quest>();
        newQuest.isInstance = true;
        newQuest.originalAsset = null;

        // IMPORTANT: Set the ID BEFORE initializing the quest!
        newQuest.id = new StringField(System.Guid.NewGuid().ToString());
        Debug.Log("RandomQuestCreator: Setting Quest ID to " + newQuest.id.value);

        newQuest.Initialize();

        // Check that the quest was initialized correctly:
        if (newQuest.nodeList == null || newQuest.nodeList.Count == 0)
        {
            Debug.LogError("RandomQuestCreator: Quest.Initialize() did not create any nodes!");
            return;
        }

        // Assign Title
        newQuest.title = new StringField($"Collect {requiredAmount} x {randomItem.itemName}");

        // --- Offer Text ---
        HeadingTextQuestContent offerTitle = ScriptableObject.CreateInstance<HeadingTextQuestContent>();
        BodyTextQuestContent offerText = ScriptableObject.CreateInstance<BodyTextQuestContent>();
        offerTitle.useQuestTitle = true;
        offerText.bodyText = new StringField(
            $"Hey! I need you to collect {requiredAmount} x {randomItem.itemName} for me. " +
            $"I'll pay you {coinReward} coins. What do you say?"
        );
        newQuest.offerContentList.Add(offerTitle);
        newQuest.offerContentList.Add(offerText);

        // --- Journal & HUD Descriptions ---
        var activeStateInfo = newQuest.GetStateInfo(QuestState.Active);
        if (activeStateInfo == null)
        {
            Debug.LogWarning("RandomQuestCreator: Active state info is null.");
        }
        else
        {
            var journalTitle = ScriptableObject.CreateInstance<HeadingTextQuestContent>();
            var journalDesc = ScriptableObject.CreateInstance<BodyTextQuestContent>();
            journalTitle.useQuestTitle = true;
            journalDesc.bodyText = new StringField(
                $"I need to collect {requiredAmount} x {randomItem.itemName} for the NPC. " +
                $"Once I have enough, I'll return for a reward of {coinReward} coins."
            );
            activeStateInfo.GetContentList(QuestContentCategory.Journal).Add(journalTitle);
            activeStateInfo.GetContentList(QuestContentCategory.Journal).Add(journalDesc);

            var hudTitle = ScriptableObject.CreateInstance<HeadingTextQuestContent>();
            var hudDesc = ScriptableObject.CreateInstance<BodyTextQuestContent>();
            hudTitle.useQuestTitle = true;
            hudDesc.bodyText = new StringField($"Collect {requiredAmount} x {randomItem.itemName}");
            activeStateInfo.GetContentList(QuestContentCategory.HUD).Add(hudTitle);
            activeStateInfo.GetContentList(QuestContentCategory.HUD).Add(hudDesc);
        }

        // --- Start Node ---
        QuestNode startNode = newQuest.nodeList[0];
        startNode.id = new StringField("StartNode");
        startNode.internalName = new StringField("Start");
        startNode.childList = new List<QuestNode>();

        // --- Requirement Node ---
        // Initialize conditionSet explicitly if needed.
        QuestNode requirementNode = new QuestNode
        {
            id = new StringField("RequirementNode"),
            internalName = new StringField("Collect Condition"),
            childList = new List<QuestNode>(),
            parentList = new List<QuestNode>(),
            conditionSet = new QuestConditionSet()  // Ensure this is not null.
        };
        if (requirementNode.conditionSet.conditionList == null)
        {
            requirementNode.conditionSet.conditionList = new List<QuestCondition>();
        }
        newQuest.nodeList.Add(requirementNode);
        startNode.childList.Add(requirementNode);
        requirementNode.parentList.Add(startNode);

        // --- Success Node ---
        QuestNode successNode = new QuestNode
        {
            id = new StringField("SuccessNode"),
            internalName = new StringField("Complete Quest"),
            childList = new List<QuestNode>(),
            parentList = new List<QuestNode>()
        };
        newQuest.nodeList.Add(successNode);
        requirementNode.childList.Add(successNode);
        successNode.parentList.Add(requirementNode);
        // --- Add custom actions to the success node:
        var successInfo = successNode.GetStateInfo(QuestNodeState.True);
        if (successInfo == null)
        {
            // If no state info exists for the True state, create one and add it.
            successInfo = new QuestStateInfo();
            // Depending on your Quest Machine version, you might need to add it to the node’s state info list.
            successNode.stateInfoList.Add(successInfo);
        }

        if (successInfo.actionList == null)
            successInfo.actionList = new List<QuestAction>();

        // Create actions using ScriptableObject.CreateInstance:
        RemoveItem removeAction = ScriptableObject.CreateInstance<RemoveItem>();
        removeAction.itemToRemove = randomItem;
        removeAction.amount = requiredAmount;
        successInfo.actionList.Add(removeAction);

        GiveMoney moneyAction = ScriptableObject.CreateInstance<GiveMoney>();
        moneyAction.amount = coinReward;
        successInfo.actionList.Add(moneyAction);

        // Add a "Thank you" dialogue content:
        BodyTextQuestContent thankYouText = ScriptableObject.CreateInstance<BodyTextQuestContent>();
        thankYouText.bodyText = new StringField("Thank you for bringing me the items!");
        successInfo.GetContentList(QuestContentCategory.Dialogue).Add(thankYouText);

        // --- Update childIndexList ---
        foreach (QuestNode node in newQuest.nodeList)
        {
            node.childIndexList.Clear();
            foreach (QuestNode child in node.childList)
            {
                int idx = newQuest.nodeList.IndexOf(child);
                if (idx >= 0)
                    node.childIndexList.Add(idx);
            }
        }

        // --- Set Runtime References ---
        newQuest.SetRuntimeReferences();
        foreach (var node in newQuest.nodeList)
        {
            node.InitializeRuntimeReferences(newQuest);
            node.ConnectRuntimeNodeReferences();
            node.SetRuntimeNodeReferences();
        }

        // --- Add a new counter to the quest ---
        QuestCounter itemCounter = new QuestCounter
        {
            name = new StringField("ItemCounter"),
            currentValue = 0,
            minValue = 0,
            maxValue = requiredAmount
        };
        newQuest.counterList.Add(itemCounter);
        // Assume our new counter is now the last one in the list.
        int counterIndex = newQuest.counterList.Count - 1;

        // --- Attach a CounterQuestCondition ---
        CounterQuestCondition counterCondition = ScriptableObject.CreateInstance<CounterQuestCondition>();
        counterCondition.counterIndex = counterIndex; // Index of the counter added to the quest.
        counterCondition.counterValueMode = CounterValueConditionMode.AtLeast;
        counterCondition.requiredCounterValue = new QuestNumber(requiredAmount);
        // Add the counter condition to the requirement node.
        requirementNode.conditionSet.conditionList.Add(counterCondition);

        newQuest.SetRuntimeReferences();
        foreach (var node in newQuest.nodeList)
        {
            node.InitializeRuntimeReferences(newQuest);
            node.ConnectRuntimeNodeReferences();
            node.SetRuntimeNodeReferences();
        }
        // --- Final: Add Quest & Start Dialogue ---
        questGiver.AddQuest(newQuest);
        Debug.Log($"Assigned quest: '{newQuest.title}' => Collect {requiredAmount} x {randomItem.itemName}, reward {coinReward} coins.");
        questGiver.StartDialogueWithPlayer();

        // Debug print each node's internal name
        foreach (QuestNode node in newQuest.nodeList)
        {
            Debug.Log("Node: " + node.internalName.value);
        }
    }
}

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

Re: Creating a counter and assigning it to a condition node through script.

Post by Tony Li »

Hi,

Quests have a lot of data fields that need to be assigned correctly. I recommend using the QuestBuilder class to create your quests in script. It has methods such as AddCounter() and AddCounterMessageEvent(). See page 65 of Quest Machine's manual for more info and an example script that uses QuestBuilder.

Alternatively, if you're creating random quests, you could use Quest Machine's procedural quest generator. It uses a STRIPS-style goal-oriented action planner (GOAP) to create quests based on the current world state. Or, even if you don't use it, you can examine the PlanToQuestBuilder.cs script to see how it puts together quests using QuestBuilder.
DiegoGary25
Posts: 5
Joined: Wed Mar 05, 2025 11:42 am

Re: Creating a counter and assigning it to a condition node through script.

Post by DiegoGary25 »

That makes so much sense, thanks !
User avatar
Tony Li
Posts: 22871
Joined: Thu Jul 18, 2013 1:27 pm

Re: Creating a counter and assigning it to a condition node through script.

Post by Tony Li »

Glad to help!
Post Reply