Quest Objectives and sub-Objectives

AC v1.70 introduced Objectives, which offer a way to provide feedback on the player's progress performing the game's tasks.

Objectives can have multiple states, but themselves are independent from one another.  This script allows you to classify specific Objectives as "Quests", which then depend on sub-Objectives for their completion state.  If all sub-Objectives are complete, the Quest Objective will be complete.  Similarly, if all sub-Objectives are failed, the Quest Objective will be failed.

This behaviour might not be desirable for all situations, so the script is intended to act as a demonstration for how the Objective system can be expanded upon through scripting.

Additionally, the state of all active Quests - and their sub-Objectives - will be condensed into a formatted Global String variable, which can then be e.g. displayed in a Label menu element for player feedback.

To use this script:

1. In the Inventory Manager's Quest tab, name your sub-Objectives using the convention "QuestTitle/SubObjectiveTitle" (no quotes).  The script works through naming-convention - so a sub-Objective will be automatically classes as such so long as another Objective named "QuestTitle" (or whatever was used to name the sub-Objective) is found. Any Objective without a forward-slash (/) character will be treated as a Quest.
2. For each Objective, enter in the text to display into the **Description** box.
3. Define a new Global String Variable in the Variables Manager
4. Paste in the code below into a C# script named QuestSystem.cs
5. Add a new GameObject to your game's first scene and attach the new Quest System component to it
6. Also in the Inspector, enter in the ID number of your new String variable.  This too will be to the left of its name in the Variables Manager.
7. (Optional) Create a new Menu with an **Appear type** set to **During Gameplay**, position it to the side of the screen, and give it a single Label element.
8. (Optional) Set that Label element's **Label type** to **Global Variable**, and select the String variable created in step 3.  Increase the size of the element so that it's large enough to display the list of objectives

QuestSystem.cs:

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace AC
{

 public class QuestSystem : MonoBehaviour
 {

 #region Variables

 private Quest[] quests;
 [SerializeField] private int stringVariableID;

 #endregion

 #region UnityStandards

 private void OnEnable ()
 {
 DontDestroyOnLoad (gameObject);
 GenerateQuests ();

 EventManager.OnObjectiveUpdate += OnObjectiveUpdate;
 }

 private void OnDisable ()
 {
 EventManager.OnObjectiveUpdate -= OnObjectiveUpdate;
 }

 #endregion

 #region PrivateFunctions

 private void GenerateQuests ()
 {
 List<Quest> questsList = new List<Quest>();
 foreach (Objective objective in KickStarter.inventoryManager.objectives)
 {
 if (!objective.Title.Contains ("/"))
 {
 Quest quest = new Quest (objective);
 questsList.Add (quest);
 }
 }

 quests = questsList.ToArray ();
 }

 private void OnObjectiveUpdate (Objective objective, ObjectiveState state)
 {
 foreach (Quest quest in quests)
 {
 quest.OnObjectiveUpdate (objective.ID);
 }

 UpdateActiveQuestsString ();
 }

 private void UpdateActiveQuestsString ()
 {
 GVar stringVariable = GlobalVariables.GetVariable (stringVariableID);
 if (stringVariable == null)
 {
 return;
 }

 string result = string.Empty;

 foreach (Quest quest in quests)
 {
 result += quest.GetActiveString ();
 result += "\n";
 }

 stringVariable.SetStringValue (result);
 }

 #endregion

 [System.Serializable]
 private class Quest
 {

 #region Variables

 private int mainObjectiveID;
 private List<int> subObjectiveIDs = new List<int>();

 #endregion

 #region Constructors

 public Quest (Objective mainObjective)
 {
 mainObjectiveID = mainObjective.ID;
 subObjectiveIDs = new List<int>();

 string requiredPrefix = mainObjective.Title + "/";
 foreach (Objective objective in KickStarter.inventoryManager.objectives)
 {
 if (objective != mainObjective && objective.Title.StartsWith (requiredPrefix))
 {
 // Is a sub-objective
 subObjectiveIDs.Add (objective.ID);
 }
 }
 }

 #endregion

 #region PublicFunctions

 public void OnObjectiveUpdate (int objectiveID)
 {
 if (!subObjectiveIDs.Contains (objectiveID) || !IsValid ()) return;
 AutoUpdateMainState ();
 }

 public string GetActiveString ()
 {
 if (!IsValid ())
 {
 return string.Empty;
 }

 ObjectiveInstance mainObjectiveInstance = KickStarter.runtimeObjectives.GetObjective (mainObjectiveID);
 if (mainObjectiveInstance == null || mainObjectiveInstance.CurrentState.stateType != ObjectiveStateType.Active)
 {
 return string.Empty;
 }

 int language = Options.GetLanguage ();
 string result = "<b>" + mainObjectiveInstance.Objective.GetDescription (language) + ":</b>";
 result += "\n";

 foreach (int subObjectiveID in subObjectiveIDs)
 {
 ObjectiveInstance subObjectiveInstance = KickStarter.runtimeObjectives.GetObjective (subObjectiveID);
 if (subObjectiveInstance == null)
 {
 continue;
 }

 result += " - " + subObjectiveInstance.Objective.GetDescription (language);

 switch (subObjectiveInstance.CurrentState.stateType)
 {
 case ObjectiveStateType.Fail:
 result += " (FAILED)";
 break;

 case ObjectiveStateType.Complete:
 result += " (COMPLETE)";
 break;

 default:
 break;
 }

 result += "\n";
 }

 return result;
 }

 #endregion

 #region PrivateFunctions

 private void AutoUpdateMainState ()
 {
 int completedSubObjectives = 0;
 int failedSubObjectives = 0;
 int activeSubObjectives = 0;

 foreach (int subObjectiveID in subObjectiveIDs)
 {
 ObjectiveState subObjectiveState = KickStarter.runtimeObjectives.GetObjectiveState (subObjectiveID);
 if (subObjectiveState == null)
 {
 continue;
 }

 switch (subObjectiveState.stateType)
 {
 case ObjectiveStateType.Active:
 activeSubObjectives ++;
 break;

 case ObjectiveStateType.Complete:
 completedSubObjectives ++;
 break;

 case ObjectiveStateType.Fail:
 failedSubObjectives ++;
 break;

 default:
 break;
 }
 }

 int totalSubObjectives = subObjectiveIDs.Count;

 if (completedSubObjectives == totalSubObjectives)
 {
 // Completed all sub-objectives, main is completed
 KickStarter.runtimeObjectives.SetObjectiveState (mainObjectiveID, ObjectiveStateType.Complete);
 }
 else if (failedSubObjectives > 0)
 {
 // Failed a sub-objectives, main is failed
 KickStarter.runtimeObjectives.SetObjectiveState (mainObjectiveID, ObjectiveStateType.Fail);
 }
 else if (activeSubObjectives > 0)
 {
 // Main is active
 KickStarter.runtimeObjectives.SetObjectiveState (mainObjectiveID, ObjectiveStateType.Active);
 }
 else
 {
 // Main is inactive
 }
 }

 private bool IsValid ()
 {
 subObjectiveIDs.Count == 0)
 {
 return false;
 }
 return true;
 }

 #endregion

 }
 }

}

```