using System; using System.Collections.Generic; using System.Linq; using Convai.Scripts.Runtime.Addons; using Convai.Scripts.Runtime.Attributes; using Convai.Scripts.Runtime.Core; using Convai.Scripts.Runtime.LoggerSystem; using Convai.Scripts.Runtime.PlayerStats; using UnityEngine; namespace Convai.Scripts.Runtime.UI { [AddComponentMenu("Convai/Chat UI Handler")] [DisallowMultipleComponent] [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(); [ColorUsage(true)] [Tooltip("Color of the player's text. Alpha value will be ignored.")] public Color playerTextColor = Color.white; private ConvaiPlayerDataSO _convaiPlayerData; private IChatUI _currentUIImplementation; private bool _hasPlayerData; 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 ConvaiLogger.DebugLog(" There's More Than One ConvaiChatUIHandler " + transform + " - " + Instance, ConvaiLogger.LogCategory.UI); Destroy(gameObject); return; } // Set the singleton instance Instance = this; ValidateUIPrefabs(); InitializeUIStrategies(); _hasPlayerData = ConvaiPlayerDataSO.GetPlayerData(out _convaiPlayerData); } // 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) { ConvaiLogger.Warn($"The provided prefab for {uiType} does not have a component that implements IChatUI.", ConvaiLogger.LogCategory.UI); return; } uiComponent.Initialize(uiPrefab); GetUIAppearances[uiType] = uiComponent; } catch (Exception ex) { ConvaiLogger.Exception($"An error occurred while initializing the UI: {ex.Message}", ConvaiLogger.LogCategory.UI); } } /// /// 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) { ConvaiLogger.Warn($"No character found named {charName}", ConvaiLogger.LogCategory.Character); return; } if (!ConvaiNPCManager.Instance.CheckForNPCToNPCConversation(character.characterGameObject)) _currentUIImplementation?.SendCharacterText(charName, text, character.CharacterTextColor); else ConvaiLogger.DebugLog($"Character {charName} is in conversation with another NPC.", ConvaiLogger.LogCategory.Character); } /// /// Sends player text to the current UI. /// /// The text to send. public void SendPlayerText(string text) { _currentUIImplementation?.SendPlayerText(_hasPlayerData ? _convaiPlayerData.PlayerName : _convaiPlayerData.DefaultPlayerName, 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) { ConvaiLogger.Exception($"An error occurred while validating UI prefabs: {ex.Message}", ConvaiLogger.LogCategory.UI); } } /// /// Sets the current UI type and fades between UIs. /// /// The new UI type to set. public void SetUIType(UIType newUIType) { if (!GetUIAppearances.ContainsKey(newUIType)) { ConvaiLogger.Warn($"The UI type {newUIType} is not available.", ConvaiLogger.LogCategory.UI); 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); } } } namespace Convai.Scripts.Runtime.Addons { [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; } } }