using System.Collections.Generic; using CSNetwork; using CSNetwork.GPDataType; using UnityEngine; using UnityEngine.UI; namespace BrewMonster.Scripts.ChatUI { /// /// Vùng xem trước chat nhỏ; đồng bộ qua EventBus với tin nhắn và bộ lọc kênh. /// Mở panel chat đầy đủ qua . /// public class MiniChatUI : MonoBehaviour { [Header("MiniChat")] public Button onOpenChatPanelButton; [Tooltip("Dialog name registered in CECUIManager/CECGameUIMan for full chat panel.")] [SerializeField] string chatDialogName = "Win_Chat"; [Tooltip("Parent cho các dòng tin xem trước (nên có VerticalLayoutGroup).")] public RectTransform miniChatContent; [Tooltip("ScrollRect bọc mini chat (null = không cuộn). Nếu để trống, Awake sẽ thử GetComponentInParent từ miniChatContent.")] [SerializeField] ScrollRect miniChatScrollRect; [Tooltip("Null = dùng messagePrefab (fallback).")] public ChatMessageView miniMessagePrefab; [Tooltip("Prefab dòng tin khi miniMessagePrefab null (giống messagePrefab panel chính).")] public ChatMessageView messagePrefab; [SerializeField] int miniChatPreviewLines = 5; [Tooltip("Tắt raycast trên dòng preview để click xuyên xuống onOpenChatPanelButton (TMP/Image mặc định chặn Button).")] [SerializeField] bool miniChatPassThroughClicks = true; [SerializeField] int maxStoredMessages = 2000; [Header("Chat System Data")] public ChatSystemSO chatSystemSO; readonly List _messages = new(); readonly List _miniFilterBuffer = new(); readonly List _miniChatViews = new(); Dictionary _iconCache; ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL; void Awake() { if (miniChatScrollRect == null && miniChatContent != null) miniChatScrollRect = miniChatContent.GetComponentInParent(); _iconCache = new Dictionary(); if (chatSystemSO != null && chatSystemSO.channelIcons != null) { foreach (var mapping in chatSystemSO.channelIcons) _iconCache[(byte)mapping.channel] = mapping.icon; } EventBus.Subscribe(OnChatMessageClear); EventBus.Subscribe(OnChatMessageReceived); EventBus.Subscribe(OnChannelFilterChanged); } void OnEnable() { RefreshMiniChat(); } void Start() { if (onOpenChatPanelButton != null) onOpenChatPanelButton.onClick.AddListener(OnOpenChatPanelButtonClicked); } void OnDestroy() { if (onOpenChatPanelButton != null) onOpenChatPanelButton.onClick.RemoveListener(OnOpenChatPanelButtonClicked); EventBus.Unsubscribe(OnChatMessageClear); EventBus.Unsubscribe(OnChatMessageReceived); EventBus.Unsubscribe(OnChannelFilterChanged); ClearMiniChatViews(); } void OnOpenChatPanelButtonClicked() { // Open through CECUIManager stack so current top UI/popup is hidden consistently. if (!string.IsNullOrEmpty(chatDialogName) && CECUIManager.Instance != null) CECUIManager.Instance.ShowUI(chatDialogName); EventBus.Publish(new OpenChatPanelRequestedEvent(false)); } void OnChannelFilterChanged(ChatChannelFilterChangedEvent e) { if (this == null) return; _currentFilterChannel = e.channel; RefreshMiniChat(); } bool ShouldShowMessage(ChatMessageData data) { if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true; if (data.channel == (byte)ChatChannel.GP_CHAT_MISC) return true; return data.channel == (byte)_currentFilterChannel; } void OnChatMessageReceived(GameSession.ChatMessageEvent x) { ChatThreadDispatcher.Instance.Post(() => { if (this == null) return; AddMessage(x.context, x.channel); }); } void OnChatMessageClear(OnEventClearChat obj) { ChatThreadDispatcher.Instance.Post(() => { if (this == null) return; ClearChat(); }); } void AddMessage(string msg, byte channel) { if (this == null) return; var data = new ChatMessageData { message = msg, channel = channel }; _messages.Add(data); if (_messages.Count > maxStoredMessages) _messages.RemoveAt(0); RefreshMiniChat(); } void RefreshMiniChat() { if (miniChatContent == null) return; _miniFilterBuffer.Clear(); foreach (var msg in _messages) { if (ShouldShowMessage(msg)) _miniFilterBuffer.Add(msg); } int take = Mathf.Min(Mathf.Max(1, miniChatPreviewLines), _miniFilterBuffer.Count); int startIdx = _miniFilterBuffer.Count - take; while (_miniChatViews.Count < take) { var v = CreateMiniChatItem(); if (v == null) return; _miniChatViews.Add(v); } while (_miniChatViews.Count > take) { var last = _miniChatViews[_miniChatViews.Count - 1]; _miniChatViews.RemoveAt(_miniChatViews.Count - 1); if (last != null) Destroy(last.gameObject); } for (int i = 0; i < take; i++) { var data = _miniFilterBuffer[startIdx + i]; var view = _miniChatViews[i]; Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null; view.gameObject.SetActive(true); view.transform.SetSiblingIndex(i); if (miniChatPassThroughClicks) DisableGraphicsRaycastUnder(view.transform); view.Bind(icon, data.message); } ScrollMiniChatToBottom(); } /// Cuộn mini log xuống dòng cuối (khi có ScrollRect và nội dung cao hơn viewport). void ScrollMiniChatToBottom() { if (miniChatScrollRect == null) return; if (miniChatContent != null) LayoutRebuilder.ForceRebuildLayoutImmediate(miniChatContent); Canvas.ForceUpdateCanvases(); miniChatScrollRect.verticalNormalizedPosition = 0f; } /// /// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks. /// static void DisableGraphicsRaycastUnder(Transform root) { if (root == null) return; foreach (var g in root.GetComponentsInChildren(true)) g.raycastTarget = false; } ChatMessageView CreateMiniChatItem() { var prefab = miniMessagePrefab != null ? miniMessagePrefab : messagePrefab; if (prefab == null || miniChatContent == null) return null; var item = Instantiate(prefab, miniChatContent, false); return item; } void ClearChat() { _messages.Clear(); ClearMiniChatViews(); } void ClearMiniChatViews() { foreach (var v in _miniChatViews) { if (v != null) Destroy(v.gameObject); } _miniChatViews.Clear(); } } }