// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace PixelCrushers.DialogueSystem
{
///
/// Specifies the orderings that can be used for a list of barks.
///
public enum BarkOrder
{
///
/// Play barks in random order, avoiding sequential repeats if possible.
///
Random,
///
/// Play barks in sequential order.
///
Sequential,
///
/// Stop evaluating dialogue entries after finding the first valid entry.
///
FirstValid
}
///
/// Keeps track of a character's current bark. This allows the BarkController to iterate
/// through a list of barks.
///
public class BarkHistory
{
public BarkOrder order;
public int index = 0;
public List entries = null;
public BarkHistory(BarkOrder order)
{
this.order = order;
this.index = 0;
this.entries = null;
}
public int GetNextIndex(int numEntries)
{
if (order == BarkOrder.Random)
{
if (numEntries == 0) return 0;
var isNewList = entries == null;
if (entries == null) entries = new List();
// If the entries have changed or we've reached the end of the shuffled list, remake the list:
if (entries.Count != numEntries || index >= entries.Count)
{
// Remember the last entry we used:
var lastEntry = (entries.Count > 0) ? entries[entries.Count - 1] : 0;
// Reshuffle the list:
entries.Clear();
for (int i = 0; i < numEntries; i++)
{
entries.Add(i);
}
entries.Shuffle();
if (entries[0] == lastEntry && !isNewList)
{
// If the first entry of new list is the same as the last entry used, move it to the end:
entries.RemoveAt(0);
entries.Add(lastEntry);
}
index = 0;
}
return (0 <= index && index < entries.Count) ? entries[index++] : 0;
//---Was: return entries[Random.Range(0, numEntries);
}
else
{
int result = (index % numEntries);
index = ((index + 1) % numEntries);
return result;
}
}
///
/// Resets the current index to the beginning.
///
public void Reset()
{
index = 0;
}
}
///
/// Specifies how to handle bark subtitles:
///
/// - SameAsDialogueManager: Use the same setting as the dialogue UI currently assigned to the
/// DialogueManager.
/// - Show: Always show using the bark UI on the character. (See IBarkUI)
/// - Hide: Never show.
///
public enum BarkSubtitleSetting
{
SameAsDialogueManager,
Show,
Hide
}
///
/// BarkController is a static utility class provides a method to make characters bark.
///
public static class BarkController
{
private static Dictionary currentBarkPriority = new Dictionary();
///
/// Gets the last sequencer created by BarkController.Bark().
///
/// The last sequencer.
public static Sequencer LastSequencer { get; private set; }
#if UNITY_2019_3_OR_NEWER && UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void InitStaticVariables()
{
currentBarkPriority = new Dictionary();
}
#endif
static BarkController()
{
LastSequencer = null;
}
private static int GetSpeakerCurrentBarkPriority(Transform speaker)
{
return currentBarkPriority.ContainsKey(speaker) ? currentBarkPriority[speaker] : 0;
}
private static void SetSpeakerCurrentBarkPriority(Transform speaker, int priority)
{
if (currentBarkPriority.ContainsKey(speaker))
{
currentBarkPriority[speaker] = priority;
}
else
{
currentBarkPriority.Add(speaker, priority);
}
}
private static int GetEntryBarkPriority(DialogueEntry entry)
{
return (entry == null) ? 0 : Field.LookupInt(entry.fields, DialogueSystemFields.Priority);
}
///
/// Attempts to make a character bark. This is a coroutine; you must start it using
/// StartCoroutine() or Unity will hang. Shows a line from the named conversation, plays
/// the sequence, and sends OnBarkStart/OnBarkEnd messages to the participants.
///
///
/// Title of conversation to pull bark lines from.
///
///
/// Speaker performing the bark.
///
///
/// Listener that the bark is directed to; may be null.
///
///
/// Bark history used to keep track of the most recent bark so this method can iterate
/// through them in a specified order.
///
///
/// The dialogue database to use. If null, uses DialogueManager.MasterDatabase.
///
public static IEnumerator Bark(string conversationTitle, Transform speaker, Transform listener, BarkHistory barkHistory, DialogueDatabase database = null, bool stopAtFirstValid = false)
{
if (CheckDontBarkDuringConversation()) yield break;
bool barked = false;
if (string.IsNullOrEmpty(conversationTitle) && DialogueDebug.logWarnings) Debug.Log(string.Format("{0}: Bark (speaker={1}, listener={2}): conversation title is blank", new System.Object[] { DialogueDebug.Prefix, speaker, listener }), speaker);
if (speaker == null) speaker = DialogueManager.instance.FindActorTransformFromConversation(conversationTitle, "Actor");
if ((speaker == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' speaker is null", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }));
if (string.IsNullOrEmpty(conversationTitle) || (speaker == null)) yield break;
IBarkUI barkUI = DialogueActor.GetBarkUI(speaker); //speaker.GetComponentInChildren(typeof(IBarkUI)) as IBarkUI;
if ((barkUI == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' speaker has no bark UI", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
var firstValid = stopAtFirstValid || ((barkHistory == null) ? false : barkHistory.order == (BarkOrder.FirstValid));
ConversationModel conversationModel = new ConversationModel(database ?? DialogueManager.masterDatabase, conversationTitle, speaker, listener, DialogueManager.allowLuaExceptions,
DialogueManager.isDialogueEntryValid, -1, firstValid, false, DialogueManager.useLinearGroupMode);
ConversationState firstState = conversationModel.firstState;
if ((firstState == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' has no START entry", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
else if (DialogueManager.useLinearGroupMode) conversationModel.UpdateResponses(firstState); // In linear mode, conversation model doesn't check responses on creation since they're evaluated when the subtitle/bark ends.
if ((firstState != null) && !firstState.hasAnyResponses && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' has no valid bark at this time", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
if ((firstState != null) && firstState.hasAnyResponses)
{
try
{
Response[] responses = firstState.hasNPCResponse ? firstState.npcResponses : firstState.pcResponses;
int index = (barkHistory ?? new BarkHistory(BarkOrder.Random)).GetNextIndex(responses.Length);
DialogueEntry barkEntry = responses[index].destinationEntry;
if ((barkEntry == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' bark entry is null", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
if (barkEntry != null)
{
var priority = GetEntryBarkPriority(barkEntry);
if (priority < GetSpeakerCurrentBarkPriority(speaker))
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' currently barking a higher priority bark", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
yield break;
}
SetSpeakerCurrentBarkPriority(speaker, priority);
barked = true;
InformParticipants(DialogueSystemMessages.OnBarkStart, speaker, listener);
ConversationState barkState = conversationModel.GetState(barkEntry, false);
if (barkState == null)
{
if (DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' can't find a valid dialogue entry", new System.Object[] { DialogueDebug.Prefix, speaker, listener, conversationTitle }), speaker);
yield break;
}
//--- Was: (swapping speaker & listener no longer appropriate)
//if (firstState.hasNPCResponse)
//{
// CharacterInfo tempInfo = barkState.subtitle.speakerInfo;
// barkState.subtitle.speakerInfo = barkState.subtitle.listenerInfo;
// barkState.subtitle.listenerInfo = tempInfo;
//}
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}'", new System.Object[] { DialogueDebug.Prefix, speaker, listener, barkState.subtitle.formattedText.text }), speaker);
InformParticipantsLine(DialogueSystemMessages.OnBarkLine, speaker, barkState.subtitle);
// Show the bark subtitle:
if (((barkUI == null) || !(barkUI as MonoBehaviour).enabled) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' bark UI is null or disabled", new System.Object[] { DialogueDebug.Prefix, speaker, listener, barkState.subtitle.formattedText.text }), speaker);
if ((barkUI != null) && (barkUI as MonoBehaviour).enabled)
{
CheckCancelPreviousBarkSequence(speaker, barkUI);
barkUI.Bark(barkState.subtitle);
}
// Start the sequence:
var sequencer = PlayBarkSequence(barkState.subtitle, speaker, listener);
LastSequencer = sequencer;
// Wait until the sequence and subtitle are done:
while (((sequencer != null) && sequencer.isPlaying) || ((barkUI != null) && barkUI.isPlaying))
{
yield return null;
}
if (sequencer != null) GameObject.Destroy(sequencer);
}
}
finally
{
if (barked)
{
InformParticipants(DialogueSystemMessages.OnBarkEnd, speaker, listener);
SetSpeakerCurrentBarkPriority(speaker, 0);
}
}
}
}
private static void CheckCancelPreviousBarkSequence(Transform speaker, IBarkUI barkUI)
{
if (barkUI.isPlaying && (barkUI is StandardBarkUI))
{
var standardBarkUI = barkUI as StandardBarkUI;
if (standardBarkUI.waitUntilSequenceEnds && standardBarkUI.cancelWaitUntilSequenceEndsIfReplacingBark)
{
standardBarkUI.Hide();
}
}
}
private static Sequencer PlayBarkSequence(Subtitle subtitle, Transform speaker, Transform listener)
{
return PlayBarkSequence(subtitle.formattedText.text, subtitle.sequence, subtitle.entrytag, speaker, listener);
}
private static Sequencer PlayBarkSequence(string barkText, string sequence, string entrytag, Transform speaker, Transform listener)
{
if (string.IsNullOrEmpty(sequence))
{
sequence = DialogueManager.displaySettings.barkSettings.defaultBarkSequence;
}
if (!string.IsNullOrEmpty(sequence))
{
sequence = Sequencer.ReplaceShortcuts(sequence);
if (sequence.Contains(SequencerKeywords.End))
{
var text = barkText;
int numCharacters = string.IsNullOrEmpty(text) ? 0 : Tools.StripRPGMakerCodes(Tools.StripTextMeshProTags(text)).Length;
var endDuration = Mathf.Max(DialogueManager.displaySettings.GetMinSubtitleSeconds(), numCharacters / Mathf.Max(1, DialogueManager.displaySettings.GetSubtitleCharsPerSecond()));
sequence = sequence.Replace(SequencerKeywords.End, endDuration.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
return DialogueManager.PlaySequence(sequence, speaker, listener, false, false, entrytag);
}
else
{
return null;
}
}
///
/// Attempts to make a character bark. This is a coroutine; you must start it using
/// StartCoroutine() or Unity will hang. Shows a specific subtitle and plays the sequence,
/// but does not send OnBarkStart/OnBarkEnd messages to the participants. This optimized version
///
///
/// Subtitle to bark.
///
///
/// Speaker performing the bark.
///
///
/// Listener that the bark is directed to; may be null.
///
///
/// The bark UI to bark with.
///
public static IEnumerator Bark(Subtitle subtitle, Transform speaker, Transform listener, IBarkUI barkUI)
{
if (CheckDontBarkDuringConversation()) yield break;
if ((subtitle == null) || (subtitle.speakerInfo == null)) yield break;
var priority = GetEntryBarkPriority(subtitle.dialogueEntry);
if (priority < GetSpeakerCurrentBarkPriority(speaker))
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' currently barking a higher priority bark", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
yield break;
}
SetSpeakerCurrentBarkPriority(speaker, priority);
InformParticipants(DialogueSystemMessages.OnBarkStart, speaker, listener);
InformParticipantsLine(DialogueSystemMessages.OnBarkLine, speaker, subtitle);
if ((barkUI == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' speaker has no bark UI", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
if (((barkUI == null) || !(barkUI as MonoBehaviour).enabled) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' bark UI is null or disabled", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
CheckCancelPreviousBarkSequence(speaker, barkUI);
// Show the bark subtitle:
if ((barkUI != null) && (barkUI as MonoBehaviour).enabled)
{
barkUI.Bark(subtitle);
}
// Start the sequence:
Sequencer sequencer = PlayBarkSequence(subtitle, speaker, listener);
LastSequencer = sequencer;
// Wait until the sequence and subtitle are done:
while (((sequencer != null) && sequencer.isPlaying) || ((barkUI != null) && barkUI.isPlaying))
{
yield return null;
}
if (sequencer != null) GameObject.Destroy(sequencer);
InformParticipants(DialogueSystemMessages.OnBarkEnd, speaker, listener);
SetSpeakerCurrentBarkPriority(speaker, 0);
}
///
/// Attempts to make a character bark. This is a coroutine; you must start it using
/// StartCoroutine() or Unity will hang. Shows a specific subtitle and plays the sequence,
/// but does not send OnBarkStart/OnBarkEnd messages to the participants.
///
///
/// Subtitle to bark.
///
///
/// If `true`, don't play the sequence associated with the subtitle.
///
public static IEnumerator Bark(Subtitle subtitle, bool skipSequence = false)
{
if (CheckDontBarkDuringConversation()) yield break;
if ((subtitle == null) || (subtitle.speakerInfo == null)) yield break;
Transform speaker = subtitle.speakerInfo.transform;
Transform listener = (subtitle.listenerInfo != null) ? subtitle.listenerInfo.transform : null;
var priority = GetEntryBarkPriority(subtitle.dialogueEntry);
if (priority < GetSpeakerCurrentBarkPriority(speaker))
{
if (DialogueDebug.logInfo) Debug.Log(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' currently barking a higher priority bark", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
yield break;
}
SetSpeakerCurrentBarkPriority(speaker, priority);
InformParticipants(DialogueSystemMessages.OnBarkStart, speaker, listener);
InformParticipantsLine(DialogueSystemMessages.OnBarkLine, speaker, subtitle);
IBarkUI barkUI = DialogueActor.GetBarkUI(speaker); // speaker.GetComponentInChildren(typeof(IBarkUI)) as IBarkUI;
if ((barkUI == null) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' speaker has no bark UI", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
if (((barkUI == null) || !(barkUI as MonoBehaviour).enabled) && DialogueDebug.logWarnings) Debug.LogWarning(string.Format("{0}: Bark (speaker={1}, listener={2}): '{3}' bark UI is null or disabled", new System.Object[] { DialogueDebug.Prefix, speaker, listener, subtitle.formattedText.text }), speaker);
CheckCancelPreviousBarkSequence(speaker, barkUI);
// Show the bark subtitle:
if ((barkUI != null) && (barkUI as MonoBehaviour).enabled)
{
barkUI.Bark(subtitle);
}
// Start the sequence:
Sequencer sequencer = null;
if (!skipSequence)
{
sequencer = PlayBarkSequence(subtitle, speaker, listener);
}
LastSequencer = sequencer;
// Wait until the sequence and subtitle are done:
while (((sequencer != null) && sequencer.isPlaying) || ((barkUI != null) && barkUI.isPlaying))
{
yield return null;
}
if (sequencer != null) GameObject.Destroy(sequencer);
InformParticipants(DialogueSystemMessages.OnBarkEnd, speaker, listener);
SetSpeakerCurrentBarkPriority(speaker, 0);
}
private static bool CheckDontBarkDuringConversation()
{
return DialogueManager.isConversationActive && DialogueManager.displaySettings != null &&
DialogueManager.displaySettings.barkSettings != null && !DialogueManager.displaySettings.barkSettings.allowBarksDuringConversations;
}
///
/// Broadcasts a message to the participants in a bark. Used to send the OnBarkStart and
/// OnBarkEnd messages to the speaker and listener. Also sends to the Dialogue Manager.
///
///
/// Message (i.e., OnBarkStart or OnBarkEnd).
///
///
/// Speaker.
///
///
/// Listener.
///
private static void InformParticipants(string message, Transform speaker, Transform listener)
{
if (speaker != null)
{
speaker.BroadcastMessage(message, speaker, SendMessageOptions.DontRequireReceiver);
if ((listener != null) && (listener != speaker))
{
listener.BroadcastMessage(message, speaker, SendMessageOptions.DontRequireReceiver);
}
}
var dialogueManagerTransform = DialogueManager.instance.transform;
if (dialogueManagerTransform != speaker && dialogueManagerTransform != listener)
{
var actor = (speaker != null) ? speaker : ((listener != null) ? listener : dialogueManagerTransform);
DialogueManager.instance.BroadcastMessage(message, actor, SendMessageOptions.DontRequireReceiver);
}
}
///
/// Broadcasts a message to the participants in a bark. Used to send the OnBarkStart and
/// OnBarkEnd messages to the speaker and listener. Also sent to Dialogue Manager.
///
///
/// Message (i.e., OnBarkStart or OnBarkEnd).
///
///
/// Speaker.
///
///
/// Listener.
///
private static void InformParticipantsLine(string message, Transform speaker, Subtitle subtitle)
{
if (speaker != null)
{
speaker.BroadcastMessage(message, subtitle, SendMessageOptions.DontRequireReceiver);
}
var dialogueManagerTransform = DialogueManager.instance.transform;
if (dialogueManagerTransform != speaker)
{
DialogueManager.instance.BroadcastMessage(message, subtitle, SendMessageOptions.DontRequireReceiver);
}
}
}
}