using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Convai.Scripts.Utils { [Serializable] public class Character { [Header("Character settings")] [Tooltip("Convai NPC Game Object")] public ConvaiNPC characterGameObject; [ReadOnly] [Tooltip("Display name of the NPC")] public string characterName = "Character"; [ColorUsage(true)] [Tooltip("Color of the NPC text. Alpha value will be ignored.")] [SerializeField] private Color characterTextColor = Color.red; public Color CharacterTextColor { get => characterTextColor; set => characterTextColor = value; } } [AddComponentMenu("Convai/Chat UI Handler")] [DisallowMultipleComponent] [HelpURL( "https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/scripts-overview/convaichatuihandler.cs")] [DefaultExecutionOrder(-100)] public class ConvaiChatUIHandler : MonoBehaviour { /// /// Enum to represent different UI types. /// public enum UIType { ChatBox, QuestionAnswer, Subtitle } [Header("UI Prefabs")] [Tooltip("Prefab for the chat box UI.")] public GameObject chatBoxPrefab; [Tooltip("Prefab for the subtitle UI.")] public GameObject subtitlePrefab; [Tooltip("Prefab for the question-answer UI.")] public GameObject questionAnswerPrefab; [Header("Character List")] [Tooltip("List of characters.")] public List characters = new(); [Header("Player settings")] [Tooltip("Display name of the player.")] public string playerName = "Player"; [ColorUsage(true)] [Tooltip("Color of the player's text. Alpha value will be ignored.")] public Color playerTextColor = Color.white; private IChatUI _currentUIImplementation; public static ConvaiChatUIHandler Instance { get; private set; } public Dictionary GetUIAppearances { get; } = new(); private void Awake() { if (Instance != null) { // Log a warning and destroy the duplicate instance Debug.Log(" There's More Than One ConvaiChatUIHandler " + transform + " - " + Instance); Destroy(gameObject); return; } // Set the singleton instance Instance = this; ValidateUIPrefabs(); InitializeUIStrategies(); } // Subscribe to events when this component is enabled. private void OnEnable() { // Subscribe to the event when saved data is loaded. UISaveLoadSystem.Instance.OnLoad += UISaveLoadSystem_OnLoad; // Subscribe to the event when data is saved. UISaveLoadSystem.Instance.OnSave += UISaveLoadSystem_OnSave; } // Unsubscribe from events when this component is disabled. private void OnDisable() { // Unsubscribe from the event when saved data is loaded. UISaveLoadSystem.Instance.OnLoad -= UISaveLoadSystem_OnLoad; // Unsubscribe from the event when data is saved. UISaveLoadSystem.Instance.OnSave -= UISaveLoadSystem_OnSave; } private void OnDestroy() { SaveUIType(); } private void OnValidate() { UpdateCharacterList(); RemoveDuplicateCharacters(); } /// /// Updates the character list by synchronizing names between Convai Transcript UI Character list and NPC /// characterName, removing null characters, and adding missing characters. /// public void UpdateCharacterList() { // Synchronize names between Convai Transcript UI Character list and NPC characterName for (int i = 0; i < characters.Count; i++) { Character character = characters[i]; // If the character's game object is missing, remove it from the list if (character.characterGameObject == null) characters.Remove(character); else // Update the character's name using the game object's characterName character.characterName = character.characterGameObject.characterName; } // Remove null characters characters = characters.Where(c => c.characterGameObject != null).ToList(); // Add missing characters foreach (ConvaiNPC convaiNpc in FindObjectsOfType()) { if (characters.Any(c => c.characterGameObject == convaiNpc)) continue; characters.Add(new Character { characterGameObject = convaiNpc, characterName = convaiNpc.characterName }); } } /// /// Removes duplicate characters from the character list based on their GameObject. /// private void RemoveDuplicateCharacters() { characters = characters .GroupBy(c => c.characterGameObject) .Select(g => g.First()) .ToList(); } /// /// Event handler when saved data is loaded. /// private void UISaveLoadSystem_OnLoad() { _currentUIImplementation = GetChatUIByUIType(UISaveLoadSystem.Instance.UIType); SetUIType(UISaveLoadSystem.Instance.UIType); _currentUIImplementation.ActivateUI(); } /// /// Event handler when data is saved. /// private void UISaveLoadSystem_OnSave() { SaveUIType(); } /// /// Initializes the UI with the given prefab and UI type. /// private void InitializeUIStrategies() { InitializeUI(chatBoxPrefab, UIType.ChatBox); InitializeUI(questionAnswerPrefab, UIType.QuestionAnswer); InitializeUI(subtitlePrefab, UIType.Subtitle); } private void InitializeUI(GameObject uiPrefab, UIType uiType) { try { IChatUI uiComponent = uiPrefab.GetComponent(); if (uiComponent == null) { Debug.LogError( $"The provided prefab for {uiType} does not have a component that implements IChatUI."); return; } uiComponent.Initialize(uiPrefab); GetUIAppearances[uiType] = uiComponent; } catch (Exception ex) { Debug.LogError($"An error occurred while initializing the UI: {ex.Message}"); } } /// /// Sends character text to the current UI. /// /// The character's name. /// The text to send. public void SendCharacterText(string charName, string text) { Character character = characters.Find(c => c.characterName == charName); if (character == null) { Debug.LogError($"No character found named {charName}"); return; } if (!ConvaiNPCManager.Instance.CheckForNPCToNPCConversation(character.characterGameObject)) _currentUIImplementation?.SendCharacterText(charName, text, character.CharacterTextColor); else Debug.LogWarning($"{charName} is in a NPC2NPC conversation."); } /// /// Sends player text to the current UI. /// /// The text to send. public void SendPlayerText(string text) { _currentUIImplementation?.SendPlayerText(playerName, text, playerTextColor); } /// /// Validates that all UI prefabs are assigned. /// private void ValidateUIPrefabs() { try { if (chatBoxPrefab == null || subtitlePrefab == null || questionAnswerPrefab == null) throw new InvalidOperationException("All UI prefabs must be assigned in the inspector."); } catch (InvalidOperationException ex) { Debug.LogError($"An error occurred while validating UI prefabs: {ex.Message}"); } } /// /// Sets the current UI type and fades between UIs. /// /// The new UI type to set. public void SetUIType(UIType newUIType) { if (!GetUIAppearances.ContainsKey(newUIType)) { Debug.LogError($"The UI type {newUIType} does not exist in the GetUIAppearances dictionary."); return; } _currentUIImplementation = GetUIAppearances[newUIType]; } private void SaveUIType() { foreach (KeyValuePair strategy in GetUIAppearances.Where(strategy => strategy.Value == _currentUIImplementation)) { UISaveLoadSystem.Instance.UIType = strategy.Key; break; } } public IChatUI GetChatUIByUIType(UIType uiType) { return GetUIAppearances[uiType]; } /// /// Gets the current UI implementation. /// /// The current IChatUI implementation. public IChatUI GetCurrentUI() { return _currentUIImplementation; } public bool HasCharacter(string convaiNPCCharacterName) { return characters.Any(character => character.characterName == convaiNPCCharacterName); } public void AddCharacter(Character newCharacter) { characters.Add(newCharacter); } } }