Script Driven Quests

Announcements, support questions, and discussion for Quest Machine.
Post Reply
Gandoff
Posts: 20
Joined: Mon Jan 01, 2018 7:13 pm

Script Driven Quests

Post by Gandoff »

Will you have a tutorial on making script driven quests? In other words, I would like to create quests via scripts (C#) and I was hoping for a tutorial. Thank you.
User avatar
Tony Li
Posts: 22143
Joined: Thu Jul 18, 2013 1:27 pm

Re: Script Driven Quests

Post by Tony Li »

That's a good idea. The manual briefly discusses the QuestBuilder class, but certainly not in enough detail to qualify as a step-by-step tutorial.

Are you interested in creating quests with your own script logic or by invoking the procedural quest generator?
Gandoff
Posts: 20
Joined: Mon Jan 01, 2018 7:13 pm

Re: Script Driven Quests

Post by Gandoff »

Of course, I want it all! At this point in my design, I am considering both types of quests as they would both be very useful!
User avatar
Tony Li
Posts: 22143
Joined: Thu Jul 18, 2013 1:27 pm

Re: Script Driven Quests

Post by Tony Li »

Using the procedural quest generator is easiest. Here's an example that assumes the script's GameObject has a QuestGeneratorEntity component:

Code: Select all

GetComponent<QuestGeneratorEntity>().GenerateQuest();
Otherwise, if you don't have a QuestGeneratorEntity, you can create a QuestGenerator manually:

Code: Select all

var questGenerator = new QuestGenerator();
questGenerator.GenerateQuest(this, questGroup, domainType, worldModel, requireReturnToComplete, rewardsUIContents, rewardSystems, existingQuestList, OnGeneratedQuest);

void OnGeneratedQuest(Quest quest)
{
    Debug.Log("We just generated a new quest titled " + quest.title);
    GetComponent<QuestGiver>().AddQuest(quest);
}
As you can see, QuestGenerator.GenerateQuest() takes a lot of parameters. It runs in the background. When it's done, it calls a function that you specify (OnGeneratedQuest in the example above), passing it the newly-generated quest.


Building a quest with your own script logic isn't too hard, but it's a lengthy process. It looks something like this:

Code: Select all

// Start our QuestBuilder: (It's like .NET's StringBuilder, but for quests.)
var questBuilder = new QuestBuilder(name, id, title);

// Set up some basic properties:
questBuilder.quest.icon = questIcon;
questBuilder.quest.isTrackable = true;
questBuilder.quest.showInTrackHUD = true;

// Add offer text:
questBuilder.AddOfferContents(questBuilder.CreateTitleContent(), questBuilder.CreateBodyContent("Do this quest!));

// Add titles to the Active and Successful states' dialogue text:
var activeState = questBuilder.quest.stateInfoList[(int)QuestState.Active];
var activeContents = activeState.categorizedContentList[(int)QuestContentCategory.Dialogue]
questBuilder.AddContents(activeContents, questBuilder.CreateTitleContent());
    // (You'll probably also want to add this to QuestContentCategory.Journal and .HUD.)

// Add a Condition node: [START] --> [Condition]
var conditionNode = questBuilder.AddConditionNode(questBuilder.GetStartNode(), id, internalName, ConditionCountMode.All);
// ...and add a message condition "Did Something":
questBuilder.AddMessageCondition(conditionNode, QuestMessageParticipant.Any, null, QuestMessageParticipant.Any, null, new StringField("Did"), new StringField("Something")));
    // (Should also add text content to dialogue, journal, and HUD categories.)

// Add a Success node: [START] --> [Condition] --> [SUCCESS]
questBuilder.AddSuccessNode(conditionNode);

// Finally(!) get the quest:
Quest myQuest = questBuilder.ToQuest();
Gandoff
Posts: 20
Joined: Mon Jan 01, 2018 7:13 pm

Re: Script Driven Quests

Post by Gandoff »

I am trying to generate the Find Coins quest via C#. Given what you posted earlier, I have created this C# class.

I am missing several pieces like adding text to dialogue, journal, and HUD categories. I am not clear on what the questBuilder.AddMessageCondition is doing (how does this relate to the UI?).

Also, notice that the counter definition does not include the Message definition.

If you would be so kind, would you please look at this code and find in the gaps?
Thank you,
Gandoff

Code: Select all

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

public class CreateFindCoinsQuest : 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?";

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

    // State parameters
    public string state1Id = "Condition1";
    public string state1Name = "Find Coins";
    public string state1ActDialogText = "Please find my money.";
    public string state1JournalText = "Find the farmer's coins hidden in containters.";
    public string state1HUDText = "{#coins}/3 Coins";
    public string state1TrueJournalText = "You found the farmer's coins and saved the farm!";

	// Use this for initialization
	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;

        // Add offer text:
        questBuilder.AddOfferContents(questBuilder.CreateTitleContent(), questBuilder.CreateBodyContent(questOfferText));
        
        // Add titles to the Active and Successful states' dialogue text:
        var activeState = questBuilder.quest.stateInfoList[(int)QuestState.Active];
        var activeContents = activeState.categorizedContentList[(int)QuestContentCategory.Dialogue];
        questBuilder.AddContents(activeContents, questBuilder.CreateTitleContent());
        // (You'll probably also want to add this to QuestContentCategory.Journal and .HUD.)

        // Add counter
        questBuilder.AddCounter(counterName, counterInitial, counterMin, counterMax, counterRndInitVal, PixelCrushers.QuestMachine.QuestCounterUpdateMode
.Messages);

        // Add a Condition node: [START] --> [Condition]
        var conditionNode = questBuilder.AddConditionNode(questBuilder.GetStartNode(), state1Id, state1Name, ConditionCountMode.All);
        // ...and add a message condition "Did Something":
        questBuilder.AddMessageCondition(conditionNode, QuestMessageParticipant.Any, null, QuestMessageParticipant.Any, null, new StringField("Did"), new StringField("Something")));
        // (Should also add text content to dialogue, journal, and HUD categories.)

        // Add a Success node: [START] --> [Condition] --> [SUCCESS]
        questBuilder.AddSuccessNode(conditionNode);

        // Finally(!) get the quest:
        Quest myQuest = questBuilder.ToQuest();
    }
}
User avatar
Tony Li
Posts: 22143
Joined: Thu Jul 18, 2013 1:27 pm

Re: Script Driven Quests

Post by Tony Li »

Here you go! If anything doesn't make sense, just ask. I added a "return to NPC" node. So after finding 3 coins, the next step is to talk to the villager again, instead of the quest immediately becoming successful.

Code: Select all

using UnityEngine;
using PixelCrushers;
using PixelCrushers.QuestMachine;

[RequireComponent(typeof(QuestGiver))]
public class CreateFindCoinsQuest : 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?";

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

    // State 1 parameters
    public string state1Id = "Condition1";
    public string state1Name = "Find Coins";
    public string state1ActDialogText = "Please find my money.";
    public string state1ActJournalText = "Find the farmer's coins hidden in containers.";
    public string state1HUDText = "{#coins}/3 Coins"; // {#coins} is a special tag representing the value of the coins counter.

    // State 1 parameters
    public string state2ActDialogText = "Thanks for finding my coins!";
    public string state2ActJournalText = "Return to the villager.";
    public string state2HUDText = "Return to villager";
    public string state2TrueJournalText = "You found the farmer's coins and saved the farm!";

    // Use this for initialization
    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 for 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, counterRndInitVal, PixelCrushers.QuestMachine.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 a counter condition node ([START] --> [Condition]):
        var conditionNode = questBuilder.AddConditionNode(questBuilder.GetStartNode(), state1Id, state1Name, 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(state1ActDialogText));
        questBuilder.AddContents(conditionNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(state1ActJournalText));
        questBuilder.AddContents(conditionNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateBodyContent(state1HUDText));


        //===== RETURN TO NPC NODE =====
        // Add a "return to quest giver" node ([START] --> [Condition] --> [Return]):
        var questGiver = GetComponent<QuestGiver>();
        var returnNode = questBuilder.AddDiscussQuestNode(conditionNode, 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(state2ActDialogText));
        questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.Journal), questBuilder.CreateBodyContent(state2ActJournalText));
        questBuilder.AddContents(returnNode.GetStateInfo(QuestNodeState.Active).GetContentList(QuestContentCategory.HUD), questBuilder.CreateBodyContent(state2HUDText));

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

        // Add an alert action to when this node becomes active:
        var actionList = returnNode.GetStateInfo(QuestNodeState.Active).actionList;
        var alertAction = questBuilder.CreateAlertAction(state2HUDText);
        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);
    }
}
Gandoff
Posts: 20
Joined: Mon Jan 01, 2018 7:13 pm

Re: Script Driven Quests

Post by Gandoff »

Wow, thanks! Is it possible to assign a quest directly to a player without going through a quest giver in C#?

I will assimilate your code and let you know.
User avatar
Tony Li
Posts: 22143
Joined: Thu Jul 18, 2013 1:27 pm

Re: Script Driven Quests

Post by Tony Li »

Gandoff wrote: Thu Jan 04, 2018 12:58 pmIs it possible to assign a quest directly to a player without going through a quest giver in C#?
Sure! At the end, instead of:

Code: Select all

GetComponent<QuestGiver>().AddQuest(myQuest);
use this:

Code: Select all

GetComponent<QuestJournal>().AddQuest(myQuest);
myQuest.SetState(QuestState.Active);
This assumes the script is on the player -- that is, the GameObject that has the QuestJournal.

The second line (myQuest.SetState) activates the quest.
Gandoff
Posts: 20
Joined: Mon Jan 01, 2018 7:13 pm

Re: Script Driven Quests

Post by Gandoff »

Your code worked like a charm.
Thank you.
User avatar
Tony Li
Posts: 22143
Joined: Thu Jul 18, 2013 1:27 pm

Re: Script Driven Quests

Post by Tony Li »

Glad to help!
Post Reply