Quest Tracker

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

Re: Quest Tracker

Post by Tony Li »

Hi,

Please take a look at this updated example:

CustomQuestStateExample_2019-11-27.unitypackage

I missed something. This code:

Code: Select all

        else if (entryState == QuestState.Active || isPreempted)
        {
            var text = DialogueLua.GetQuestField(quest, QuestLog.GetQuestEntry(quest, 1)).asString;
            if (!string.IsNullOrEmpty(text)) return text;
        }
should be this:

Code: Select all

        else if (entryState == QuestState.Active || isPreempted)
        {
            var text = DialogueLua.GetQuestField(quest, QuestLog.GetQuestEntry(quest, entryNum)).asString;
            if (!string.IsNullOrEmpty(text)) return text;
        }
When getting the text of a quest entry, you need to specify the quest entry number. When I copied your code from a previous post, I didn't notice that it was always specifying entry number 1.

The Dialogue Editor window shows design time values. It doesn't update in realtime during play. The only exceptions are:
  • The Conversation section shows the state of the active conversation. The current node is green. Valid links are green and invalid links are red.
  • The Watches tab lets you add runtime watches on values such as:

    Code: Select all

    CurrentQuestState("Something for Rose")
The updated example includes a quest log window subclass, since it looks like you need custom behavior there, too. I added comments to explain why certain things were overridden.
nivlekius
Posts: 105
Joined: Thu Feb 28, 2019 4:39 pm

Re: Quest Tracker

Post by nivlekius »

It's still not showing entry 2. I've looked very carefully and have set everything on the quest exactly as you have it. I've used only your scripts and as far as I know the only other thing that references custom quest states is the indicator script. I checked the hierarchy to make sure the text wasn't getting cut off or something but it wasn't there either so it not getting changed for some reason. The QuestStae is but not the entry is not. Everything works except for the entry. Could I possibly be missing a setting somewhere?
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest Tracker

Post by Tony Li »

To make sure we're on the same page: In my example scene, does entry 2 appear correctly?
nivlekius
Posts: 105
Joined: Thu Feb 28, 2019 4:39 pm

Re: Quest Tracker

Post by nivlekius »

yes it does
and I can't find anything that is different between the 2
nivlekius
Posts: 105
Joined: Thu Feb 28, 2019 4:39 pm

Re: Quest Tracker

Post by nivlekius »

I'll post all of the custom quest classes I have. I don't see what could be interfering with the entry. But maybe you can

Code: Select all

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

public class CustomQuestLogWindow : StandardUIQuestLogWindow
{

    /*
    protected override void ShowQuests(QuestState questStateMask)
    {
        currentQuestStateMask = questStateMask;

        // If we're viewing active quests, also include preempted ones, which report as unassigned:
        var mask = questStateMask;

        if (mask == QuestState.Active) mask = mask | QuestState.Unassigned;

        noQuestsMessage = GetNoQuestsMessage(questStateMask);
        List<QuestInfo> questList = new List<QuestInfo>();
        if (useGroups)
        {
            var records = QuestLog.GetAllGroupsAndQuests(mask, true);
            foreach (var record in records)
            {
                if (!IsQuestVisible(record.questTitle)) continue;
                // If it's really unassigned and not preempted, skip it:
                if (QuestLog.CurrentQuestState(record.questTitle) == "unassigned" || QuestLog.CurrentQuestState(record.questTitle) == "ready") continue;
                questList.Add(GetQuestInfo(record.groupName, record.questTitle));
            }
        }
        else
        {
            string[] titles = QuestLog.GetAllQuests(mask, true, null);
            foreach (var title in titles)
            {
                if (!IsQuestVisible(title)) continue;
                // If it's really unassigned and not preempted, skip it:
                if (QuestLog.CurrentQuestState(title) == "unassigned" || QuestLog.CurrentQuestState(title) == "ready") continue;
                questList.Add(GetQuestInfo(string.Empty, title));
            }
        }
        quests = questList.ToArray();
        OnQuestListUpdated();
    }*/

    protected override void ShowQuests(QuestState questStateMask)
    {
        // Added: If showing active quests, also consider unassigned quests 
        // so we can include preempted.
        if ((questStateMask & QuestState.Active) != 0)
        {
            questStateMask |= QuestState.Unassigned;
        }

        currentQuestStateMask = questStateMask;
        noQuestsMessage = GetNoQuestsMessage(questStateMask);
        List<QuestInfo> questList = new List<QuestInfo>();
        if (useGroups)
        {
            var records = QuestLog.GetAllGroupsAndQuests(questStateMask, true);
            foreach (var record in records)
            {
                if (!IsQuestVisible(record.questTitle)) continue;
                if (IsQuestUnassignedOrReady(record.questTitle)) continue; // Added: Skip unassigned and ready quests.
                questList.Add(GetQuestInfo(record.groupName, record.questTitle));
            }
        }
        else
        {
            string[] titles = QuestLog.GetAllQuests(questStateMask, true, null);
            foreach (var title in titles)
            {
                if (!IsQuestVisible(title)) continue;
                if (IsQuestUnassignedOrReady(title)) continue; // Added: Skip unassigned and ready quests.
                questList.Add(GetQuestInfo(string.Empty, title));
            }
        }
        quests = questList.ToArray();
        OnQuestListUpdated();
    }

    // Added: This tells us if a quest's state is 'unassigned' or 'ready' (i.e., don't show).
    private bool IsQuestUnassignedOrReady(string quest)
    {
        var stateString = QuestLog.CurrentQuestState(quest);
        return stateString == "unassigned" || stateString == "ready";
    }

    protected override QuestInfo GetQuestInfo(string group, string title)
    {
        FormattedText description = FormattedText.Parse(QuestLog.GetQuestDescription(title), DialogueManager.masterDatabase.emphasisSettings);
        FormattedText localizedTitle = FormattedText.Parse(QuestLog.GetQuestTitle(title), DialogueManager.masterDatabase.emphasisSettings);
        FormattedText heading = (questHeadingSource == QuestHeadingSource.Description) ? description : localizedTitle;
        bool abandonable = QuestLog.IsQuestAbandonable(title) && isShowingActiveQuests;
        bool trackable = QuestLog.IsQuestTrackingAvailable(title) && isShowingActiveQuests;
        bool track = QuestLog.IsQuestTrackingEnabled(title);
        int entryCount = QuestLog.GetQuestEntryCount(title);
        FormattedText[] entries = new FormattedText[entryCount];
        QuestState[] entryStates = new QuestState[entryCount];
        for (int i = 0; i < entryCount; i++)
        {
            entries[i] = FormattedText.Parse(QuestLog.GetQuestEntry(title, i + 1), DialogueManager.masterDatabase.emphasisSettings);
            entryStates[i] = QuestLog.GetQuestEntryState(title, i + 1);

            // Added: If quest is preempted, change its entryStates[i] to Active:
            if (QuestLog.CurrentQuestEntryState(title, i + 1) == "preempted")
            {
                entryStates[i] = QuestState.Active;
            }
        }
        return new QuestInfo(group, title, heading, description, entries, entryStates, trackable, track, abandonable);
    }
}

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

public class CustomQuestTracker : StandardUIQuestTracker
{

    // Need to define these again because they're private in the parent class:
    private List<StandardUIQuestTrackTemplate> _unusedInstances = new List<StandardUIQuestTrackTemplate>();
    private int _siblingIndexCounter = 0;

    protected override IEnumerator RefreshAtEndOfFrame()
    {
        yield return new WaitForEndOfFrame();

        // Move instances to the unused list:
        _unusedInstances.AddRange(instantiatedItems);
        instantiatedItems.Clear();
        _siblingIndexCounter = 0;

        // Add quests, drawing from unused list when possible:
        int numTracked = 0;
        QuestState flags = (showActiveQuests ? QuestState.Active : 0) |
            (showCompletedQuests ? QuestState.Success | QuestState.Failure : 0) | QuestState.Unassigned;
        foreach (string quest in QuestLog.GetAllQuests(flags))
        {
            if (QuestLog.IsQuestTrackingEnabled(quest) &&
                QuestLog.CurrentQuestState(quest) != "unassigned" &&
                QuestLog.CurrentQuestState(quest) != "ready")
            {
                AddQuestTrack(quest);
                numTracked++;
            }
        }
        if (container != null)
        {
            container.gameObject.SetActive(showContainerIfEmpty || numTracked > 0);
        }

        // Destroy remaining unused instances:
        for (int i = 0; i < _unusedInstances.Count; i++)
        {
            Destroy(_unusedInstances[i].gameObject);
        }
        _unusedInstances.Clear();
        refreshCoroutine = null;
    }

    protected override void AddQuestTrack(string quest)
    {
        if (container == null || questTrackTemplate == null) return;

        var questDescription = (questDescriptionSource == QuestDescriptionSource.Title)
            ? QuestLog.GetQuestTitle(quest)
                : QuestLog.GetQuestDescription(quest);
        var heading = FormattedText.Parse(questDescription, DialogueManager.masterDatabase.emphasisSettings).text;

        GameObject go;
        if (_unusedInstances.Count > 0)
        {
            // Try to use an unused instance:
            go = _unusedInstances[0].gameObject;
            _unusedInstances.RemoveAt(0);
        }
        else
        {
            // Otherwise instantiate one:
            go = Instantiate(questTrackTemplate.gameObject) as GameObject;
            if (go == null)
            {
                Debug.LogError(string.Format("{0}: {1} couldn't instantiate quest track template", new object[] { DialogueDebug.Prefix, name }));
                return;
            }
        }

        go.name = heading;
        go.transform.SetParent(container.transform, false);
        go.SetActive(true);
        var questTrack = go.GetComponent<StandardUIQuestTrackTemplate>();
        instantiatedItems.Add(questTrack);
        if (questTrack != null)
        {
            questTrack.Initialize();
            // Handle special quest states
            var questStateString = QuestLog.CurrentQuestState(quest);
            var questState = (questStateString == "preempted") ? QuestState.Active
                : (questStateString == "complete") ? QuestState.Active // Could be Success
                : QuestLog.GetQuestState(quest);
            questTrack.SetDescription(heading, questState);
            int entryCount = QuestLog.GetQuestEntryCount(quest);
            for (int i = 1; i <= entryCount; i++)
            {
                var entryState = QuestLog.GetQuestEntryState(quest, i);
                var entryText = FormattedText.Parse(GetQuestEntryText(quest, i, entryState), DialogueManager.masterDatabase.emphasisSettings).text;
                if (!string.IsNullOrEmpty(entryText))
                {
                    questTrack.AddEntryDescription(entryText, entryState);
                }
            }

            questTrack.transform.SetSiblingIndex(_siblingIndexCounter++);
        }
    }

    protected override string GetQuestEntryText(string quest, int entryNum, QuestState entryState)
    {
        var questStateString = QuestLog.CurrentQuestEntryState(quest, entryNum);
        var isPreempted = questStateString == "preempted";
        var isReallyUnassigned = questStateString == "unassigned";
        if (isReallyUnassigned || entryState == QuestState.Abandoned)
        {
            return string.Empty;
        }
        else if ((entryState == QuestState.Success || entryState == QuestState.Failure) && !showCompletedEntryText)
        {
            return string.Empty;
        }
        else if (entryState == QuestState.Success)
        {
            return string.Empty;
        }
        else if (entryState == QuestState.Failure)
        {
            return string.Empty;
        }
        else if (entryState == QuestState.Active || isPreempted)
        {
            var text = DialogueLua.GetQuestField(quest, QuestLog.GetQuestEntry(quest, entryNum)).asString;
            if (!string.IsNullOrEmpty(text)) return text;
        }
        return QuestLog.GetQuestEntry(quest, entryNum);
    }
  }
  
 using PixelCrushers.DialogueSystem;
using UnityEngine;
using Beebyte.Obfuscator;

public class CustomQuestStateCode : MonoBehaviour
{

    public const string QuestStatePreempted = "preempted";
    public const string QuestStateReady = "ready";
    public const string QuestStateComplete = "complete";

    private void Start()
    {
        QuestLog.StringToState = CustomStringToState;
        QuestLog.StringToState = MakeReadyState;
        QuestLog.StringToState = CustomActiveState;
    }

    public static QuestState CustomStringToState(string s)
    {
        if (s == "preempted") return QuestState.Unassigned;
        else return QuestLog.DefaultStringToState(s);
    }

    public static QuestState CustomActiveState(string s)
    {
        if (s == "complete") return QuestState.Active;
        else return QuestLog.DefaultStringToState(s);
    }

    public static QuestState MakeReadyState(string s)
    {
        if (s == "ready") return QuestState.Unassigned;
        else return QuestLog.DefaultStringToState(s);
    }

    public void OnQuestTrackingEnabled(string questName)
    {
        ManagerSupp.instance.SaveLua();
    }

    public void OnQuestTrackingDisabled(string questName)
    {
        ManagerSupp.instance.SaveLua();
    }

}

using UnityEngine;
using UnityEngine.Events;
using System;

namespace PixelCrushers.DialogueSystem
{

    /// <summary>
    /// This is a subclass of QuestStateListener. It defines and uses 
    /// CustomQuestStateIndicatorLevel that checks quest states by their string value.
    /// It also uses a custom editor script to hide the base class's
    /// QuestStateIndicatorLevel.
    /// </summary>
    public class CustomQuestStateListener : QuestStateListener
    {

        [Serializable]
        public class CustomQuestStateIndicatorLevel
        {
            [Tooltip("Quest state to listen for.")]
            public string questState;

            [Tooltip("Conditions that must also be true.")]
            public Condition condition;

            [Tooltip("Indicator level to use when this quest state is reached.")]
            public int indicatorLevel;

            public UnityEvent onEnterState = new UnityEvent();
        }

        public CustomQuestStateIndicatorLevel[] customQuestStateIndicatorLevels = new CustomQuestStateIndicatorLevel[0];

        [Serializable]
        public class CustomQuestEntryStateIndicatorLevel
        {
            [Tooltip("Quest entry number.")]
            public int entryNumber;

            [Tooltip("Quest entry state to listen for.")]
            public string questState;

            [Tooltip("Conditions that must also be true.")]
            public Condition condition;

            [Tooltip("Indicator level to use when this quest state is reached.")]
            public int indicatorLevel;

            public UnityEvent onEnterState = new UnityEvent();
        }

        public CustomQuestEntryStateIndicatorLevel[] customQuestEntryStateIndicatorLevels = new CustomQuestEntryStateIndicatorLevel[0];

        /// <summary>
        /// Update the current quest state indicator based on the specified quest state indicator 
        /// levels and quest entry state indicator levels.
        /// 
        /// In this version, we ignore questStateIndicatorLevels[] and instead use
        /// customQuestStateIndicatorLevels[].
        /// </summary>
        public override void UpdateIndicator()
        {
            // Check quest state:
            var questState = QuestLog.CurrentQuestState(questName); // Get string version of quest state.
            for (int i = 0; i < customQuestStateIndicatorLevels.Length; i++)
            {
                var questStateIndicatorLevel = customQuestStateIndicatorLevels[i]; // Check custom value.
                if (questState == questStateIndicatorLevel.questState && questStateIndicatorLevel.condition.IsTrue(null))
                {
                    if (DialogueDebug.logInfo) Debug.Log("Dialogue System: " + name + ": Quest '" + questName + "' changed to state " + questState + ".", this);
                    if (questStateIndicator != null) questStateIndicator.SetIndicatorLevel(this, questStateIndicatorLevel.indicatorLevel);
                    questStateIndicatorLevel.onEnterState.Invoke();
                }
            }

            // Check quest entry states:
            for (int i = 0; i < questEntryStateIndicatorLevels.Length; i++)
            {
                var questEntryStateIndicatorLevel = customQuestEntryStateIndicatorLevels[i];
                var questEntryState = QuestLog.CurrentQuestEntryState(questName, questEntryStateIndicatorLevel.entryNumber);
                if (questEntryState == questEntryStateIndicatorLevel.questState && questEntryStateIndicatorLevel.condition.IsTrue(null))
                {
                    if (DialogueDebug.logInfo) Debug.Log("Dialogue System: " + name + ": Quest '" + questName + "' entry " + questEntryStateIndicatorLevel.entryNumber + " changed to state " + questEntryState + ".", this);
                    if (questStateIndicator != null) questStateIndicator.SetIndicatorLevel(this, questEntryStateIndicatorLevel.indicatorLevel);
                    questEntryStateIndicatorLevel.onEnterState.Invoke();
                }
            }
        }

    }
}

using UnityEditor;

namespace PixelCrushers.DialogueSystem
{

    /// <summary>
    /// This custom editor hides the base class's QuestStateIndicatorLevel and
    /// QuestEntryStateIndicatorLevel arrays since the subclass uses new arrays.
    /// </summary>
    [CustomEditor(typeof(CustomQuestStateListener), true)]
    public class CustomQuestStateListenerEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            serializedObject.Update();
            EditorGUILayout.PropertyField(serializedObject.FindProperty("questName"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("customQuestStateIndicatorLevels"), true);
            EditorGUILayout.PropertyField(serializedObject.FindProperty("customQuestEntryStateIndicatorLevels"), true);
            serializedObject.ApplyModifiedProperties();
        }
    }
}
Again, that's all I know of that references quest entries that have been changed. In my dialogue, I start with checking if unassigned, get the quest and set queststate to active, when I pick up the flowers this is run under lua

if (CurrentQuestState("Something for Rose") == "active") then
SetQuestState("Something for Rose", "preempted")
ShowAlert("Rose might like these flowers")
SetQuestEntryState("Something for Rose", 2, "preempted")
UpdateTracker()
end

Entry 1 is active and Entry 2 is unassigned. I have the field for entry2 set for text. All of this is how your example has it.
Everything works except for showing entry2.

Also, I'm not sure if it has to be this way, if it does then I'll have to live with it, but now the quests do not allow tracking to be disabled at all, and they are greyed out. Can still click them and see details but they have on color. Like it's using the completed or failed heading or something.
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest Tracker

Post by Tony Li »

Good catch on the tracking. To fix that, in ShowQuests() move the "currentQuestStateMask = questStateMask" line to the top:

Code: Select all

    protected override void ShowQuests(QuestState questStateMask)
    {
        currentQuestStateMask = questStateMask; //<-- MOVE THIS TO HERE.

        // Added: If showing active quests, also consider unassigned quests 
        // so we can include preempted.
        if ((questStateMask & QuestState.Active) != 0)
        {
            questStateMask |= QuestState.Unassigned;
        }

        noQuestsMessage = GetNoQuestsMessage(questStateMask);
        ...
I don't know why entry 2 isn't showing. I ran a diff on your scripts, including the Lua code, and the code is identical to the example. Would it be possible for you to send a reproduction project to tony (at) pixelcrushers.com? (All customer files are handled confidentially.)
nivlekius
Posts: 105
Joined: Thu Feb 28, 2019 4:39 pm

Re: Quest Tracker

Post by nivlekius »

Sure it’ll be big though and I don’t know how obfuscatory works if you don’t have. Assuming you don’t. It’ll be about 2 gigs. I’ll have to upload it and send you a link

I’m in the middle of cleaning up my player animator controller too so if you need to replace player animator control to player_anim the original, current is named player. Nvm no reason I can’t just do that before uploading
nivlekius
Posts: 105
Joined: Thu Feb 28, 2019 4:39 pm

Re: Quest Tracker

Post by nivlekius »

e-mail sent
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest Tracker

Post by Tony Li »

Thanks! Please check your email. I sent a reply. Not a fix yet. I'm not that fast. ;-) I just have a question about the download.
User avatar
Tony Li
Posts: 22055
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest Tracker

Post by Tony Li »

Change this line:

Code: Select all

else if (entryState == QuestState.Active || isPreempted)
{
    var text = DialogueLua.GetQuestField(quest, QuestLog.GetQuestEntry(quest, entryNum)).asString; //<--THIS.
    if (!string.IsNullOrEmpty(text)) return text;
}
to this:

Code: Select all

else if (entryState == QuestState.Active || isPreempted)
{
    var text = QuestLog.GetQuestEntry(quest, entryNum); //<-- TO THIS.
    if (!string.IsNullOrEmpty(text)) return text;
}
And above, in the loop near the bottom of AddQuestTrack(), add these lines:

Code: Select all

if (!string.IsNullOrEmpty(entryText))
{
    var entryStateString = QuestLog.CurrentQuestEntryState(quest, i); //<--ADD THIS & THE ONE BELOW.
    if (entryStateString == "preempted" || questStateString == "complete") entryState = QuestState.Active;
    questTrack.AddEntryDescription(entryText, entryState);
}
Post Reply