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 questsList = new List; 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 subObjectiveIDs = new List ; #endregion #region Constructors public Quest (Objective mainObjective) { 				mainObjectiveID = mainObjective.ID; subObjectiveIDs = new List ; 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 = " " + mainObjectiveInstance.Objective.GetTitle (language) + ": "; result += "\n"; foreach (int subObjectiveID in subObjectiveIDs) { 					ObjectiveInstance subObjectiveInstance = KickStarter.runtimeObjectives.GetObjective (subObjectiveID); if (subObjectiveInstance == null) { 						continue; } 					result += " - " + subObjectiveInstance.Objective.GetTitle (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 { 				if (mainObjectiveID < 0 ||  					subObjectiveIDs.Contains (mainObjectiveID) ||  					subObjectiveIDs.Count == 0) { 					return false; } 				return true; } 			#endregion } 	}  }