// Copyright (c) Pixel Crushers. All rights reserved.
using System.Collections.Generic;
using UnityEngine;
namespace PixelCrushers.DialogueSystem
{
///
/// Manages response menus for StandardDialogueUI.
///
[System.Serializable]
public class StandardUIResponseMenuControls : AbstractUIResponseMenuControls
{
#region Public Fields
///
/// Assign this delegate if you want it to replace the default timeout handler.
///
public System.Action timeoutHandler = null;
public override AbstractUISubtitleControls subtitleReminderControls { get { return null; } } // Not used.
#endregion
#region Private Fields
protected List m_builtinPanels = new List();
protected StandardUIMenuPanel m_defaultPanel = null;
protected Dictionary m_actorPanelCache = new Dictionary();
protected Dictionary m_actorIdPanelCache = new Dictionary();
protected StandardUIMenuPanel m_currentPanel = null;
protected StandardUIMenuPanel m_forcedOverridePanel = null;
protected Sprite m_pcPortraitSprite = null;
protected string m_pcPortraitName = null;
protected bool useFirstResponseForPortrait = false;
public StandardUIMenuPanel defaultPanel
{
get { return m_defaultPanel; }
set { m_defaultPanel = value; }
}
public virtual bool allowDialogueActorCustomPanels { get; set; } = true;
#endregion
#region Initialization & Lookup
public void Initialize(StandardUIMenuPanel[] menuPanels, StandardUIMenuPanel defaultMenuPanel, bool useFirstResponseForMenuPortrait)
{
m_builtinPanels.Clear();
m_builtinPanels.AddRange(menuPanels);
m_defaultPanel = (defaultMenuPanel != null) ? defaultMenuPanel : (m_builtinPanels.Count > 0) ? m_builtinPanels[0] : null;
ClearCache();
if (timeoutHandler == null) timeoutHandler = DefaultTimeoutHandler;
useFirstResponseForPortrait = useFirstResponseForMenuPortrait;
}
public void ClearCache()
{
m_actorPanelCache.Clear();
m_actorIdPanelCache.Clear();
}
///
/// Changes a dialogue actor's menu panel for the current conversation.
///
public virtual void SetActorMenuPanelNumber(DialogueActor dialogueActor, MenuPanelNumber menuPanelNumber)
{
if (dialogueActor == null) return;
OverrideActorMenuPanel(dialogueActor.transform, menuPanelNumber, dialogueActor.standardDialogueUISettings.customMenuPanel);
}
///
/// Forces menus to use a specific panel regardless of any other default or override settings.
///
public void ForceOverrideMenuPanel(StandardUIMenuPanel panel)
{
m_forcedOverridePanel = panel;
}
///
/// For speakers who do not have DialogueActor components, this method overrides the
/// actor's default panel.
///
public void OverrideActorMenuPanel(Transform actorTransform, MenuPanelNumber menuPanelNumber, StandardUIMenuPanel customPanel)
{
if (actorTransform == null) return;
m_actorPanelCache[actorTransform] = GetPanelFromNumber(menuPanelNumber, customPanel);
}
///
/// For speakers who do not have a GameObject, this method overrides the actor's default panel.
///
public void OverrideActorMenuPanel(Actor actor, MenuPanelNumber menuPanelNumber, StandardUIMenuPanel customPanel)
{
if (actor == null) return;
m_actorIdPanelCache[actor.id] = GetPanelFromNumber(menuPanelNumber, customPanel);
}
protected Transform GetActorTransformFromID(int actorID)
{
var actor = DialogueManager.masterDatabase.GetActor(actorID);
if (actor != null)
{
var actorTransform = CharacterInfo.GetRegisteredActorTransform(actor.Name);
if (actorTransform == null)
{
var actorGO = GameObject.Find(actor.Name);
if (actorGO != null) actorTransform = actorGO.transform;
}
if (actorTransform != null) return actorTransform;
}
return DialogueManager.currentActor;
}
public virtual StandardUIMenuPanel GetPanel(Subtitle lastSubtitle, Response[] responses)
{
// Check if we have a forced override panel:
if (m_forcedOverridePanel != null) return m_forcedOverridePanel;
// Find player's transform & DialogueActor:
// [2021-04-20]: Prioritize responses[0] panel only if useFirstResponseForPortrait is true:
var playerTransform = (lastSubtitle != null && lastSubtitle.speakerInfo.isPlayer) ? lastSubtitle.speakerInfo.transform : null;
if (playerTransform == null)
{
if (useFirstResponseForPortrait)
{
playerTransform =
(responses != null && responses.Length > 0) ? GetActorTransformFromID(responses[0].destinationEntry.ActorID)
: (lastSubtitle != null && lastSubtitle.listenerInfo.isPlayer) ? lastSubtitle.listenerInfo.transform
: DialogueManager.currentActor;
}
else
{
playerTransform =
(lastSubtitle != null && lastSubtitle.listenerInfo.isPlayer) ? lastSubtitle.listenerInfo.transform
: (responses != null && responses.Length > 0) ? GetActorTransformFromID(responses[0].destinationEntry.ActorID)
: DialogueManager.currentActor;
}
}
if (playerTransform == null) playerTransform = DialogueManager.currentActor;
var playerDialogueActor = DialogueActor.GetDialogueActorComponent(playerTransform);
// Check NPC for non-default menu panel:
var playerUsesDefaultMenuPanel = playerDialogueActor != null && playerDialogueActor.standardDialogueUISettings.menuPanelNumber == MenuPanelNumber.Default;
var npcTransform = (lastSubtitle != null && lastSubtitle.speakerInfo.isNPC) ? lastSubtitle.speakerInfo.transform
: (lastSubtitle != null) ? lastSubtitle.listenerInfo.transform : DialogueManager.currentConversant;
if (npcTransform == null) npcTransform = DialogueManager.currentConversant;
if (playerUsesDefaultMenuPanel && npcTransform != null && m_actorPanelCache.ContainsKey(npcTransform))
{
// We've already cached a menu panel to use when responding to this NPC, so return it:
return m_actorPanelCache[npcTransform];
}
var npcDialogueActor = DialogueActor.GetDialogueActorComponent(npcTransform);
if (npcDialogueActor != null &&
(npcDialogueActor.standardDialogueUISettings.useMenuPanelFor == DialogueActor.UseMenuPanelFor.MeAndResponsesToMe ||
(npcDialogueActor.standardDialogueUISettings.menuPanelNumber != MenuPanelNumber.Default &&
playerUsesDefaultMenuPanel)))
{
// NPC's DialogueActor specifies a menu panel to use when responding to it, so cache and return it:
var npcMenuPanel = GetDialogueActorPanel(npcDialogueActor);
if (npcMenuPanel != null)
{
m_actorPanelCache[npcTransform] = npcMenuPanel;
return npcMenuPanel;
}
}
if (playerTransform != null)
{
// If NPC doesn't specify a menu panel, check for an override by player's transform:
if (m_actorPanelCache.ContainsKey(playerTransform))
{
var actorTransformPanel = m_actorPanelCache[playerTransform];
if (actorTransformPanel != m_defaultPanel) return actorTransformPanel;
}
}
// Check for an override by player actor ID:
var playerID = (lastSubtitle != null && lastSubtitle.speakerInfo.isPlayer) ? lastSubtitle.speakerInfo.id
: (responses != null && responses.Length > 0) ? responses[0].destinationEntry.ActorID : -1;
if (m_actorIdPanelCache.ContainsKey(playerID)) return m_actorIdPanelCache[playerID];
// Otherwise use player's menu panel:
var panel = GetDialogueActorPanel(playerDialogueActor);
if (panel == null) panel = m_defaultPanel;
if (playerTransform != null) m_actorPanelCache[playerTransform] = panel;
return panel;
}
protected StandardUIMenuPanel GetDialogueActorPanel(DialogueActor dialogueActor)
{
if (dialogueActor == null) return null;
return GetPanelFromNumber(dialogueActor.standardDialogueUISettings.menuPanelNumber, dialogueActor.standardDialogueUISettings.customMenuPanel);
}
protected StandardUIMenuPanel GetPanelFromNumber(MenuPanelNumber menuPanelNumber, StandardUIMenuPanel customPanel)
{
switch (menuPanelNumber)
{
case MenuPanelNumber.Default:
return m_defaultPanel;
case MenuPanelNumber.Custom:
if (!allowDialogueActorCustomPanels) return null;
return customPanel;
default:
var index = PanelNumberUtility.GetMenuPanelIndex(menuPanelNumber);
return (0 <= index && index < m_builtinPanels.Count) ? m_builtinPanels[index] : null;
}
}
#endregion
#region Portraits
///
/// Sets the PC portrait name and sprite to use in the response menu.
///
/// Portrait sprite.
/// Portrait name.
public override void SetPCPortrait(Sprite portraitSprite, string portraitName)
{
m_pcPortraitSprite = portraitSprite;
m_pcPortraitName = portraitName;
}
///
/// Sets the portrait sprite to use in the response menu if the named actor is the player.
/// This is used to immediately update the GUI control if the SetPortrait() sequencer
/// command changes the portrait sprite.
///
/// Actor name in database.
/// Portrait sprite.
public override void SetActorPortraitSprite(string actorName, Sprite portraitSprite)
{
if (string.Equals(actorName, m_pcPortraitName))
{
var actorPortraitSprite = AbstractDialogueUI.GetValidPortraitSprite(actorName, portraitSprite);
m_pcPortraitSprite = portraitSprite;
if (m_currentPanel != null && m_currentPanel.pcImage != null && DialogueManager.masterDatabase.IsPlayer(actorName))
{
m_currentPanel.pcImage.sprite = actorPortraitSprite;
}
}
}
#endregion
#region Show & Hide Responses
protected override void ClearResponseButtons() { } // Unused. Handled by StandardUIMenuPanel.
protected override void SetResponseButtons(Response[] responses, Transform target) { } // Unused. Handled by StandardUIMenuPanel.
public override void SetActive(bool value)
{
// Only hide. Show is handled by StandardUIMenuPanel.
if (value == false && m_currentPanel != null) m_currentPanel.HideResponses();
}
///
/// Shows a response menu.
///
/// The last subtitle shown. Used to determine which menu panel to use.
/// Responses to show in menu panel.
/// Send OnClick events to this GameObject (the dialogue UI).
public override void ShowResponses(Subtitle lastSubtitle, Response[] responses, Transform target)
{
var panel = GetPanel(lastSubtitle, responses);
if (panel == null)
{
if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: Can't find menu panel.");
}
else
{
m_currentPanel = panel;
if (useFirstResponseForPortrait && responses.Length > 0)
{
var menuCharacterInfo = DialogueManager.conversationModel.GetCharacterInfo(responses[0].destinationEntry.ActorID);
if (menuCharacterInfo != null)
{
m_pcPortraitName = menuCharacterInfo.Name;
m_pcPortraitSprite = menuCharacterInfo.portrait;
}
}
panel.SetPCPortrait(m_pcPortraitSprite, m_pcPortraitName);
panel.ShowResponses(lastSubtitle, responses, target);
}
}
///
/// Makes the current menu panel's buttons non-clickable.
/// Typically called by the dialogue UI as soon as a button has been
/// clicked to make sure the player can't click another one while the
/// menu is playing its hide animation.
///
public virtual void MakeButtonsNonclickable()
{
if (m_currentPanel != null)
{
m_currentPanel.MakeButtonsNonclickable();
}
}
///
/// Close all panels.
///
public void Close()
{
for (int i = 0; i < m_builtinPanels.Count; i++)
{
if (m_builtinPanels[i] != null) m_builtinPanels[i].Close();
}
if (m_defaultPanel != null && !m_builtinPanels.Contains(m_defaultPanel)) m_defaultPanel.Close();
foreach (var kvp in m_actorPanelCache)
{
var panel = kvp.Value;
if (panel != null && !m_builtinPanels.Contains(panel)) panel.Close();
}
if (m_actorIdPanelCache.Count > 0)
{
var cachedPanels = new List(m_actorIdPanelCache.Values);
foreach (var kvp in m_actorIdPanelCache)
{
var panel = kvp.Value;
if (panel != null && !m_builtinPanels.Contains(panel) && !cachedPanels.Contains(panel)) panel.Close();
}
}
//--- No longer close cache when closing menus because SetDialoguePanel may close them: ClearCache();
}
public bool AreAnyPanelsClosing()
{
for (int i = 0; i < m_builtinPanels.Count; i++)
{
if (m_builtinPanels[i] != null && m_builtinPanels[i].panelState == UIPanel.PanelState.Closing) return true;
}
if (m_defaultPanel != null && !m_builtinPanels.Contains(m_defaultPanel) && m_defaultPanel.panelState == UIPanel.PanelState.Closing) return true;
foreach (var kvp in m_actorPanelCache)
{
var panel = kvp.Value;
if (panel != null && !m_builtinPanels.Contains(panel) && panel.panelState == UIPanel.PanelState.Closing) return true;
}
if (m_actorIdPanelCache.Count > 0)
{
var cachedPanels = new List(m_actorIdPanelCache.Values);
foreach (var kvp in m_actorIdPanelCache)
{
var panel = kvp.Value;
if (panel != null && !m_builtinPanels.Contains(panel) && !cachedPanels.Contains(panel) && panel.panelState == UIPanel.PanelState.Closing) return true;
}
}
return false;
}
///
/// Starts the timer.
///
/// Timeout duration in seconds.
public override void StartTimer(float timeout)
{
if (m_currentPanel != null) m_currentPanel.StartTimer(timeout, timeoutHandler);
}
public void DefaultTimeoutHandler()
{
DialogueManager.instance.SendMessage(DialogueSystemMessages.OnConversationTimeout);
}
#endregion
}
}