using System; using System.Threading; using BrewMonster.Scripts.ChatUI; using CSNetwork.GPDataType; using Cysharp.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.UI; namespace BrewMonster.PerfectWorld.Scripts.UI { public class UIPlayer : MonoBehaviour { [Header("World HUD (optional)")] public Image healthImage; [SerializeField] private TextMeshProUGUI nameText; [SerializeField] private Transform canvasRoot; [SerializeField] private TextMeshProUGUI chatText; [Header("References")] [SerializeField] private CECPlayer hostplayer; [SerializeField] private float chatDisplayDuration = 5f; private CancellationTokenSource _chatCts; private const string DefaultLayerName = "Default"; private bool _isVisible; private void Awake() { CacheRefs(); } private void Start() { CacheRefs(); if (hostplayer == null) { Debug.LogError("[UIPlayer] CECPlayer not found in parents."); return; } SetVisible(true); RefreshName(); EventBus.SubscribeChannel(hostplayer.GetCharacterID(), SetChatMessage); EventBus.SubscribeChannel(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI); } private void OnDestroy() { _chatCts?.Cancel(); _chatCts?.Dispose(); if (hostplayer == null) { return; } EventBus.UnsubscribeChannel(hostplayer.GetCharacterID(), SetChatMessage); EventBus.UnsubscribeChannel(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI); } private void UpdateHostPlayerInfoUI(cmd_self_info_00 obj) { //healthText.text = $"{obj.iHP}/{obj.iMaxHP}"; if (healthImage != null && obj.iMaxHP > 0) healthImage.fillAmount = (float)obj.iHP / obj.iMaxHP; } public void SetVisible(bool visible) { _isVisible = visible; if (canvasRoot == null) return; if (visible) { int defaultLayer = LayerMask.NameToLayer(DefaultLayerName); if (defaultLayer < 0) defaultLayer = 0; SetLayerRecursively(canvasRoot.gameObject, defaultLayer); RefreshName(); } canvasRoot.gameObject.SetActive(visible); } public bool IsVisible() => _isVisible; public void RefreshName() { if (nameText == null || hostplayer == null) return; var n = hostplayer.GetName(); if (string.IsNullOrWhiteSpace(n)) n = hostplayer.gameObject != null ? hostplayer.gameObject.name : ""; if (!string.IsNullOrWhiteSpace(n)) nameText.text = n; } private void CacheRefs() { if (hostplayer == null) hostplayer = GetComponentInParent(); if (canvasRoot == null) { var t = transform.Find("Canvas"); if (t != null) canvasRoot = t; } if (nameText == null) { var named = transform.GetComponentsInChildren(true); foreach (var tmp in named) { if (tmp != null && tmp.gameObject.name.Contains("Name", StringComparison.OrdinalIgnoreCase)) { nameText = tmp; break; } } } } private static void SetLayerRecursively(GameObject obj, int layer) { if (obj == null) return; obj.layer = layer; var t = obj.transform; for (int i = 0; i < t.childCount; i++) SetLayerRecursively(t.GetChild(i).gameObject, layer); } private void SetChatMessage(EventChatMessageOnTopPlayer cxt) { if (chatText == null) { BMLogger.LogError("Don't have chatText TMProUI"); return; } ChatThreadDispatcher.Instance.Post(() =>{ SetLastSaidWords(cxt.context); }); } public void SetLastSaidWords(string message, float duration = -1f) { if (chatText == null || string.IsNullOrEmpty(message)) return; chatText.text = message; chatText.gameObject.SetActive(true); // cancel timer cũ _chatCts?.Cancel(); _chatCts = new CancellationTokenSource(); float time = duration > 0 ? duration : chatDisplayDuration; HideChatAsync(time, _chatCts.Token).Forget(); } private async UniTaskVoid HideChatAsync(float time, CancellationToken token) { try { await UniTask.Delay( TimeSpan.FromSeconds(time), cancellationToken: token ); if (chatText != null) chatText.gameObject.SetActive(false); } catch (OperationCanceledException) { // chat mới đến → timer cũ bị cancel } } } public struct EventChatMessageOnTopPlayer { public int roleId; public string context; public EventChatMessageOnTopPlayer(int roleId, string context) { this.roleId = roleId; this.context = context; } } }