628 lines
22 KiB
C#
628 lines
22 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using TMPro;
|
|
using CSNetwork.GPDataType;
|
|
using System.Collections.Generic;
|
|
using BrewMonster;
|
|
using BrewMonster.Network;
|
|
using BrewMonster.Scripts.Managers;
|
|
using BrewMonster.UI;
|
|
using CSNetwork;
|
|
using PerfectWorld.Scripts.Managers;
|
|
|
|
namespace BrewMonster.Scripts.ChatUI
|
|
{
|
|
public struct ChatChannelFilterChangedEvent
|
|
{
|
|
public ChatChannel channel;
|
|
public ChatChannelFilterChangedEvent(ChatChannel c) { channel = c; }
|
|
}
|
|
|
|
public struct WhisperPlayerEvent
|
|
{
|
|
public string playerName;
|
|
public WhisperPlayerEvent(string name) { playerName = name; }
|
|
}
|
|
|
|
public class ChatInputHandler : MonoBehaviour
|
|
{
|
|
public TMP_InputField inputField;
|
|
public ChatSystemSO chatSystem;
|
|
|
|
[Serializable]
|
|
public struct ChannelButtonMapping
|
|
{
|
|
public ChatChannel channel;
|
|
public UnityEngine.UI.Button button;
|
|
}
|
|
|
|
public List<ChannelButtonMapping> channelButtons = new();
|
|
|
|
private const int MAX_HISTORY = 10;
|
|
private ChatChannel m_currentChannel = ChatChannel.GP_CHAT_LOCAL;
|
|
private string m_whisperTarget = "";
|
|
|
|
public void SetWhisperTarget(string playerName)
|
|
{
|
|
if (string.IsNullOrEmpty(playerName)) return;
|
|
m_whisperTarget = playerName;
|
|
OnCommand_speakmode(ChatChannel.GP_CHAT_WHISPER);
|
|
}
|
|
|
|
private struct ChatMsg
|
|
{
|
|
public ChatChannel channel;
|
|
public float time;
|
|
public string msg;
|
|
public int pack;
|
|
public int slot;
|
|
}
|
|
|
|
private List<ChatMsg> m_vecHistory = new();
|
|
private int m_nCurHistory = 0;
|
|
|
|
private float m_dwTickFarCry = 0;
|
|
private float m_dwTickFarCry2 = 0;
|
|
|
|
private void Awake()
|
|
{
|
|
EventBus.Subscribe<WhisperPlayerEvent>(OnWhisperPlayerEvent);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
EventBus.Unsubscribe<WhisperPlayerEvent>(OnWhisperPlayerEvent);
|
|
}
|
|
|
|
private void OnWhisperPlayerEvent(WhisperPlayerEvent e)
|
|
{
|
|
SetWhisperTarget(e.playerName);
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
inputField.onSubmit.AddListener(OnSubmit);
|
|
|
|
foreach (var mapping in channelButtons)
|
|
{
|
|
if (mapping.button != null)
|
|
{
|
|
ChatChannel ch = mapping.channel; // Capture variable for closure
|
|
mapping.button.onClick.AddListener(() => OnCommand_speakmode(ch));
|
|
}
|
|
}
|
|
|
|
// Select the first button as default if available
|
|
if (channelButtons.Count > 0 && channelButtons[0].button != null)
|
|
{
|
|
OnCommand_speakmode(channelButtons[0].channel);
|
|
}
|
|
|
|
// [Mobile Fix] Giữ cho chữ được hiển thị trực tiếp trên UI của game thay vì bị
|
|
// đẩy vào một thanh ngang phụ (Native OS Input) bật lên cùng bàn phím.
|
|
if (inputField != null)
|
|
{
|
|
inputField.shouldHideMobileInput = true;
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// PORT C++: CDlgChat::OnCommand_speakmode
|
|
// Sets the chat input prefix based on the selected channel from SO.
|
|
// =====================================================
|
|
private void OnCommand_speakmode(ChatChannel channel)
|
|
{
|
|
if (chatSystem == null) return;
|
|
|
|
// Update button visual states using sprites from ChatSystemSO
|
|
foreach (var mapping in channelButtons)
|
|
{
|
|
if (mapping.button != null && mapping.button.image != null)
|
|
{
|
|
if (mapping.channel == channel)
|
|
mapping.button.image.sprite = chatSystem.selectedSprite;
|
|
else
|
|
mapping.button.image.sprite = chatSystem.unselectedSprite;
|
|
}
|
|
}
|
|
|
|
if (m_currentChannel != channel && channel != ChatChannel.GP_CHAT_WHISPER)
|
|
{
|
|
m_whisperTarget = "";
|
|
}
|
|
|
|
m_currentChannel = channel;
|
|
|
|
// Handle System channel input restriction
|
|
if (m_currentChannel == ChatChannel.GP_CHAT_SYSTEM)
|
|
{
|
|
inputField.interactable = false;
|
|
inputField.text = "";
|
|
}
|
|
else
|
|
{
|
|
inputField.interactable = true;
|
|
|
|
var config = chatSystem.channelIcons.Find(c => c.channel == channel);
|
|
string currentText = inputField.text;
|
|
currentText = RemoveKnownPrefix(currentText);
|
|
|
|
if (channel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
|
|
{
|
|
inputField.text = "/" + m_whisperTarget + " " + currentText;
|
|
}
|
|
else if (config.prefix != null)
|
|
{
|
|
inputField.text = config.prefix + currentText;
|
|
}
|
|
else
|
|
{
|
|
inputField.text = currentText;
|
|
}
|
|
|
|
inputField.ActivateInputField();
|
|
inputField.MoveTextEnd(false);
|
|
}
|
|
|
|
EventBus.Publish(new ChatChannelFilterChangedEvent(m_currentChannel));
|
|
}
|
|
|
|
private string RemoveKnownPrefix(string text)
|
|
{
|
|
if (string.IsNullOrEmpty(text)) return text;
|
|
|
|
if (text.StartsWith("!!") || text.StartsWith("!~") ||
|
|
text.StartsWith("!@") || text.StartsWith("!#"))
|
|
{
|
|
return text.Substring(2);
|
|
}
|
|
else if (text.StartsWith("$"))
|
|
{
|
|
return text.Substring(1);
|
|
}
|
|
else if (text.StartsWith("/"))
|
|
{
|
|
int spaceIndex = text.IndexOf(' ');
|
|
if (spaceIndex > 0)
|
|
{
|
|
return text.Substring(spaceIndex + 1);
|
|
}
|
|
return "";
|
|
}
|
|
return text;
|
|
}
|
|
|
|
private void OnSubmit(string text)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return;
|
|
|
|
OnCommand_speak(text);
|
|
}
|
|
|
|
// =====================================================
|
|
// PORT C++: CDlgChat::OnCommand_speak
|
|
// =====================================================
|
|
|
|
private void OnCommand_speak(string text)
|
|
{
|
|
string strText = text.Trim();
|
|
|
|
if (strText.Length <= 0)
|
|
{
|
|
ClearTextInInputField();
|
|
//ChangeFocus();
|
|
return;
|
|
}
|
|
|
|
int nPack = -1;
|
|
int nSlot = -1;
|
|
|
|
FilterBadWords(ref strText);
|
|
|
|
if (HandleDebugCommand(strText))
|
|
return;
|
|
|
|
float now = Time.time;
|
|
|
|
if (!CheckFarCryRequirement(strText, now))
|
|
return;
|
|
|
|
if (!CheckSuperFarCryRequirement(strText, now))
|
|
return;
|
|
|
|
if (!CheckSpamProtection(strText, now))
|
|
return;
|
|
|
|
ChatChannel resolvedChannel = ParseAndSendMessage(strText, nPack, nSlot);
|
|
|
|
if (resolvedChannel == ChatChannel.GP_CHAT_FARCRY && strText.StartsWith("!@"))
|
|
m_dwTickFarCry = now;
|
|
if (resolvedChannel == ChatChannel.GP_CHAT_SUPERFARCRY && strText.StartsWith("!#"))
|
|
m_dwTickFarCry2 = now;
|
|
|
|
SaveHistory(resolvedChannel, strText, nPack, nSlot, now);
|
|
|
|
ClearTextInInputField();
|
|
//ChangeFocus();
|
|
}
|
|
|
|
// =====================================================
|
|
// DEBUG COMMAND
|
|
// =====================================================
|
|
|
|
private bool HandleDebugCommand(string text)
|
|
{
|
|
if (text == "##debug")
|
|
{
|
|
Debug.Log("Toggle debug console");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// =====================================================
|
|
// FARCRY CHECK (!@)
|
|
// =====================================================
|
|
|
|
private bool CheckFarCryRequirement(string text, float now)
|
|
{
|
|
if (text.StartsWith("!@"))
|
|
{
|
|
int itemNum = GetPlayerItemCount(12979) + GetPlayerItemCount(36092);
|
|
|
|
if (itemNum < 1 || GetPlayerLevel() < 5)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(731), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
|
|
if (now - m_dwTickFarCry <= 1f)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(730), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// =====================================================
|
|
// SUPER FARCRY (!#)
|
|
// =====================================================
|
|
|
|
private bool CheckSuperFarCryRequirement(string text, float now)
|
|
{
|
|
if (text.StartsWith("!#"))
|
|
{
|
|
int itemNum = GetPlayerItemCount(27728) + GetPlayerItemCount(27729);
|
|
|
|
if (itemNum < 1)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(8531), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
|
|
if (now - m_dwTickFarCry2 <= 1f)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(8530), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// =====================================================
|
|
// SPAM PROTECTION
|
|
// =====================================================
|
|
|
|
private bool CheckSpamProtection(string text, float now)
|
|
{
|
|
if (m_vecHistory.Count == 0)
|
|
return true;
|
|
|
|
var last = m_vecHistory[m_vecHistory.Count - 1];
|
|
|
|
if (now - last.time <= 1f)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(272), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
|
|
foreach (var cm in m_vecHistory)
|
|
{
|
|
if (cm.channel != ChatChannel.GP_CHAT_WHISPER &&
|
|
cm.msg == text &&
|
|
now - cm.time <= 6f)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(273), ChatChannel.GP_CHAT_MISC);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// =====================================================
|
|
// PARSE MESSAGE PREFIX
|
|
// =====================================================
|
|
|
|
private ChatChannel ParseAndSendMessage(string text, int nPack, int nSlot)
|
|
{
|
|
ChatChannel channel = m_currentChannel;
|
|
string pszMsg = text;
|
|
|
|
if (text.StartsWith("!!"))
|
|
{
|
|
channel = ChatChannel.GP_CHAT_TEAM;
|
|
pszMsg = text.Substring(2);
|
|
}
|
|
else if (text.StartsWith("!~"))
|
|
{
|
|
channel = ChatChannel.GP_CHAT_FACTION;
|
|
pszMsg = text.Substring(2);
|
|
}
|
|
else if (text.StartsWith("!@"))
|
|
{
|
|
channel = ChatChannel.GP_CHAT_FARCRY;
|
|
pszMsg = text.Substring(2);
|
|
}
|
|
else if (text.StartsWith("!#"))
|
|
{
|
|
channel = ChatChannel.GP_CHAT_SUPERFARCRY;
|
|
pszMsg = text.Substring(2);
|
|
}
|
|
else if (text.StartsWith("$"))
|
|
{
|
|
channel = ChatChannel.GP_CHAT_TRADE;
|
|
pszMsg = text.Substring(1);
|
|
}
|
|
else if (text.StartsWith("/"))
|
|
{
|
|
HandleWhisper(text, nPack, nSlot);
|
|
return ChatChannel.GP_CHAT_WHISPER;
|
|
}
|
|
else if (channel == ChatChannel.GP_CHAT_WHISPER)
|
|
{
|
|
// Cho phép chat kênh Whisper mượt mà mà không bắt buộc gõ ký tự "/" ở đầu
|
|
if (!string.IsNullOrEmpty(m_whisperTarget))
|
|
{
|
|
HandleWhisper("/" + m_whisperTarget + " " + text, nPack, nSlot);
|
|
}
|
|
else
|
|
{
|
|
HandleWhisper("/" + text, nPack, nSlot);
|
|
}
|
|
return ChatChannel.GP_CHAT_WHISPER;
|
|
}
|
|
// Không gõ prefix thủ công thì sẽ dùng m_currentChannel đã được gán ở đầu hàm
|
|
|
|
SendChat(channel, pszMsg, nPack, nSlot);
|
|
return channel;
|
|
}
|
|
|
|
// =====================================================
|
|
// WHISPER
|
|
// =====================================================
|
|
|
|
private void HandleWhisper(string text, int nPack, int nSlot)
|
|
{
|
|
string cmd = text.Substring(1);
|
|
|
|
int spaceIndex = cmd.IndexOf(' ');
|
|
|
|
if (spaceIndex <= 0)
|
|
{
|
|
AddChatMessage(CECUIManager.Instance.GameUI.GetStringFromTable(234), ChatChannel.GP_CHAT_MISC);
|
|
return;
|
|
}
|
|
|
|
string player = cmd.Substring(0, spaceIndex);
|
|
string msg = cmd.Substring(spaceIndex + 1);
|
|
|
|
m_whisperTarget = player;
|
|
|
|
SendPrivateChat(player, msg, nPack, nSlot);
|
|
}
|
|
|
|
// =====================================================
|
|
// SEND CHAT
|
|
// =====================================================
|
|
|
|
private void SendChat(ChatChannel channel, string msg, int pack, int slot)
|
|
{
|
|
UnityGameSession.SendChatData((byte)channel, msg, pack, slot);
|
|
}
|
|
|
|
private void SendPrivateChat(string target, string msg, int pack, int slot)
|
|
{
|
|
BMLogger.Log($"[Cuong] Whisper to {target}: {msg}");
|
|
|
|
// [Port] C++: CDlgChat::OnCommand_speak → privatechat branch
|
|
// Gửi tin nhắn mật (whisper) lên server qua GameSession.
|
|
// Tra cứu ID của người nhận từ danh sách bạn bè (giống C++: pFriend ? pFriend->id : -1)
|
|
int idTarget = GetCharacterIdByName(target);
|
|
UnityGameSession.Instance.GameSession.SendPrivateChatData(target, msg, 0, idTarget, pack, slot);
|
|
|
|
// [Port] C++: DlgChat.cpp dòng 1531-1532
|
|
// Server KHÔNG echo whisper lại cho người gửi → phải hiển thị local.
|
|
// C++ dùng: a_sprintf(szMsg, GetStringFromTable(233), szName, szText)
|
|
// → format "对&Name&说:msg" — dấu & được thêm bởi FORMAT STRING, không phải bởi caller.
|
|
// C# dùng FIXMSG_PRIVATECHAT2 tương ứng; fallback nếu format string lỗi.
|
|
// QUAN TRỌNG: chỉ truyền target (không có & bao quanh) vào string.Format.
|
|
// Nếu format string đã có &{0}& thì kết quả đúng là "&target&".
|
|
// Nếu format string không có & thì AddChatMessage cũng chỉ wrap màu bình thường.
|
|
// Tránh truyền "&target&" vì kết quả sẽ là "&&target&&" → regex chỉ match "target"
|
|
// nhưng để lại & dư ở ngoài → UI hiển thị "&target&" thay vì link sạch.
|
|
CECStringTab pStrTab = EC_Game.GetFixedMsgs();
|
|
string fmt = AUIDialog.FormatPrintf(pStrTab.GetWideString((int)FixedMsg.FIXMSG_PRIVATECHAT2));
|
|
string localMsg;
|
|
try
|
|
{
|
|
localMsg = string.Format(fmt, target, msg);
|
|
}
|
|
catch
|
|
{
|
|
// Fallback: dùng &target& để AddChatMessage tạo link — format không có & thì mình thêm
|
|
localMsg = $"&{target}&: {msg}";
|
|
}
|
|
|
|
CECGameUIMan pGameUI = EC_Game.GetGameRun()?.GetUIManager()?.GetInGameUIMan();
|
|
if (pGameUI != null)
|
|
{
|
|
// idTarget là ID người NHẬN (hoặc -1 nếu không tìm thấy trong friend list)
|
|
// AddChatMessage sẽ convert &target& thành TMP link tag có dạng idTarget|target
|
|
pGameUI.AddChatMessage(localMsg, ChatChannel.GP_CHAT_WHISPER, idTarget, null, 0, 0, null, msg);
|
|
}
|
|
}
|
|
|
|
// [Port] C++: pFriendMan->GetFriendByName(name) → pFriend->id
|
|
// Tìm character ID của người chơi theo tên từ friend list.
|
|
// Trả về -1 nếu không tìm thấy (giống C++ trả về idFriend = -1).
|
|
private int GetCharacterIdByName(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return -1;
|
|
var host = EC_Game.GetGameRun()?.GetHostPlayer();
|
|
if (host == null) return -1;
|
|
var friendMan = host.GetFriendMan();
|
|
if (friendMan == null) return -1;
|
|
var friend = friendMan.GetFriendByName(name);
|
|
return friend.HasValue ? friend.Value.Id : -1;
|
|
}
|
|
|
|
// =====================================================
|
|
// HISTORY
|
|
// =====================================================
|
|
|
|
private void SaveHistory(ChatChannel channel, string msg, int pack, int slot, float now)
|
|
{
|
|
if (m_vecHistory.Count >= MAX_HISTORY)
|
|
m_vecHistory.RemoveAt(0);
|
|
|
|
m_vecHistory.Add(new ChatMsg
|
|
{
|
|
channel = channel,
|
|
msg = msg,
|
|
pack = pack,
|
|
slot = slot,
|
|
time = now
|
|
});
|
|
|
|
m_nCurHistory = m_vecHistory.Count;
|
|
}
|
|
|
|
// =====================================================
|
|
// UTILITIES
|
|
// =====================================================
|
|
|
|
private void FilterBadWords(ref string text)
|
|
{
|
|
CECUIManager.Instance.FilterBadWords(ref text);
|
|
}
|
|
|
|
private void AddChatMessage(string msg, ChatChannel channel, int idPlayer = -1, string pszPlayer = "", byte byFlag = 0)
|
|
{
|
|
string strModified = msg;
|
|
|
|
if (channel is not ChatChannel.GP_CHAT_MISC)
|
|
{
|
|
// 1. Filter bad words
|
|
CECUIManager.Instance.FilterBadWords(ref strModified);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(strModified))
|
|
return;
|
|
|
|
// 2. Blacklist check (Placeholder for porting)
|
|
if (IsPlayerBlacklisted(idPlayer))
|
|
return;
|
|
|
|
// 3. Flag handling (Friend chat routing)
|
|
if (byFlag == 1) // CHANNEL_FRIEND equivalent
|
|
{
|
|
// AddFriendMessage(strModified, idPlayer, pszPlayer, ...);
|
|
// return;
|
|
}
|
|
|
|
// 4. Formatting with Rich Text
|
|
string colorHex = GetChannelColorHex(channel);
|
|
string prefix = GetChannelPrefix(channel);
|
|
string sender = string.IsNullOrEmpty(pszPlayer) ? "" : $"{pszPlayer}: ";
|
|
|
|
string finalMsg = $"<color=#{colorHex}>{prefix}{sender}{strModified}</color>";
|
|
|
|
// 5. Publish event
|
|
EventBus.Publish(new GameSession.ChatMessageEvent(finalMsg, (byte)channel));
|
|
}
|
|
|
|
private string GetChannelColorHex(ChatChannel channel)
|
|
{
|
|
// [Port] C++: CDlgChat::m_pszColor[] — DlgChat.cpp dòng 120-136
|
|
// Giữ đúng màu gốc cho các kênh dùng trong local error messages
|
|
return channel switch
|
|
{
|
|
ChatChannel.GP_CHAT_LOCAL => "FFFFFF",
|
|
ChatChannel.GP_CHAT_FARCRY => "FFE400",
|
|
ChatChannel.GP_CHAT_TEAM => "00FF00",
|
|
ChatChannel.GP_CHAT_FACTION => "00FFFC",
|
|
ChatChannel.GP_CHAT_WHISPER => "0065FE", // C++: "^0065FE" — KHÔNG phải FF00FF
|
|
ChatChannel.GP_CHAT_TRADE => "FF742E",
|
|
ChatChannel.GP_CHAT_SYSTEM => "BED293",
|
|
ChatChannel.GP_CHAT_BROADCAST => "FF3600",
|
|
ChatChannel.GP_CHAT_MISC => "9AA6FF",
|
|
ChatChannel.GP_CHAT_SUPERFARCRY => "ff9b3e",
|
|
ChatChannel.GP_CHAT_BATTLE => "FFFFFF",
|
|
ChatChannel.GP_CHAT_COUNTRY => "FFFFFF",
|
|
_ => "FFFFFF"
|
|
};
|
|
}
|
|
|
|
private string GetChannelPrefix(ChatChannel channel)
|
|
{
|
|
return channel switch
|
|
{
|
|
ChatChannel.GP_CHAT_TEAM => "[Team] ",
|
|
ChatChannel.GP_CHAT_FACTION => "[Faction] ",
|
|
ChatChannel.GP_CHAT_FARCRY => "[World] ",
|
|
ChatChannel.GP_CHAT_WHISPER => "[Whisper] ",
|
|
_ => ""
|
|
};
|
|
}
|
|
|
|
private bool IsPlayerBlacklisted(int idPlayer) => false; // Placeholder
|
|
|
|
private void ClearTextInInputField()
|
|
{
|
|
if (m_currentChannel == ChatChannel.GP_CHAT_WHISPER && !string.IsNullOrEmpty(m_whisperTarget))
|
|
{
|
|
inputField.text = "/" + m_whisperTarget + " ";
|
|
}
|
|
else
|
|
{
|
|
inputField.text = "";
|
|
}
|
|
}
|
|
private void ChangeFocus()
|
|
{
|
|
inputField.ActivateInputField();
|
|
}
|
|
|
|
/// <summary>
|
|
/// C++: GetHostPlayer()->GetPack()->GetItemTotalNum(id) — đếm túi chính.
|
|
/// </summary>
|
|
private int GetPlayerItemCount(int templateId)
|
|
{
|
|
var host = EC_Game.GetGameRun()?.GetHostPlayer();
|
|
EC_Inventory pack = host?.GetPack();
|
|
return pack?.GetItemTotalNum(templateId) ?? 0;
|
|
}
|
|
|
|
private int GetPlayerLevel()
|
|
{
|
|
return EC_Game.GetGameRun()?.GetHostPlayer()?.GetBasicProps().iLevel ?? 0;
|
|
}
|
|
}
|
|
}
|