using EasyTalk.Controller;
using EasyTalk.Localization;
using EasyTalk.Nodes.Common;
using EasyTalk.Nodes.Core;
using EasyTalk.Settings;
using EasyTalk.Utils;
using System.Collections.Generic;
#if TEXTMESHPRO_INSTALLED
using TMPro;
#endif
using UnityEngine;
using UnityEngine.Events;
namespace EasyTalk.Display
{
///
/// This class is an abstract framework laying the foundation for a Dialogue Display.
///
public abstract class AbstractDialogueDisplay : DialogueListener
{
///
/// The EasyTalk dialogue settings to use.
///
[Tooltip("The EasyTalk dialogue settings to use.")]
[SerializeField]
protected EasyTalkDialogueSettings dialogueSettings;
///
/// The currently active dialogue controller.
///
protected DialogueController dialogueController;
///
/// An instance of a Dialogue Display.
///
protected static AbstractDialogueDisplay instance;
///
/// When true, the dialogue display will be destroyed when a new scene is loaded.
///
[Tooltip("If set to true, the dialogue display will be destroyed when a new scene is loaded.")]
[SerializeField]
protected bool destroyOnLoad = false;
///
/// When set to true, the dialogue display allows an immediate exit from the currently running dialogue.
///
[Tooltip("When set to true, the player can exit a playing dialogue by pressing a button corresponding to the input for the Exit Conversation Action.")]
[SerializeField]
private bool allowQuickExit;
///
/// A collection of Dialogue Listeners to call as dialogue playback occurs.
///
[Tooltip("The Dialogue Display will call the relevant methods on all Dialogue Listeners in this list as events occur during dialogue playback.")]
[NonReorderable]
[SerializeField]
protected List dialogueListeners = new List();
///
/// A Unity Event which is triggered whenever continuation of the current dialogue is allowed.
///
[Tooltip("This event is riggered whenever continuation to the next line of dialogue is allowed.")]
[SerializeField]
[HideInInspector]
public UnityEvent onContinueEnabled;
///
/// A Unity Event which is triggered whenever continuation of the current dialogue is disabled.
///
[Tooltip("This event is triggered whenever continuation to the next line of dialogue is disabled.")]
[SerializeField]
[HideInInspector]
public UnityEvent onContinueDisabled;
///
/// A Unity Event which is triggered whenever option selection is enabled (after options are presented).
///
[Tooltip("This event is triggered whenever the player is allowed to select options (this normally occurs after options are displayed to the player).")]
[SerializeField]
[HideInInspector]
public UnityEvent onOptionSelectionEnabled;
///
/// A Unity Event which is triggered whenever option selection is disabled (after an option is chosen).
///
[Tooltip("This event is triggered whenever option selection is disallowed (this normally occurs after an option is chosen).")]
[SerializeField]
[HideInInspector]
public UnityEvent onOptionSelectionDisabled;
///
/// A flag indicating whether the display is currently playing a dialogue.
///
public bool IsPlaying { get; private set; } = false;
///
/// A flag indicating whether the dialogue is currently displaying a conversation (rather than options).
///
public bool IsCurrentlyInConversation { get; protected set; } = false;
///
/// A flag indicating whether continuation is currently permitted in the conversation.
///
private bool isContinueAllowed = false;
///
/// A flag indicating whether option selection is currently permitted.
///
private bool isOptionSelectionAllowed = false;
///
private void Awake()
{
if (instance == null)
{
instance = this;
}
if(!destroyOnLoad)
{
DontDestroyOnLoad(this.gameObject);
}
SetupUtils.SetUpTextComponents(this.gameObject, false);
UpdateFontsForLanguage(this);
}
///
/// Enables and disables Unity standard Text components and TextMeshPro components based on whether TextMeshPro is enabled/installed.
///
private void OnValidate()
{
SetupUtils.SetUpTextComponents(this.gameObject, false);
}
///
/// If using the new input system, Init() sets up the input actions. This method also registers a method with the onLanguageChanged delegate so that the dialogue
/// display can update fonts of all child components whenever the language is changed.
///
protected virtual void Init()
{
EasyTalkGameState.Instance.onLanguageChanged += LanguageChanged;
}
///
/// Unregister the LanguageChanged method frm the onLanguageChanged delegate so that the EasyTalkGameState instance will no longer try to call this object.
///
private void OnDestroy()
{
EasyTalkGameState.Instance.onLanguageChanged -= LanguageChanged;
}
///
/// If continuation is currently permitted, this will call the dialogue controller's Continue() method to continue to the next part of the current converstaion
/// and disable continuation.
///
public virtual void Continue()
{
if (isContinueAllowed && dialogueController != null)
{
DisableContinue();
dialogueController.Continue();
}
}
///
/// Selects the next option in the option display (only applicable when options are being presented).
///
/// Returns true if an option was selected; false otherwise.
public abstract bool SelectNextOption();
///
/// Selects the previous option in the option display (only applicable when options are being presented).
///
/// Returns true if an option was selected; false otherwise.
public abstract bool SelectPreviousOption();
///
/// Chooses the selected option. This method should notify the Dialogue Controller of the option that was chosen using DialogueOption.OptionIndex.
///
public abstract void ChooseSelectedOption();
///
/// Selects the option which most closely corresponds to the specified direction.
///
/// The direction to select an option in.
/// Returns true if an option was selected; false otherwise.
public abstract bool SelectOptionInDirection(Vector2 direction);
///
/// This method tells the active dialogue controller to exit the dialogue immediately.
///
public virtual void ExitDialogue()
{
if (dialogueController != null)
{
dialogueController.ExitDialogue();
}
}
///
/// Set the active dialogue controller for the dialogue display to communicate with.
///
/// The dialogue controller to use.
public void SetActiveDialogueController(DialogueController dialogueController)
{
dialogueController.AddDialogueListener(this);
this.dialogueController = dialogueController;
}
///
/// This method is called when options are displayed and sets the IsCurrentlyInConversation flag to false.
///
/// The List of dialogue options being displayed.
public override void OnDisplayOptions(List options)
{
base.OnDisplayOptions(options);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnDisplayOptions(options);
}
IsCurrentlyInConversation = false;
}
///
/// This method is called when a line of dialogue is being displayed and sets the IsCurrentlyInConversation flag to true.
///
/// The line of dialogue being displayed.
public override void OnDisplayLine(ConversationLine line)
{
base.OnDisplayLine(line);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnDisplayLine(line);
}
IsCurrentlyInConversation = true;
}
///
/// This method is called when the dialogue exits playback and sets the IsCurrentlyInConversation flag to false;
///
/// The name of the exit point where the dialogue exited.
public override void OnDialogueExited(string exitName)
{
base.OnDialogueExited(exitName);
IsPlaying = false;
dialogueController.RemoveDialogueListener(this);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnDialogueExited(exitName);
}
IsCurrentlyInConversation = false;
}
///
public override void OnExitCompleted()
{
base.OnExitCompleted();
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnExitCompleted();
}
}
///
/// Returns the conversation display being used by this dialogue display.
///
/// The conversation display being used.
public abstract ConversationDisplay GetConversationDisplay();
///
/// Sets the conversation display used by the dialogue display.
///
/// The conversation display to use.
public abstract void SetConversationDisplay(ConversationDisplay convoDisplay);
///
/// Returns the option display being used by this dialogue display.
///
/// The option display being used.
public abstract OptionDisplay GetOptionDisplay();
///
/// Sets the option display being used by this dialogue display.
///
/// The option display to use.
public abstract void SetOptionDisplay(OptionDisplay optionDisplay);
///
/// Returns the continue display being used by this dialogue display.
///
/// The continue display being used.
public abstract ContinueDisplay GetContinueDisplay();
///
/// Sets the continue display used by this dialogue display.
///
/// The continue display to use.
public abstract void SetContinueDisplay(ContinueDisplay continueDisplaY);
///
/// Gets the TextInputDisplay used to retrieve text input from the player.
///
///
public abstract TextInputDisplay GetTextInputDisplay();
///
/// Sets the TextInputDisplay used to retrieve text input from the player.
///
/// The TextInputDisplay to use.
public abstract void SetTextInputDisplay(TextInputDisplay textInputDisplay);
///
/// Gets the dialogue display instance.
///
public static AbstractDialogueDisplay Instance
{
get { return instance; }
}
///
/// Returns the current DialogueController being used by this dialogue display, if there is one.
///
public DialogueController CurrentController
{
get { return dialogueController; }
}
///
/// Gets or sets whether the dialogue is currently allowing options to be selected by the player.
///
public bool IsOptionSelectionAllowed
{
get { return isOptionSelectionAllowed; }
set { isOptionSelectionAllowed = value; }
}
///
/// Gets or sets whether the player is allowed to immediately exit the dialogue by pressing a button.
///
public bool IsQuickExitAllowed
{
get { return this.allowQuickExit; }
set { this.allowQuickExit = value; }
}
///
/// Gets or sets whether continuation is allowed by the player by pressing a button.
///
public bool IsContinueAllowed
{
get { return this.isContinueAllowed; }
set { this.isContinueAllowed = value; }
}
///
/// Allows the current conversation to be continued.
///
public virtual void AllowContinue()
{
this.isContinueAllowed = true;
if (onContinueEnabled != null)
{
onContinueEnabled.Invoke();
}
}
///
/// Disallows continuation of the current conversation.
///
public virtual void DisableContinue()
{
this.isContinueAllowed = false;
if (onContinueDisabled != null)
{
onContinueDisabled.Invoke();
}
}
///
/// Allows option selection.
///
public void AllowOptionSelection()
{
this.isOptionSelectionAllowed = true;
if (onOptionSelectionEnabled != null)
{
onOptionSelectionEnabled.Invoke();
}
}
///
/// Disallows option selection.
///
public void DisableOptionSelection()
{
this.isOptionSelectionAllowed = false;
if (onOptionSelectionDisabled != null)
{
onOptionSelectionDisabled.Invoke();
}
}
///
/// This method is called when a continue occurs.
///
public override void OnContinue()
{
base.OnContinue();
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnContinue();
}
DisableContinue();
}
///
/// Sets the language used by the dialogue display.
///
/// The ISO-639 language code to use.
public void SetLanguage(string languageCode)
{
EasyTalkGameState.Instance.Language = languageCode;
}
///
/// This method is called whenever the language is changed by setting the language on the EasyTalkGameState.
///
/// The previous ISO-639 language code being used.
/// The new ISO-639 language code to use.
protected void LanguageChanged(string oldLanguage, string newLanguage)
{
UpdateFontsForLanguage(this);
TranslateComponents();
}
///
/// Translates the text set on the conversation display and option display based on the currently set language of EasyTalkGameState.Instance.
///
protected void TranslateComponents()
{
ConversationDisplay convoDisplay = GetConversationDisplay();
convoDisplay.TranslateText(this.dialogueController);
OptionDisplay optionDisplay = GetOptionDisplay();
optionDisplay.TranslateOptions(this.dialogueController);
}
///
/// Updates all of the fonts on child text components based on the language currently being used.
///
/// The component to update.
protected void UpdateFontsForLanguage(Component component)
{
LanguageFontOverride fontOverride = dialogueSettings.LanguageFontOverrides.GetOverrideForLanguage(EasyTalkGameState.Instance.Language);
//Update fonts for normal standard Unity text components.
ComponentFontManager.UpdateFonts(component, fontOverride);
#if TEXTMESHPRO_INSTALLED
//Update fonts for TextMeshPro text components
ComponentFontManager.UpdateTextMeshProFonts(component, fontOverride);
#endif
}
///
/// Adds the specified Dialogue Listener to the list of listeners.
///
/// The Dialogue Listener to add.
public void AddDialogueListener(DialogueListener dialogueListener)
{
if (!dialogueListeners.Contains(dialogueListener))
{
dialogueListeners.Add(dialogueListener);
}
}
///
/// Removes the specified Dialogue Listener from the list of listeners.
///
/// The Dialogue Listener to remove.
public void RemoveDialogueListener(DialogueListener dialogueListener)
{
if (dialogueListeners.Contains(dialogueListener))
{
dialogueListeners.Remove(dialogueListener);
}
}
///
/// Removes all Dialogue Listeners of the display.
///
public void RemoveDialogueListeners()
{
dialogueListeners.Clear();
}
///
public override void OnOptionChosen(DialogueOption option)
{
base.OnOptionChosen(option);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnOptionChosen(option);
}
}
///
public override void OnDialogueEntered(string entryPointName)
{
base.OnDialogueEntered(entryPointName);
IsPlaying = true;
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnDialogueEntered(entryPointName);
}
}
///
public override void OnStory(string storyText)
{
base.OnStory(storyText);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnStory(storyText);
}
}
///
public override void OnVariableUpdated(string variableName, object value)
{
base.OnVariableUpdated(variableName, value);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnVariableUpdated(variableName, value);
}
}
///
public override void OnCharacterChanged(string oldCharacterName, string newCharacterName)
{
base.OnCharacterChanged(oldCharacterName, newCharacterName);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnCharacterChanged(oldCharacterName, newCharacterName);
}
}
///
public override void OnAudioStarted(ConversationLine line)
{
base.OnAudioStarted(line);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnAudioStarted(line);
}
}
///
public override void OnAudioCompleted(ConversationLine line, bool forceStopped)
{
base.OnAudioCompleted(line, forceStopped);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnAudioCompleted(line, forceStopped);
}
}
///
public override void OnActivateKey(string key)
{
base.OnActivateKey(key);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnActivateKey(key);
}
}
///
public override void Wait(float timeInSeconds)
{
base.Wait(timeInSeconds);
foreach (DialogueListener listener in dialogueListeners)
{
listener.Wait(timeInSeconds);
}
}
///
public override void OnConversationEnding(ConversationLine line, Node nextNode)
{
base.OnConversationEnding(line, nextNode);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnConversationEnding(line, nextNode);
}
}
///
public override void OnNodeChanged(Node node)
{
base.OnNodeChanged(node);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnNodeChanged(node);
}
}
///
public override void OnPause(string signal)
{
base.OnPause(signal);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnPause(signal);
}
}
///
public override void OnExecuteAsyncNode(AsyncNode node)
{
base.OnExecuteAsyncNode(node);
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnExecuteAsyncNode(node);
}
}
///
public override void OnAppendText(string text)
{
base.OnAppendText(text);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnAppendText(text);
}
}
///
public override void OnWaitingForNodeEvaluation(Node asyncNode)
{
base.OnWaitingForNodeEvaluation(asyncNode);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnWaitingForNodeEvaluation(asyncNode);
}
}
///
public override void OnNodeEvaluationCompleted(Node asyncNode)
{
base.OnNodeEvaluationCompleted(asyncNode);
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnNodeEvaluationCompleted(asyncNode);
}
}
/*public override void OnAIPromptStarted()
{
base.OnAIPromptStarted();
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnAIPromptStarted();
}
}
public override void OnAIPromptFinished()
{
base.OnAIPromptFinished();
foreach(DialogueListener listener in dialogueListeners)
{
listener.OnAIPromptFinished();
}
}
public override void OnConversationClear()
{
base.OnConversationClear();
foreach (DialogueListener listener in dialogueListeners)
{
listener.OnConversationClear();
}
}*/
///
/// Gets or sets the EasyTalk Dialogue Settings used by the display.
///
public EasyTalkDialogueSettings DialogueSettings
{
get { return this.dialogueSettings; }
set { this.dialogueSettings = value; }
}
///
/// Internal class used to keep track of font and font size settings for standard text components used by a dialogue display.
///
public class TextFontSettings
{
public Font font;
public int minFontSize;
public int maxFontSize;
public TextFontSettings(Font font, int minFontSize, int maxFontSize)
{
this.font = font;
this.minFontSize = minFontSize;
this.maxFontSize = maxFontSize;
}
}
#if TEXTMESHPRO_INSTALLED
///
/// Internal class used to keep track of font and font size settings for TextMeshPro text components used by a dialogue display.
///
public class TMPTextFontSettings
{
public TMP_FontAsset font;
public float minFontSize;
public float maxFontSize;
public TMPTextFontSettings(TMP_FontAsset font, float minFontSize, float maxFontSize)
{
this.font = font;
this.minFontSize = minFontSize;
this.maxFontSize = maxFontSize;
}
}
#endif
}
}