using System; using System.Collections.Generic; using CSNetwork; using CSNetwork.GPDataType; using UnityEngine; using UnityEngine.Pool; using UnityEngine.UI; namespace BrewMonster.Scripts.ChatUI { [Serializable] public struct ChatMessageData { public string message; public byte channel; } public class ChatSystemlUI : MonoBehaviour { [Header("ChatPanelUI")] public ScrollRect scrollRect; [Tooltip("Nền/fullscreen block — bật/tắt cùng lúc với chatPanelUIGO (có thể để null).")] public GameObject BgGameObject; public GameObject chatPanelUIGO; public RectTransform content; public ChatMessageView messagePrefab; public Button closeChatPanelButton; public Button closeBGChatPanelButton; public Button emojiButton; public Button sendButton; [Header("EmojiPanelUI")] public GameObject EmojiPanelUI; public Button closeEmojiPanelButton; [Header("Config")] public int maxVisibleMessages = 30; public int maxStoredMessages = 2000; [Header("Chat System Data")] public ChatSystemSO chatSystemSO; private Dictionary _iconCache; private List _messages = new(); private List _visibleViews = new(); private List _filteredMessagesCache = new(); private ObjectPool _pool; private bool _userAtBottom = true; private ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL; ChatInputHandler _chatInput; void Awake() { ClearChat(); _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); EventBus.Subscribe(OnOpenChatPanelRequested); _pool = new ObjectPool( CreateItem, OnGetItem, OnReleaseItem, OnDestroyItem, false, 10, 100 ); scrollRect.onValueChanged.AddListener(OnScrollChanged); SetChatPanelAndBgVisible(false); } /// Bật/tắt panel chat và BG cùng trạng thái (2 GO tách nhưng luồng giống nhau). void SetChatPanelAndBgVisible(bool visible) { if (chatPanelUIGO != null) chatPanelUIGO.SetActive(visible); if (BgGameObject != null) BgGameObject.SetActive(visible); } void OnEnable() { if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf || _pool == null || content == null) return; RefreshVisible(); } void Start() { _chatInput = GetComponent(); WireUiButtons(); } void WireUiButtons() { if (closeChatPanelButton != null) closeChatPanelButton.onClick.AddListener(CloseChatPanel); if (closeBGChatPanelButton != null) closeBGChatPanelButton.onClick.AddListener(CloseChatPanel); if (emojiButton != null) emojiButton.onClick.AddListener(ToggleEmojiPanel); if (closeEmojiPanelButton != null) closeEmojiPanelButton.onClick.AddListener(CloseEmojiPanel); if (sendButton != null) sendButton.onClick.AddListener(OnSendButtonClicked); } private void OnDestroy() { if (closeChatPanelButton != null) closeChatPanelButton.onClick.RemoveListener(CloseChatPanel); if (closeBGChatPanelButton != null) closeBGChatPanelButton.onClick.RemoveListener(CloseChatPanel); if (emojiButton != null) emojiButton.onClick.RemoveListener(ToggleEmojiPanel); if (closeEmojiPanelButton != null) closeEmojiPanelButton.onClick.RemoveListener(CloseEmojiPanel); if (sendButton != null) sendButton.onClick.RemoveListener(OnSendButtonClicked); EventBus.Unsubscribe(OnChatMessageClear); EventBus.Unsubscribe(OnChatMessageReceived); EventBus.Unsubscribe(OnChannelFilterChanged); EventBus.Unsubscribe(OnOpenChatPanelRequested); if (scrollRect != null) scrollRect.onValueChanged.RemoveListener(OnScrollChanged); if (_pool != null) { _pool.Clear(); _pool.Dispose(); } } private void OnChannelFilterChanged(ChatChannelFilterChangedEvent e) { if (this == null) return; _currentFilterChannel = e.channel; if (chatPanelUIGO != null && chatPanelUIGO.activeSelf) RefreshVisible(); } void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _) { if (this == null) return; OpenChatPanel(_.focusInputOnOpen); } private 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; } private void OnChatMessageReceived(GameSession.ChatMessageEvent x) { ChatThreadDispatcher.Instance.Post(() => { if (this == null) return; AddMessage(x.context, x.channel); }); } private void OnChatMessageClear(OnEventClearChat obj) { ChatThreadDispatcher.Instance.Post(() => { if (this == null) return; ClearChat(); }); } ChatMessageView CreateItem() { if (messagePrefab == null || content == null) return null; var item = Instantiate(messagePrefab); item.transform.SetParent(content, false); return item; } void OnGetItem(ChatMessageView item) { if (item != null) item.gameObject.SetActive(true); } void OnReleaseItem(ChatMessageView item) { if (item != null) item.gameObject.SetActive(false); } void OnDestroyItem(ChatMessageView item) { if (item != null) Destroy(item.gameObject); } void OnScrollChanged(Vector2 pos) { _userAtBottom = scrollRect.verticalNormalizedPosition <= 0.001f; } bool IsAtBottom() { return scrollRect.verticalNormalizedPosition <= 0.001f; } public 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); if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf) return; if (ShouldShowMessage(data)) { AddMessageView(data); if (_userAtBottom) ScrollToBottom(); } } void AddMessageView(ChatMessageData data) { if (this == null) return; var view = _pool.Get(); if (view == null) view = CreateItem(); if (view == null) return; view.transform.SetParent(content, false); view.transform.SetAsLastSibling(); Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null; view.Bind(icon, data.message); _visibleViews.Add(view); if (_visibleViews.Count > maxVisibleMessages) { var old = _visibleViews[0]; _visibleViews.RemoveAt(0); if (old != null) { _pool.Release(old); } } Canvas.ForceUpdateCanvases(); } void RefreshVisible() { foreach (var view in _visibleViews) { if (view != null) _pool.Release(view); } _visibleViews.Clear(); _filteredMessagesCache.Clear(); foreach (var msg in _messages) { if (ShouldShowMessage(msg)) { _filteredMessagesCache.Add(msg); } } int start = Mathf.Max(0, _filteredMessagesCache.Count - maxVisibleMessages); for (int i = start; i < _filteredMessagesCache.Count; i++) { var view = _pool.Get(); if (view == null) view = CreateItem(); if (view == null) continue; view.transform.SetParent(content, false); view.transform.SetAsLastSibling(); var data = _filteredMessagesCache[i]; Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null; view.Bind(icon, data.message); _visibleViews.Add(view); } Canvas.ForceUpdateCanvases(); ScrollToBottom(); } /// Cuộn log chat xuống dòng cuối (sau khi layout xong). public void ScrollToBottom() { if (scrollRect == null) return; if (content != null) LayoutRebuilder.ForceRebuildLayoutImmediate(content); Canvas.ForceUpdateCanvases(); scrollRect.verticalNormalizedPosition = 0f; } public void ClearChat() { foreach (var view in _visibleViews) { if (view != null) _pool.Release(view); } _visibleViews.Clear(); _messages.Clear(); } public void OnHandlerChatButton() { if (chatPanelUIGO == null) return; bool open = !chatPanelUIGO.activeSelf; SetChatPanelAndBgVisible(open); if (open) RefreshVisible(); else SetEmojiPanelVisible(false); } public void OpenChatPanel() { OpenChatPanel(true); } public void OpenChatPanel(bool focusInputOnOpen) { if (chatPanelUIGO == null) return; SetChatPanelAndBgVisible(true); RefreshVisible(); _chatInput ??= GetComponent(); if (focusInputOnOpen && _chatInput != null && _chatInput.inputField != null) _chatInput.inputField.ActivateInputField(); } public void CloseChatPanel() { SetChatPanelAndBgVisible(false); SetEmojiPanelVisible(false); } public void ToggleEmojiPanel() { if (EmojiPanelUI == null) return; SetEmojiPanelVisible(!EmojiPanelUI.activeSelf); } public void CloseEmojiPanel() { SetEmojiPanelVisible(false); } void SetEmojiPanelVisible(bool visible) { if (EmojiPanelUI != null) EmojiPanelUI.SetActive(visible); } void OnSendButtonClicked() { _chatInput ??= GetComponent(); _chatInput?.SubmitFromSendButton(); } } public struct OnEventClearChat { } /// /// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; subscribe. /// public struct OpenChatPanelRequestedEvent { public bool focusInputOnOpen; public OpenChatPanelRequestedEvent(bool focusInputOnOpen) { this.focusInputOnOpen = focusInputOnOpen; } } }