CapersProject/Assets/Plugins/Pixel Crushers/Dialogue System/Scripts/Quests/QuestLogWindow.cs

594 lines
23 KiB
C#

// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
namespace PixelCrushers.DialogueSystem
{
/// <summary>
/// This is the abstract base class for quest log windows. You can implement a quest log
/// window in any GUI system by creating a subclass.
///
/// When open, the window displays active and completed quests. It gets the titles,
/// descriptions, and states of the quests from the QuestLog class.
///
/// The window allows the player to abandon quests (if the quest's Abandonable field is
/// true) and toggle tracking (if the quest's Trackable field is true).
/// </summary>
/// <remarks>
/// If pauseWhileOpen is set to <c>true</c>, the quest log window pauses the game by setting
/// <c>Time.timeScale</c> to <c>0</c>. When closed, it restores the previous time scale.
/// </remarks>
public abstract class QuestLogWindow : MonoBehaviour
{
[Tooltip("Optional localized text table to use to localize no active/completed quests.")]
public TextTable textTable = null; // v2: changed from LocalizedTextTable.
[Tooltip("Text to show (or localize) when there are no active quests.")]
public string noActiveQuestsText = "No Active Quests";
[Tooltip("Text to show (or localize) when there are no completed quests.")]
public string noCompletedQuestsText = "No Completed Quests";
[Tooltip("Check if quest has a field named 'Visible'. If field is false, don't show quest.")]
public bool checkVisibleField = false;
public enum QuestHeadingSource
{
/// <summary>
/// Use the name of the item for the quest heading.
/// </summary>
Name,
/// <summary>
/// Use the item's Description field for the quest heading.
/// </summary>
Description
};
/// <summary>
/// The quest title source.
/// </summary>
public QuestHeadingSource questHeadingSource = QuestHeadingSource.Name;
/// <summary>
/// The state to assign abandoned quests.
/// </summary>
[Tooltip("State to assign to quests when player abandons then.")]
[QuestState]
public QuestState abandonQuestState = QuestState.Unassigned;
/// <summary>
/// If <c>true</c>, the window sets <c>Time.timeScale = 0</c> to pause the game while
/// displaying the quest log window.
/// </summary>
public bool pauseWhileOpen = true;
/// <summary>
/// If <c>true</c>, the cursor is unlocked while the quest log window is open.
/// </summary>
public bool unlockCursorWhileOpen = true;
/// <summary>
/// If <c>true</c>, organize the quests by group.
/// </summary>
[Tooltip("Organize quests by the values of their Group fields.")]
public bool useGroups = false;
[Tooltip("If not blank, show this text next to quest titles that haven't been viewed yet. Will be localized if text has entry in Dialogue Manager's Text Table.")]
public string newQuestText = string.Empty;
[Tooltip("Allow only one quest to be tracked at a time.")]
public bool trackOneQuestAtATime = false;
[Tooltip("Clicking again on selected quest title deselects quest.")]
public bool deselectQuestOnSecondClick = true;
[Serializable]
public class QuestInfo
{
public string Group { get; set; }
public string GroupDisplayName { get; set; }
public string Title { get; set; }
public FormattedText Heading { get; set; }
public FormattedText Description { get; set; }
public FormattedText[] Entries { get; set; }
public QuestState[] EntryStates { get; set; }
public bool Trackable { get; set; }
public bool Track { get; set; }
public bool Abandonable { get; set; }
public QuestInfo(string group, string groupDisplayName, string title, FormattedText heading, FormattedText description,
FormattedText[] entries, QuestState[] entryStates, bool trackable,
bool track, bool abandonable)
{
this.Group = group;
this.GroupDisplayName = groupDisplayName;
this.Title = title;
this.Heading = heading;
this.Description = description;
this.Entries = entries;
this.EntryStates = entryStates;
this.Trackable = trackable;
this.Track = track;
this.Abandonable = abandonable;
}
public QuestInfo(string group, string title, FormattedText heading, FormattedText description,
FormattedText[] entries, QuestState[] entryStates, bool trackable,
bool track, bool abandonable)
{
this.Group = group;
this.GroupDisplayName = string.Empty;
this.Title = title;
this.Heading = heading;
this.Description = description;
this.Entries = entries;
this.EntryStates = entryStates;
this.Trackable = trackable;
this.Track = track;
this.Abandonable = abandonable;
}
public QuestInfo(string title, FormattedText heading, FormattedText description,
FormattedText[] entries, QuestState[] entryStates, bool trackable,
bool track, bool abandonable)
{
this.Group = string.Empty;
this.GroupDisplayName = string.Empty;
this.Title = title;
this.Heading = heading;
this.Description = description;
this.Entries = entries;
this.EntryStates = entryStates;
this.Trackable = trackable;
this.Track = track;
this.Abandonable = abandonable;
}
}
/// <summary>
/// Indicates whether the quest log window is currently open.
/// </summary>
/// <value>
/// <c>true</c> if open; otherwise, <c>false</c>.
/// </value>
public bool isOpen { get; protected set; }
/// <summary>
/// The current list of quests. This will change based on whether the player is
/// viewing active or completed quests.
/// </summary>
/// <value>The quests.</value>
public QuestInfo[] quests { get; protected set; }
/// <summary>
/// The current list of quest groups.
/// </summary>
/// <value>The quest group names.</value>
public string[] groups { get; protected set; }
/// <summary>
/// The title of the currently-selected quest.
/// </summary>
/// <value>The selected quest.</value>
public string selectedQuest { get; protected set; }
/// <summary>
/// The message to show if Quests[] is empty.
/// </summary>
/// <value>The no quests message.</value>
public string noQuestsMessage { get; protected set; }
/// <summary>
/// Indicates whether the window is showing active quests or completed quests.
/// </summary>
/// <value><c>true</c> if showing active quests; otherwise, <c>false</c>.</value>
public virtual bool isShowingActiveQuests { get { return currentQuestStateMask == ActiveQuestStateMask; } }
/// @cond FOR_V1_COMPATIBILITY
public bool IsOpen { get { return isOpen; } protected set { isOpen = value; } }
public QuestInfo[] Quests { get { return quests; } protected set { quests = value; } }
public string[] Groups { get { return groups; } protected set { groups = value; } }
public string SelectedQuest { get { return selectedQuest; } protected set { selectedQuest = value; } }
public string NoQuestsMessage { get { return noQuestsMessage; } protected set { noQuestsMessage = value; } }
public bool IsShowingActiveQuests { get { return isShowingActiveQuests; } }
/// @endcond
protected const QuestState ActiveQuestStateMask = QuestState.Active | QuestState.ReturnToNPC;
/// <summary>
/// The current quest state mask.
/// </summary>
protected QuestState currentQuestStateMask = ActiveQuestStateMask;
/// <summary>
/// The previous time scale prior to opening the window.
/// </summary>
protected float previousTimeScale = 1;
protected Coroutine refreshCoroutine = null;
protected bool started = false;
public virtual void Awake()
{
isOpen = false;
quests = new QuestInfo[0];
groups = new string[0];
selectedQuest = string.Empty;
noQuestsMessage = string.Empty;
}
protected virtual void Start()
{
started = true;
RegisterForUpdateTrackerEvents();
}
protected virtual void OnEnable()
{
if (started) RegisterForUpdateTrackerEvents();
}
protected virtual void OnDisable()
{
refreshCoroutine = null;
UnregisterFromUpdateTrackerEvents();
}
protected void RegisterForUpdateTrackerEvents()
{
if (!started || DialogueManager.instance == null) return;
if (GetComponentInParent<DialogueSystemController>() != null) return; // Children of Dialogue Manager automatically receive UpdateTracker; no need to register.
DialogueManager.instance.receivedUpdateTracker -= UpdateTracker;
DialogueManager.instance.receivedUpdateTracker += UpdateTracker;
}
protected void UnregisterFromUpdateTrackerEvents()
{
if (!started || DialogueManager.instance == null) return;
DialogueManager.instance.receivedUpdateTracker -= UpdateTracker;
}
/// <summary>
/// Opens the window. Your implementation should override this to handle any
/// window-opening activity, then call openedWindowHandler at the end.
/// </summary>
/// <param name="openedWindowHandler">Opened window handler.</param>
public virtual void OpenWindow(Action openedWindowHandler)
{
openedWindowHandler();
}
/// <summary>
/// Closes the window. Your implementation should override this to handle any
/// window-closing activity, then call closedWindowHandler at the end.
/// </summary>
/// <param name="openedWindowHandler">Closed window handler.</param>
public virtual void CloseWindow(Action closedWindowHandler)
{
closedWindowHandler();
}
/// <summary>
/// Called when the quest list has been updated -- for example, when switching between
/// active and completed quests. Your implementation may override this to do processing.
/// </summary>
public virtual void OnQuestListUpdated() { }
/// <summary>
/// Asks the player to confirm abandonment of a quest. Your implementation should override
/// this to show a modal dialogue box or something similar. If confirmed, it should call
/// confirmedAbandonQuestHandler.
/// </summary>
/// <param name="title">Title.</param>
/// <param name="confirmedAbandonQuestHandler">Confirmed abandon quest handler.</param>
public virtual void ConfirmAbandonQuest(string title, Action confirmedAbandonQuestHandler) { }
/// <summary>
/// Opens the quest window.
/// </summary>
public virtual void Open()
{
QuestLog.trackOneQuestAtATime = trackOneQuestAtATime;
PauseGameplay();
OpenWindow(OnOpenedWindow);
}
protected virtual void OnOpenedWindow()
{
isOpen = true;
ShowQuests(currentQuestStateMask);
}
/// <summary>
/// Closes the quest log window. While you can call this manually in your own script, this
/// method is normally called internally when the player clicks the close button. You can
/// call it manually to support alternate methods of closing the window.
/// </summary>
/// <example>
/// if (Input.GetKeyDown(KeyCode.L) && myQuestLogWindow.IsOpen) {
/// myQuestLogWindow.Close();
/// }
/// </example>
public virtual void Close()
{
//--- No need to clear it: selectedQuest = string.Empty;
CloseWindow(OnClosedWindow);
}
protected virtual void OnClosedWindow()
{
isOpen = false;
ResumeGameplay();
}
private bool wasCursorActive = false;
protected virtual void PauseGameplay()
{
if (pauseWhileOpen)
{
previousTimeScale = Time.timeScale;
Time.timeScale = 0;
}
if (unlockCursorWhileOpen)
{
wasCursorActive = Tools.IsCursorActive();
Tools.SetCursorActive(true);
}
}
protected virtual void ResumeGameplay()
{
if (pauseWhileOpen) Time.timeScale = previousTimeScale;
if (unlockCursorWhileOpen && !wasCursorActive) Tools.SetCursorActive(false);
}
public virtual bool IsQuestVisible(string questTitle)
{
return !checkVisibleField || Lua.IsTrue("Quest[\"" + DialogueLua.StringToTableIndex(questTitle) + "\"].Visible ~= false");
}
protected virtual void ShowQuests(QuestState questStateMask)
{
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;
questList.Add(GetQuestInfo(record.groupName, record.questTitle));
}
}
else
{
string[] titles = QuestLog.GetAllQuests(questStateMask, true, null);
foreach (var title in titles)
{
if (!IsQuestVisible(title)) continue;
questList.Add(GetQuestInfo(string.Empty, title));
}
}
quests = questList.ToArray();
OnQuestListUpdated();
}
protected virtual 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;
string localizedGroup = string.IsNullOrEmpty(group) ? string.Empty : QuestLog.GetQuestGroup(title);
string localizedGroupDisplayName = string.IsNullOrEmpty(group) ? string.Empty : QuestLog.GetQuestGroupDisplayName(title);
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);
}
// Check if need to show [new]:
if (!string.IsNullOrEmpty(newQuestText))
{
if (!QuestLog.WasQuestViewed(title))
{
heading.text += " " + FormattedText.Parse(DialogueManager.GetLocalizedText(newQuestText)).text;
}
}
return new QuestInfo(localizedGroup, localizedGroupDisplayName, title, heading, description, entries, entryStates, trackable, track, abandonable);
}
/// <summary>
/// Gets the "no quests" message for a quest state (active or success|failure). This
/// method uses the strings "No Active Quests" and "No Completed Quests" or their
/// localized equivalents if you've set the localized text table.
/// </summary>
/// <returns>The "no quests" message.</returns>
/// <param name="questStateMask">Quest state mask.</param>
protected virtual string GetNoQuestsMessage(QuestState questStateMask)
{
return (questStateMask == ActiveQuestStateMask) ? GetLocalizedText(noActiveQuestsText) : GetLocalizedText(noCompletedQuestsText);
}
/// <summary>
/// Gets the localized text for a field name.
/// </summary>
/// <returns>The localized text.</returns>
/// <param name="fieldName">Field name.</param>
public virtual string GetLocalizedText(string fieldName)
{
if ((textTable != null) && textTable.HasFieldTextForLanguage(fieldName, Localization.GetCurrentLanguageID(textTable)))
{
return textTable.GetFieldTextForLanguage(fieldName, Localization.GetCurrentLanguageID(textTable));
}
else
{
return DialogueManager.GetLocalizedText(fieldName);
}
}
/// <summary>
/// Determines whether the specified questInfo is for the currently-selected quest.
/// </summary>
/// <returns><c>true</c> if this is the selected quest; otherwise, <c>false</c>.</returns>
/// <param name="questInfo">Quest info.</param>
public virtual bool IsSelectedQuest(QuestInfo questInfo)
{
return string.Equals(questInfo.Title, selectedQuest);
}
/// <summary>
/// Your GUI close button should call this.
/// </summary>
/// <param name="data">Ignored.</param>
public void ClickClose(object data)
{
Close();
}
/// <summary>
/// Your GUI "show active quests" button should call this.
/// </summary>
/// <param name="data">Ignored.</param>
public virtual void ClickShowActiveQuests(object data)
{
ShowQuests(ActiveQuestStateMask);
}
/// <summary>
/// Your GUI "show completed quests" button should call this.
/// </summary>
/// <param name="data">Ignored.</param>
public virtual void ClickShowCompletedQuests(object data)
{
ShowQuests(QuestState.Success | QuestState.Failure);
}
/// <summary>
/// Your GUI should call this when the player clicks on a quest to expand
/// or close it.
/// </summary>
/// <param name="data">The quest title.</param>
public virtual void ClickQuest(object data)
{
if (!IsString(data)) return;
string clickedQuest = (string)data;
selectedQuest = (deselectQuestOnSecondClick && string.Equals(selectedQuest, clickedQuest)) ? string.Empty : clickedQuest;
// Mark viewed:
if (!string.IsNullOrEmpty(newQuestText) && !string.IsNullOrEmpty(selectedQuest))
{
QuestLog.MarkQuestViewed(selectedQuest);
foreach (var quest in quests)
{
if (IsSelectedQuest(quest))
{
var newQuestInfo = GetQuestInfo(quest.Group, quest.Title);
quest.Heading = newQuestInfo.Heading;
break;
}
}
}
OnQuestListUpdated();
}
/// <summary>
/// Your GUI should call this when the player clicks to abandon a quest.
/// </summary>
/// <param name="data">Ignored.</param>
public virtual void ClickAbandonQuest(object data)
{
if (string.IsNullOrEmpty(selectedQuest)) return;
ConfirmAbandonQuest(selectedQuest, OnConfirmAbandonQuest);
}
/// <summary>
/// Your GUI should call this when the player confirms abandonment of a quest.
/// </summary>
protected virtual void OnConfirmAbandonQuest()
{
QuestLog.SetQuestState(selectedQuest, abandonQuestState);
selectedQuest = string.Empty;
ShowQuests(currentQuestStateMask);
DialogueManager.instance.BroadcastMessage(DialogueSystemMessages.OnQuestTrackingDisabled, selectedQuest, SendMessageOptions.DontRequireReceiver);
string sequence = QuestLog.GetQuestAbandonSequence(selectedQuest);
if (!string.IsNullOrEmpty(sequence)) DialogueManager.PlaySequence(sequence);
}
/// <summary>
/// Your GUI should call this when the player clicks to toggle quest tracking.
/// </summary>
/// <param name="data">Ignored.</param>
public virtual void ClickTrackQuest(object data)
{
if (string.IsNullOrEmpty(selectedQuest)) return;
bool track = !QuestLog.IsQuestTrackingEnabled(selectedQuest);
QuestLog.SetQuestTracking(selectedQuest, track);
}
private bool IsString(object data)
{
return (data != null) && (data.GetType() == typeof(string));
}
// Parameter-less versions of methods for GUI systems that require them for button hookups:
public virtual void ClickShowActiveQuestsButton()
{
ClickShowActiveQuests(null);
}
public void ClickShowCompletedQuestsButton()
{
ClickShowCompletedQuests(null);
}
public void ClickCloseButton()
{
ClickClose(null);
}
public void ClickAbandonQuestButton()
{
ClickAbandonQuest(null);
}
public void ClickTrackQuestButton()
{
ClickTrackQuest(null);
}
public void UpdateTracker()
{
if (isOpen)
{
if (refreshCoroutine == null)
{
refreshCoroutine = StartCoroutine(UpdateQuestDisplayAtEndOfFrame());
}
}
}
protected IEnumerator UpdateQuestDisplayAtEndOfFrame()
{
yield return CoroutineUtility.endOfFrame;
refreshCoroutine = null;
ShowQuests(currentQuestStateMask);
}
}
}