223 lines
8.0 KiB
C#
223 lines
8.0 KiB
C#
using System.Collections.Generic;
|
|
using CSNetwork;
|
|
using CSNetwork.GPDataType;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BrewMonster.Scripts.ChatUI
|
|
{
|
|
/// <summary>
|
|
/// 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 <see cref="OpenChatPanelRequestedEvent"/>.
|
|
/// </summary>
|
|
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<ChatMessageData> _messages = new();
|
|
readonly List<ChatMessageData> _miniFilterBuffer = new();
|
|
readonly List<ChatMessageView> _miniChatViews = new();
|
|
Dictionary<byte, Sprite> _iconCache;
|
|
ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL;
|
|
|
|
void Awake()
|
|
{
|
|
if (miniChatScrollRect == null && miniChatContent != null)
|
|
miniChatScrollRect = miniChatContent.GetComponentInParent<ScrollRect>();
|
|
|
|
_iconCache = new Dictionary<byte, Sprite>();
|
|
if (chatSystemSO != null && chatSystemSO.channelIcons != null)
|
|
{
|
|
foreach (var mapping in chatSystemSO.channelIcons)
|
|
_iconCache[(byte)mapping.channel] = mapping.icon;
|
|
}
|
|
|
|
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
|
|
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
|
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
RefreshMiniChat();
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
if (onOpenChatPanelButton != null)
|
|
onOpenChatPanelButton.onClick.AddListener(OnOpenChatPanelButtonClicked);
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
if (onOpenChatPanelButton != null)
|
|
onOpenChatPanelButton.onClick.RemoveListener(OnOpenChatPanelButtonClicked);
|
|
|
|
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
|
|
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
|
|
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(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());
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
/// <summary>Cuộn mini log xuống dòng cuối (khi có ScrollRect và nội dung cao hơn viewport).</summary>
|
|
void ScrollMiniChatToBottom()
|
|
{
|
|
if (miniChatScrollRect == null) return;
|
|
if (miniChatContent != null)
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(miniChatContent);
|
|
Canvas.ForceUpdateCanvases();
|
|
miniChatScrollRect.verticalNormalizedPosition = 0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mini chat rows use TMP/Image with raycastTarget on — they sit above the open button and steal clicks.
|
|
/// </summary>
|
|
static void DisableGraphicsRaycastUnder(Transform root)
|
|
{
|
|
if (root == null) return;
|
|
foreach (var g in root.GetComponentsInChildren<Graphic>(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();
|
|
}
|
|
}
|
|
}
|