// Copyright (c) Pixel Crushers. All rights reserved. using UnityEngine; using UnityEngine.Events; using System; using System.Collections.Generic; namespace PixelCrushers.DialogueSystem { /// /// This is an implementation of the abstract QuestLogWindow class for the Unity UI. /// [AddComponentMenu("")] // Use wrapper. public class UnityUIQuestLogWindow : QuestLogWindow { /// /// The main quest log window panel. /// public UnityEngine.UI.Graphic mainPanel; /// /// The active quests button. /// public UnityEngine.UI.Button activeQuestsButton; /// /// The completed quests button. /// public UnityEngine.UI.Button completedQuestsButton; /// /// The quest table. /// public UnityEngine.UI.Graphic questTable; public UnityUIQuestGroupTemplate questGroupTemplate; /// /// The quest template. /// public UnityUIQuestTemplate questTemplate; /// /// The confirmation popup to use if the player clicks the abandon quest button. /// It should send ClickConfirmAbandonQuest if the player confirms, or /// ClickCancelAbandonQuest if the player cancels. /// public UnityEngine.UI.Graphic abandonPopup; /// /// The quest title label to set in the abandon quest dialog popup. /// public UnityEngine.UI.Text abandonQuestTitle; [Serializable] public class AnimationTransitions { public string showTrigger = "Show"; public string hideTrigger = "Hide"; [Tooltip("Specifies whether Show Trigger and Hide Trigger are animator states or trigger parameters.")] public UIShowHideController.TransitionMode transitionMode = UIShowHideController.TransitionMode.State; public bool debug = false; } public AnimationTransitions animationTransitions = new AnimationTransitions(); /// /// Set true to always keep a control focused; useful for gamepads. /// [Tooltip("Always keep a control focused; useful for gamepads and keyboard.")] public bool autoFocus = false; /// /// If auto focusing, check on this frequency in seconds that the control is focused. /// [Tooltip("If auto focusing, check on this frequency in seconds that the control is focused.")] public float autoFocusCheckFrequency = 0.5f; public UnityEvent onOpen = new UnityEvent(); public UnityEvent onClose = new UnityEvent(); public UnityEvent onContentChanged = new UnityEvent(); [Tooltip("Add an EventSystem if one isn't in the scene.")] public bool addEventSystemIfNeeded = true; /// /// This handler is called if the player confirms abandonment of a quest. /// protected System.Action confirmAbandonQuestHandler = null; private UIShowHideController m_showHideController = null; protected UIShowHideController showHideController { get { if (m_showHideController == null) m_showHideController = new UIShowHideController(this.gameObject, mainPanel, animationTransitions.transitionMode, animationTransitions.debug); return m_showHideController; } } protected List collapsedGroups = new List(); protected List groupTemplateInstances = new List(); protected List questTemplateInstances = new List(); protected List unusedGroupTemplateInstances = new List(); protected List unusedQuestTemplateInstances = new List(); protected int siblingIndexCounter = 0; private float nextAutoFocusCheckTime = 0; public override void Awake() { base.Awake(); Tools.DeprecationWarning(this); } /// /// Hide the main panel and all of the templates on start. /// protected override void Start() { base.Start(); if (addEventSystemIfNeeded) UITools.RequireEventSystem(); Tools.SetGameObjectActive(mainPanel, false); Tools.SetGameObjectActive(abandonPopup, false); Tools.SetGameObjectActive(questGroupTemplate, false); Tools.SetGameObjectActive(questTemplate, false); showHideController.state = UIShowHideController.State.Hidden; SetStateButtonListeners(); SetStateToggleButtons(); if (DialogueDebug.logWarnings) { if (mainPanel == null) Debug.LogWarning(string.Format("{0}: {1} Main Panel is unassigned", new object[] { DialogueDebug.Prefix, name })); if (questTable == null) Debug.LogWarning(string.Format("{0}: {1} Quest Table is unassigned", new object[] { DialogueDebug.Prefix, name })); if (useGroups && ((questTemplate == null || !questTemplate.ArePropertiesAssigned))) { Debug.LogWarning(string.Format("{0}: {1} Quest Group Template or one of its properties is unassigned", new object[] { DialogueDebug.Prefix, name })); } if (questTemplate == null || !questTemplate.ArePropertiesAssigned) { Debug.LogWarning(string.Format("{0}: {1} Quest Template or one of its properties is unassigned", new object[] { DialogueDebug.Prefix, name })); } } } public virtual void Update() { if (autoFocus && isOpen && UnityEngine.EventSystems.EventSystem.current != null && UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject == null && autoFocusCheckFrequency > 0.001f && Time.realtimeSinceStartup > nextAutoFocusCheckTime) { nextAutoFocusCheckTime = Time.realtimeSinceStartup + autoFocusCheckFrequency; AutoFocus(); } } /// /// Open the window by showing the main panel. The bark UI may conflict with the quest /// log window, so temporarily disable it. /// /// Opened window handler. public override void OpenWindow(System.Action openedWindowHandler) { showHideController.Show(animationTransitions.showTrigger, pauseWhileOpen, openedWindowHandler, false); isOpen = true; AutoFocus(); onOpen.Invoke(); } public void AutoFocus() { GameObject go = completedQuestsButton.gameObject.activeSelf ? completedQuestsButton.gameObject : activeQuestsButton.gameObject; UITools.Select(go.GetComponent()); } /// /// Close the window by hiding the main panel. Re-enable the bark UI. /// /// Closed window handler. public override void CloseWindow(System.Action closedWindowHandler) { ResumeGameplay(); showHideController.Hide(animationTransitions.hideTrigger, closedWindowHandler); isOpen = false; onClose.Invoke(); } /// /// Whenever the quest list is updated, repopulate the scroll panel. /// public override void OnQuestListUpdated() { // Move instances to the unused lists: unusedGroupTemplateInstances.AddRange(groupTemplateInstances); unusedQuestTemplateInstances.AddRange(questTemplateInstances); groupTemplateInstances.Clear(); questTemplateInstances.Clear(); siblingIndexCounter = 0; // Add content, drawing from unused list when possible: if (quests.Length == 0) { AddQuestToTable(new QuestInfo(string.Empty, new FormattedText(noQuestsMessage), FormattedText.empty, new FormattedText[0], new QuestState[0], false, false, false)); } else { AddQuestsToTable(); } // Destroy all unused instances: for (int i = 0; i < unusedGroupTemplateInstances.Count; i++) { Destroy(unusedGroupTemplateInstances[i].gameObject); } unusedGroupTemplateInstances.Clear(); for (int i = 0; i < unusedQuestTemplateInstances.Count; i++) { Destroy(unusedQuestTemplateInstances[i].gameObject); } unusedQuestTemplateInstances.Clear(); SetStateToggleButtons(); if (mainPanel != null) UnityEngine.UI.LayoutRebuilder.MarkLayoutForRebuild(mainPanel.rectTransform); } protected void SetStateButtonListeners() { if (activeQuestsButton != null) { activeQuestsButton.onClick.RemoveListener(() => ClickShowActiveQuestsButton()); activeQuestsButton.onClick.AddListener(() => ClickShowActiveQuestsButton()); } if (completedQuestsButton != null) { completedQuestsButton.onClick.RemoveListener(() => ClickShowCompletedQuestsButton()); completedQuestsButton.onClick.AddListener(() => ClickShowCompletedQuestsButton()); } } protected void SetStateToggleButtons() { if (activeQuestsButton != null) activeQuestsButton.interactable = !isShowingActiveQuests; if (completedQuestsButton != null) completedQuestsButton.interactable = isShowingActiveQuests; } protected virtual void ClearQuestTable() { if (questTable == null) return; foreach (Transform child in questTable.transform) { if (child.gameObject.activeSelf) { Destroy(child.gameObject); } } NotifyContentChanged(); } protected virtual void AddQuestsToTable() { if (questTable == null) return; string currentGroup = null; var isCurrentGroupCollapsed = false; for (int i = 0; i < quests.Length; i++) { if (!string.Equals(quests[i].Group, currentGroup)) { currentGroup = quests[i].Group; AddQuestGroupToTable(currentGroup); isCurrentGroupCollapsed = collapsedGroups.Contains(currentGroup); } if (!isCurrentGroupCollapsed) { AddQuestToTable(quests[i]); } } NotifyContentChanged(); } /// /// Adds a quest group to the table using the template. /// /// Quest group name. protected virtual void AddQuestGroupToTable(string group) { if (string.IsNullOrEmpty(group) || questGroupTemplate == null || !questGroupTemplate.ArePropertiesAssigned) return; // Try to use existing instance from unused list first: var existingChild = unusedGroupTemplateInstances.Find(x => string.Equals(x.heading.text, group)); if (existingChild != null) { unusedGroupTemplateInstances.Remove(existingChild); groupTemplateInstances.Add(existingChild); existingChild.transform.SetSiblingIndex(siblingIndexCounter++); return; } // Otherwise create a new one: GameObject questGroupGameObject = Instantiate(questGroupTemplate.gameObject) as GameObject; if (questGroupGameObject == null) { Debug.LogError(string.Format("{0}: {1} couldn't instantiate quest group template", new object[] { DialogueDebug.Prefix, name })); return; } questGroupGameObject.name = group; questGroupGameObject.transform.SetParent(questTable.transform, false); questGroupGameObject.SetActive(true); UnityUIQuestGroupTemplate template = questGroupGameObject.GetComponent(); if (template == null) return; groupTemplateInstances.Add(template); template.transform.SetSiblingIndex(siblingIndexCounter++); template.Initialize(); template.heading.text = group; // Set up the collapse/expand button: var button = questGroupGameObject.GetComponentInChildren(); if (button != null) { button.onClick.AddListener(() => ClickQuestGroupFoldout(group)); } } /// /// Adds a quest to the table using the template. /// /// Quest info. protected virtual void AddQuestToTable(QuestInfo questInfo) { if ((questTable == null) || (questTemplate == null) || !questTemplate.ArePropertiesAssigned) return; // Try to use existing instance from unused list first: var existingChild = unusedQuestTemplateInstances.Find(x => x.heading.GetComponentInChildren() != null && string.Equals(x.heading.GetComponentInChildren().text, questInfo.Heading.text)); if (existingChild != null) { unusedQuestTemplateInstances.Remove(existingChild); questTemplateInstances.Add(existingChild); existingChild.transform.SetSiblingIndex(siblingIndexCounter++); existingChild.description.text = questInfo.Description.text; existingChild.ClearQuestDetails(); SetQuestDetails(existingChild, questInfo); return; } // Otherwise create a new one: // Instantiate a copy of the template: GameObject questGameObject = Instantiate(questTemplate.gameObject) as GameObject; if (questGameObject == null) { Debug.LogError(string.Format("{0}: {1} couldn't instantiate quest template", new object[] { DialogueDebug.Prefix, name })); return; } questGameObject.name = questInfo.Heading.text; questGameObject.transform.SetParent(questTable.transform, false); questGameObject.transform.SetSiblingIndex(siblingIndexCounter++); questGameObject.SetActive(true); UnityUIQuestTemplate questControl = questGameObject.GetComponent(); if (questControl == null) return; questTemplateInstances.Add(questControl); questControl.Initialize(); // Set the heading button: var questHeadingButton = questControl.heading; var heading = questHeadingButton.GetComponentInChildren(); if (heading != null) heading.text = questInfo.Heading.text; questHeadingButton.onClick.AddListener(() => ClickQuestFoldout(questInfo.Title)); SetQuestDetails(questControl, questInfo); } protected virtual void SetQuestDetails(UnityUIQuestTemplate questControl, QuestInfo questInfo) { // Set details if this is the selected quest: if (IsSelectedQuest(questInfo)) { // Set the description: if (questHeadingSource == QuestHeadingSource.Name) { questControl.description.text = questInfo.Description.text; Tools.SetGameObjectActive(questControl.description, true); } // Set the entry description: if (questInfo.EntryStates.Length > 0) { for (int i = 0; i < questInfo.Entries.Length; i++) { questControl.AddEntryDescription(questInfo.Entries[i].text, questInfo.EntryStates[i]); } } UnityUIQuestTitle unityUIQuestTitle = null; // Set the track button: if (questControl.trackButton != null) { unityUIQuestTitle = questControl.trackButton.gameObject.AddComponent(); unityUIQuestTitle.questTitle = questInfo.Title; questControl.trackButton.onClick.RemoveAllListeners(); questControl.trackButton.onClick.AddListener(() => ClickTrackQuestButton()); Tools.SetGameObjectActive(questControl.trackButton, questInfo.Trackable); } // Set the abandon button: if (questControl.abandonButton != null) { unityUIQuestTitle = questControl.abandonButton.gameObject.AddComponent(); unityUIQuestTitle.questTitle = questInfo.Title; questControl.abandonButton.onClick.RemoveAllListeners(); questControl.abandonButton.onClick.AddListener(() => ClickAbandonQuestButton()); Tools.SetGameObjectActive(questControl.abandonButton, questInfo.Abandonable); } } else { Tools.SetGameObjectActive(questControl.description, false); Tools.SetGameObjectActive(questControl.entryDescription, false); Tools.SetGameObjectActive(questControl.trackButton, false); Tools.SetGameObjectActive(questControl.abandonButton, false); } } public void NotifyContentChanged() { onContentChanged.Invoke(); } public void ClickQuestFoldout(string questTitle) { ClickQuest(questTitle); } public void ClickQuestGroupFoldout(string group) { if (collapsedGroups.Contains(group)) { collapsedGroups.Remove(group); } else { collapsedGroups.Add(group); } OnQuestListUpdated(); } /// /// Track button clicked event that sets SelectedQuest first. /// /// Button. protected void OnTrackButtonClicked(GameObject button) { selectedQuest = button.GetComponent().questTitle; ClickTrackQuest(selectedQuest); } /// /// Abandon button clicked event that sets SelectedQuest first. /// /// Button. protected void OnAbandonButtonClicked(GameObject button) { selectedQuest = button.GetComponent().questTitle; ClickAbandonQuest(selectedQuest); } /// /// Opens the abandon confirmation popup. /// /// Quest title. /// Confirm abandon quest handler. public override void ConfirmAbandonQuest(string title, System.Action confirmAbandonQuestHandler) { this.confirmAbandonQuestHandler = confirmAbandonQuestHandler; OpenAbandonPopup(title); } /// /// Opens the abandon popup modally if assigned; otherwise immediately confirms. /// /// Quest title. protected void OpenAbandonPopup(string title) { if (abandonPopup != null) { Tools.SetGameObjectActive(abandonPopup, true); if (abandonQuestTitle != null) abandonQuestTitle.text = QuestLog.GetQuestTitle(title); if (autoFocus && (UnityEngine.EventSystems.EventSystem.current != null)) { var button = abandonPopup.GetComponentInChildren(); if (button != null) { UnityEngine.EventSystems.EventSystem.current.SetSelectedGameObject(button.gameObject); } } else { this.confirmAbandonQuestHandler(); } } } /// /// Closes the abandon popup. /// protected void CloseAbandonPopup() { Tools.SetGameObjectActive(abandonPopup, false); } public void ClickConfirmAbandonQuestButton() { CloseAbandonPopup(); confirmAbandonQuestHandler(); } public void ClickCancelAbandonQuestButton() { CloseAbandonPopup(); } } }