add UI chat

This commit is contained in:
CuongNV
2026-03-12 17:26:24 +07:00
parent 5cb958a25f
commit e32ff4631b
15 changed files with 937 additions and 129 deletions
+19 -12
View File
@@ -22,21 +22,13 @@ namespace BrewMonster.Scripts.Chat
public struct chat_item_base
{
public short cmd_id;
};
}
public struct chat_equip_item
{
public short cmd_id;
public int type;
public int expire_date;
public int proc_type;
public ushort content_length;
public byte[] content;
public int LenghtHeader()
{
return Marshal.SizeOf<short>() + (Marshal.SizeOf<int>() * 3) + Marshal.SizeOf<ushort>();
}
public char where;
public short index;
}
struct chat_generalcard_collection
@@ -47,6 +39,21 @@ namespace BrewMonster.Scripts.Chat
public static class CHAT_S2C
{
public struct chat_equip_item
{
public short cmd_id;
public int type;
public int expire_date;
public int proc_type;
public ushort content_length;
public byte[] content;
public int LenghtHeader()
{
return Marshal.SizeOf<short>() + (Marshal.SizeOf<int>() * 3) + Marshal.SizeOf<ushort>();
}
}
public enum EChatS2CCommand : short
{
CHAT_EQUIP_ITEM,
@@ -1,23 +0,0 @@
using UnityEngine;
namespace BrewMonster.Scripts.Chat
{
public class TestChat : MonoSingleton<TestChat>
{
public ChatInputHandler testChat;
public string text;
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.C))
{
testChat.Send(text);
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 71c6bd36af2b4627838a10eb95f147b6
timeCreated: 1772782953
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using CSNetwork;
using UnityEngine;
@@ -9,6 +10,7 @@ namespace BrewMonster.Scripts.ChatUI
public class ChatPanelUI : MonoBehaviour
{
[Header("UI")] public ScrollRect scrollRect;
public GameObject chatPanelUIGO;
public RectTransform content;
public ChatMessageView messagePrefab;
//public GameObject newMessageIndicator;
@@ -25,8 +27,7 @@ namespace BrewMonster.Scripts.ChatUI
void Awake()
{
EventBus.Subscribe<GameSession.ChatMessageEvent>(
(x) => AddMessage(x.context));
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
_pool = new ObjectPool<ChatMessageView>(
CreateItem,
OnGetItem,
@@ -40,6 +41,21 @@ namespace BrewMonster.Scripts.ChatUI
scrollRect.onValueChanged.AddListener(OnScrollChanged);
}
private void OnDestroy()
{
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
}
private void OnChatMessageReceived(GameSession.ChatMessageEvent x)
{
Debug.Log("[Cuong] OnChatMessageReceived " + x.context);
ChatThreadDispatcher.Instance.Post(() =>
{
AddMessage(x.context);
});
}
ChatMessageView CreateItem()
{
var item = Instantiate(messagePrefab);
@@ -84,16 +100,13 @@ namespace BrewMonster.Scripts.ChatUI
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
if (!chatPanelUIGO.activeSelf)
return;
AddMessageView(msg);
if (_userAtBottom)
{
ScrollToBottom();
}
else
{
//newMessageIndicator.SetActive(true);
}
}
void AddMessageView(string msg)
@@ -150,5 +163,14 @@ namespace BrewMonster.Scripts.ChatUI
_visibleViews.Clear();
_messages.Clear();
}
public void OnHandlerChatButton()
{
bool open = !chatPanelUIGO.activeSelf;
chatPanelUIGO.SetActive(open);
if (open)
RefreshVisible();
}
}
}
@@ -0,0 +1,47 @@
using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Responsible for switching chat messages from background threads to Unity main thread.
/// Only handles chat related actions.
/// </summary>
public class ChatThreadDispatcher : MonoSingleton<ChatThreadDispatcher>
{
private static readonly ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>();
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
}
/// <summary>
/// Called from ANY thread (network thread safe)
/// </summary>
public void Post(Action action)
{
if (action == null)
return;
_queue.Enqueue(action);
}
private void Update()
{
while (_queue.TryDequeue(out var action))
{
try
{
action?.Invoke();
}
catch (Exception e)
{
Debug.LogError($"ChatThreadDispatcher error: {e}");
}
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 00392315a7ee47e7a9527eda504fb312
timeCreated: 1773310087
@@ -270,7 +270,7 @@ namespace BrewMonster.Common
{
m_UseItemCmdList.Clear();
// 重置 C2S 命令计时器
// C2S ʱ
m_CounterMap[(int)CommandID.USE_ITEM].Reset(true);
m_EnterSanctuaryList.Clear();
@@ -280,7 +280,7 @@ namespace BrewMonster.Common
m_PresentInfoList.Clear();
m_CounterMap[(int)CommandID.PLAYER_GIVE_PRESENT].Reset(true);
// 重置协议计时器
// Эʱ
m_GetPlayerBriefInfoList.Clear();
m_CounterMap2[(int)ProtocolType.PROTOCOL_GETPLAYERBRIEFINFO].Reset(true);
@@ -377,13 +377,13 @@ namespace BrewMonster.Common
getplayerbriefinfo p = m_GetPlayerBriefInfoList[0];
if (p.Playerlist.Count != 0)
{
// 获取第一个玩家id并向服务器发送协议
// ȡһidЭ
getplayerbriefinfo temp = p;
temp.Playerlist.Clear();
temp.Playerlist.Add(p.Playerlist[0]);
UnityGameSession.Instance.GameSession.SendProtocol(temp);
// 从列表中清除
// б
p.Playerlist.Remove(p.Playerlist[0]);
}
@@ -670,12 +670,12 @@ namespace BrewMonster.Common
}
// Send protocols ...
void SendGetPlayerBriefInfo(int iNumPlayer, int[] aIDs, int iReason)
public void SendGetPlayerBriefInfo(int iNumPlayer, int[] aIDs, int iReason)
{
if (iNumPlayer == 0 || aIDs == null || aIDs.Length == 0)
return;
// 1.合并添加到列表
// 1.ϲӵб
getplayerbriefinfo p = new getplayerbriefinfo();
p.Roleid = EC_Game.GetGameRun().GetHostPlayer().GetCharacterID();
p.Reason = (byte)iReason;
@@ -687,7 +687,7 @@ namespace BrewMonster.Common
if (p.Playerlist.Count > 0)
m_GetPlayerBriefInfoList.Add(p);
// 2.检查并发送
// 2.
SendCachedGetPlayerBriefInfo();
}
@@ -11,7 +11,8 @@ namespace BrewMonster.Scripts.Managers
public class EC_Inventory
{
// ===== Instance-based inventory (per-pack) =====
public static int IVTRTYPE_CLIENT_GENERALCARD_PACK = 1024; // ͻ˱ذ ڿͼҪѻÿͨ͡ΪͳһڵƷӱذ
// Item array: index is slot, null means empty.
private EC_IvtrItem[] m_aItems = Array.Empty<EC_IvtrItem>();
@@ -27,12 +28,6 @@ namespace BrewMonster.Scripts.Managers
IVTRTYPE_GENERALCARD_BOX = 7; // ư
};
// ע IVTRTYPE_CLIENT_GENERALCARD_PACK öֵܺ Inventory type ֵظ
public static class IVTRTYPE_PACK_CLIENT_GENERALCAR
{
public const int IVTRTYPE_CLIENT_GENERALCARD_PACK = 1024; // ͻ˱ذ ڿͼҪѻÿͨ͡ΪͳһڵƷӱذ
};
public EC_Inventory()
{
}
@@ -13,9 +13,8 @@ using UnityEngine;
//
///////////////////////////////////////////////////////////////////////////
public class CECObject : MonoBehaviour
public partial class CECObject : MonoBehaviour
{
protected static int ALPHA_HASH = Shader.PropertyToID("_Alpha");
protected Quaternion targetRotation;
protected Quaternion startRotation; // Store starting rotation for Slerp
@@ -1645,6 +1645,8 @@ namespace CSNetwork
gamedatasend.Data = C2SCommandFactory.CreateNakeCmd(C2SCommand.CommandID.ENTER_PK_PROTECTED);
SendProtocol(gamedatasend);
}
public const int SUPER_FAR_CRY_EMOTION_SET = 6;
/// <summary>
/// Convert string become byte (Encoding.Unicode trong .NET = UTF-16 LE)
/// </summary>
@@ -1654,16 +1656,85 @@ namespace CSNetwork
/// <param name="iSlot"></param>
public void SendChatData(byte cChannel, in string szMsg, int iPack, int iSlot)
{
EventBus.Publish(new EventChatMessageOnTopPlayer(szMsg));
EventBus.Publish(new ChatMessageEvent(szMsg));
publicchat publicChat = new publicchat();
publicChat.Channel = cChannel;
publicChat.Roleid = m_iCharID;
if (string.IsNullOrEmpty(szMsg))
return;
publicchat p = new publicchat();
p.Channel = cChannel;
p.Roleid = m_iCharID;
if (iPack == EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK)
{
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
EC_Inventory clientPack =
pHost != null ? pHost.GetPack(EC_Inventory.IVTRTYPE_CLIENT_GENERALCARD_PACK) : null;
if (pHost != null && clientPack != null)
{
EC_IvtrItem pCard = clientPack.GetItem(iSlot);
if (pCard != null)
{
short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_GENERALCARD_COLLECTION;
int cardId = pCard.GetTemplateID();
byte[] bytes = new byte[6];
BitConverter.GetBytes(cmd).CopyTo(bytes, 0);
BitConverter.GetBytes(cardId).CopyTo(bytes, 2);
Debug.Log($"[Cuong] SendChatData {bytes}");
p.Data.Replace(bytes);
}
}
}
else if (iPack >= 0 && iSlot >= 0)
{
byte[] bytes = new byte[5];
short cmd = (short)CHAT_S2C.EChatS2CCommand.CHAT_EQUIP_ITEM;
short index = (short)iSlot;
byte where = (byte)iPack;
BitConverter.GetBytes(cmd).CopyTo(bytes, 0);
bytes[2] = where;
BitConverter.GetBytes(index).CopyTo(bytes, 3);
p.Data.Replace(bytes);
}
byte[] unicodeBytes = Encoding.Unicode.GetBytes(szMsg);
publicChat.Msg.Replace(unicodeBytes);
SendProtocol(publicChat);
p.Msg.Replace(unicodeBytes);
SendProtocol(p);
if (cChannel is (byte)ChatChannel.GP_CHAT_LOCAL
or (byte)ChatChannel.GP_CHAT_FARCRY
or (byte)ChatChannel.GP_CHAT_SUPERFARCRY
or (byte)ChatChannel.GP_CHAT_BATTLE
or (byte)ChatChannel.GP_CHAT_COUNTRY) {
CECHostPlayer pHost = EC_Game.GetGameRun().GetHostPlayer();
//int nEmotionSet = pHost.GetCurEmotionSet();
int nEmotionSet = 0;
string strMsg = szMsg;
if (cChannel == (byte)ChatChannel.GP_CHAT_SUPERFARCRY)
{
if (strMsg.Length > 8)
{
strMsg = strMsg.Substring(0, strMsg.Length - 8);
}
nEmotionSet = SUPER_FAR_CRY_EMOTION_SET;
}
EC_IvtrItem pItem = null;
if (iPack >= 0 && iSlot >= 0) {
EC_Inventory pPack = pHost.GetPack(iPack);
if (pPack != null) {
pItem = pPack.GetItem(iSlot);
}
}
//pHost.SetLastSaidWords(strMsg, nEmotionSet, pItem);
Debug.Log("[Cuong] EventChatMessageOnTopPlayer " + strMsg);
EventBus.Publish(new EventChatMessageOnTopPlayer(pHost.GetCharacterID(),szMsg));
}
}
public void LoadConfigData()
{
getuiconfig p = new getuiconfig();
@@ -1698,10 +1769,51 @@ namespace CSNetwork
private bool OnPrtcWorldChat(Protocol pProtocol, bool bCalledagain)
{
worldchat p = (worldchat)pProtocol;
/*if (p.Channel == (byte)ChatChannel.GP_CHAT_FARCRY && p.Roleid == 24)
{
OnTaskChatMessage(p.Msg.RawBuffer, p.Msg.Size);
return true;
}*/
// TODO: Porting logic OnPrtcWorldChat hoàn chỉnh từ C++ if needed
Debug.Log("[Cuong] OnPrtcWorldChat");
return true;
}
/*private void OnTaskChatMessage(byte[] pBuf, int sz)
{
... logic ẩn ...
}*/
private bool OnBattleChatMessage(chatmessage p, List<int> pPendingFactions)
{
// Tương đương OnBattleChatMessage trong C++
// Hiện tại đơn giản hóa việc hiển thị, thực tế cần parse nội dung Battle cụ thể
Debug.Log($"[Battle Chat] RoleID: {p.Srcroleid}");
return true;
}
private bool OnFortressChatMessage(chatmessage p, List<int> pPendingFactions)
{
// Tương đương OnFortressChatMessage trong C++
Debug.Log($"[Fortress Chat] RoleID: {p.Srcroleid}");
return true;
}
private bool OnKingChatMessage(chatmessage p, bool bGetPlayerName = true)
{
// Tương đương OnKingChatMessage trong C++
Debug.Log($"[King Chat] RoleID: {p.Srcroleid}");
return true;
}
private void OnFactionPVPChatMessage(chatmessage p)
{
// Tương đương OnFactionPVPChatMessage trong C++
Debug.Log($"[FactionPVP Chat] RoleID: {p.Srcroleid}");
}
private bool OnPrtcChatMessage(Protocol pProtocol, bool bCalledagain)
{
CECGameUIMan pGameUI = EC_Game.GetGameRun().GetUIManager().GetInGameUIMan();
@@ -1716,20 +1828,20 @@ namespace CSNetwork
return true;
}
EC_IvtrItem pItem = CHAT_S2C.CreateChatItem(p.Data);
// Todo: Dead Game
///EC_IvtrItem pItem = CHAT_S2C.CreateChatItem(p.Data);
string szMsg = null;
string strTemp = Encoding.Unicode.GetString(p.Msg.ToArray(), 0, p.Msg.Length);
string strMsgOrigion = strTemp;
// Todo: Show Text on Ui Game
//strTemp = pGameUI.FilterInvalidTags(strTemp, pItem==NULL);
if (!Chat_GameSession.PolicyResolver(pProtocol, p, ref strTemp))
/*if (!Chat_GameSession.PolicyResolver(pProtocol, p, ref strTemp))
{
Debug.Log("[Cuong] 2");
return false;
}
/*
if (Chat_GameSession.HanldeGPChatSystem(p, bCalledagain))
{
Debug.Log("[Cuong] 3");
@@ -1740,19 +1852,54 @@ namespace CSNetwork
{
if (p.Channel == (byte)ChatChannel.GP_CHAT_SYSTEM && p.Srcroleid > 0)
{
Debug.Log("[Cuong] 4");
switch (p.Srcroleid)
{
case 1: case 2: case 3: case 4: case 6: case 7: // Battle Message
if (!bCalledagain)
{
List<int> pending = new List<int>();
if (!OnBattleChatMessage(p, pending))
{
// Handle pending faction info
return false;
}
}
break;
case 18: case 19: case 20: case 21: case 22: // Auction Message
// pGameUI.AddSysAuctionMessage(...)
Debug.Log("[Auction] " + strTemp);
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
break;
case 24: // Task Message
// OnTaskChatMessage(p.Data.RawBuffer, p.Data.Size);
break;
case 29: case 30: case 31: case 32: case 33: case 34:
case 35: case 36: case 37: case 38: case 39: case 40:
case 41: case 42: case 43: case 45: // Fortress Message
OnFortressChatMessage(p, null);
break;
case 46: case 47: case 48: case 49: // Country Battle
// OnCountryChatMessage(p);
break;
case 50: case 51: case 52: case 53: case 54:
case 55: case 56: case 57: case 58: case 59: // King Chat
OnKingChatMessage(p);
break;
case 60: case 61: case 62: case 63: case 64: // Faction PVP
OnFactionPVPChatMessage(p);
break;
}
}
else
{
Debug.Log("[Cuong] 5");
EventBus.Publish(new ChatMessageEvent(strTemp));
//EC_Game.GetGameRun().AddChatMessage(szMsg, p.Channel, p.Srcroleid, null, 0, p.Emotion);
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
}
}else if (p.Channel == (byte)ChatChannel.GP_CHAT_INSTANCE && p.Srcroleid == 1)
{
Debug.Log("[Cuong] 6");
Chat_GameSession.AUICTranslate trans;
//EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().AddHeartBeatHint(trans.Translate(szMsg ));
// Chat_GameSession.AUICTranslate trans;
// EC_Game.GetGameRun().GetUIManager().GetInGameUIMan().AddHeartBeatHint(trans.Translate(szMsg ));
}
else
{
@@ -1762,9 +1909,9 @@ namespace CSNetwork
if (ISPLAYERID(p.Srcroleid))
{
string szName = EC_Game.GetGameRun().GetPlayerName(p.Srcroleid, false);
Debug.Log("[Cuong] Other Msg " + szName);
if (szName == null)
if (string.IsNullOrEmpty(strTemp))
{
Debug.Log("[Cuong] Other 0");
if (!bCalledagain)
{
Chat_GameSession.AddElemForPendingProtocols(pProtocol);
@@ -1774,41 +1921,35 @@ namespace CSNetwork
}
else
{
Debug.Log("[Cuong] Other 1");
char[] szText = new char[80];
AUICommon.AUI_ConvertChatString(szName, szText, false);
string str = string.Format(
pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT),
szName,
szMsg
strTemp
);
// Convert to EventBus
/*EC_Game.GetGameRun().AddChatMessage(str, p.Channel, p.Srcroleid,
null, 0, p.Emotion, pItem, strMsgOrigion); */
EventBus.Publish(new ChatMessageEvent(str));
EC_Game.GetGameRun().AddChatMessage(strTemp, p.Channel, p.Srcroleid,
null, 0, p.Emotion, null, strMsgOrigion);
EventBus.Publish(new ChatMessageEvent(strTemp, p.Channel));
// Set player's last said words
CECPlayer pPlayer = EC_Game.GetGameRun().GetWorld().GetPlayerMan().GetPlayer(p.Srcroleid);
Debug.Log("[Cuong] name " + pPlayer == null ? "null" : "have name");
if (pPlayer != null)
{
if (p.Channel == (byte)ChatChannel.GP_CHAT_SUPERFARCRY) {
if (strTemp.Length > 8)
strTemp = strTemp.Substring(0, strTemp.Length - 8);
else
strTemp = "";
}
//pPlayer.SetLastSaidWords(strTemp, p.Emotion, pItem);
Debug.Log("[Cuong] " + pPlayer);
EventBus.Publish(new EventChatMessageOnTopPlayer(strTemp));
EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strTemp));
//pPlayer.SetLastSaidWords(strTemp, p.Emotion);
}
}
}
else if(ISNPCID(p.Srcroleid))
{
Debug.Log("[Cuong] ISNPCID " + strTemp);
CECNPC pNPC = EC_Game.GetGameRun().GetWorld().GetNPCMan().GetNPC(p.Srcroleid);
if (pNPC)
if (pNPC != null)
{
string str;
string template = pStrTab.GetWideString((int)FixedMsg.FIXMSG_CHAT2);
@@ -1816,17 +1957,17 @@ namespace CSNetwork
string message = string.Format(
template,
pNPC.GetName(),
szMsg
strTemp
);
/*EC_Game.GetGameRun().AddChatMessage(
EC_Game.GetGameRun().AddChatMessage(
message,
p.Channel,
p.Srcroleid,
null,
0,
p.Emotion
);*/
);
EventBus.Publish(new ChatMessageEvent(message));
CECUIHelper.RemoveNameFlagFromNPCChat(strTemp, out szMsg);
@@ -1841,10 +1982,12 @@ namespace CSNetwork
public struct ChatMessageEvent
{
public string context;
public byte channel;
public ChatMessageEvent(string context)
public ChatMessageEvent(string context, byte channel = 0)
{
this.context = context;
this.channel = channel;
}
}
public void OnPrtcGetConfigRe(Protocol pProtocol)
+52 -5
View File
@@ -1,5 +1,8 @@
using System;
using System.Threading;
using BrewMonster.Scripts.ChatUI;
using CSNetwork.GPDataType;
using Cysharp.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -16,6 +19,10 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
[Header("References")]
[SerializeField] private CECPlayer hostplayer;
[SerializeField] private float chatDisplayDuration = 5f;
private CancellationTokenSource _chatCts;
private const string DefaultLayerName = "Default";
private bool _isVisible;
@@ -38,17 +45,19 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
SetVisible(true);
RefreshName();
EventBus.Subscribe<EventChatMessageOnTopPlayer>((x)=> SetChatMessage(x));
EventBus.SubscribeChannel<EventChatMessageOnTopPlayer>(hostplayer.GetCharacterID(), SetChatMessage);
EventBus.SubscribeChannel<cmd_self_info_00>(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI);
}
private void OnDestroy()
{
_chatCts?.Cancel();
_chatCts?.Dispose();
if (hostplayer == null)
{
return;
}
EventBus.Unsubscribe<EventChatMessageOnTopPlayer>(SetChatMessage);
EventBus.UnsubscribeChannel<EventChatMessageOnTopPlayer>(hostplayer.GetCharacterID(), SetChatMessage);
EventBus.UnsubscribeChannel<cmd_self_info_00>(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI);
}
@@ -127,21 +136,59 @@ namespace BrewMonster.PerfectWorld.Scripts.UI
private void SetChatMessage(EventChatMessageOnTopPlayer cxt)
{
Debug.Log("[Cuong] SetChatMessage");
if (chatText == null)
{
BMLogger.LogError("Don't have chatText TMProUI");
return;
}
chatText.text = cxt.context;
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(string context)
public EventChatMessageOnTopPlayer(int roleId, string context)
{
this.roleId = roleId;
this.context = context;
}
}
+328 -21
View File
@@ -36,6 +36,142 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -20, y: -20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &964780034169504921
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7133716985767026273}
- component: {fileID: 7030320337240937342}
- component: {fileID: 6160464800274915712}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7133716985767026273
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 964780034169504921}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4454076196230765805}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7030320337240937342
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 964780034169504921}
m_CullTransparentMesh: 1
--- !u!114 &6160464800274915712
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 964780034169504921}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Chat
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4281479730
m_fontColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 36
m_fontSizeBase: 36
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 1
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1002556096278408428
GameObject:
m_ObjectHideFlags: 0
@@ -291,6 +427,42 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Padding: {x: -8, y: -5, z: -8, w: -5}
m_Softness: {x: 0, y: 0}
--- !u!1 &2806664982819397903
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4963429530816417249}
m_Layer: 5
m_Name: Panel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4963429530816417249
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2806664982819397903}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4454076196230765805}
m_Father: {fileID: 6199635200021499044}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &4190358850504021446
GameObject:
m_ObjectHideFlags: 0
@@ -302,7 +474,6 @@ GameObject:
- component: {fileID: 1983722419643715407}
- component: {fileID: 5213722908587404148}
- component: {fileID: 343348515878574983}
- component: {fileID: 7869768243714293468}
- component: {fileID: 4104602819061927609}
m_Layer: 5
m_Name: Chat_PanelUI
@@ -369,23 +540,6 @@ MonoBehaviour:
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7869768243714293468
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4190358850504021446}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5790dcd71bafcec4697f10b5366bec2c, type: 3}
m_Name:
m_EditorClassIdentifier:
scrollRect: {fileID: 5786096855155260472}
content: {fileID: 6422370174043654984}
messagePrefab: {fileID: 1976417251556044024, guid: dcc75569599675f46a99bc66a87efc9a, type: 3}
maxVisibleMessages: 30
maxStoredMessages: 2000
--- !u!114 &4104602819061927609
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -399,7 +553,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
inputField: {fileID: 2725480406471239880}
sendButton: {fileID: 0}
maxChatLength: 256
--- !u!1 &4425286880128952433
GameObject:
m_ObjectHideFlags: 0
@@ -946,6 +1100,139 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &6847709244521616304
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4454076196230765805}
- component: {fileID: 5054963699385999060}
- component: {fileID: 8898467712372273739}
- component: {fileID: 8167470961996667984}
m_Layer: 5
m_Name: Button
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4454076196230765805
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6847709244521616304}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7133716985767026273}
m_Father: {fileID: 4963429530816417249}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -746.7, y: 304}
m_SizeDelta: {x: 160, y: 50}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5054963699385999060
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6847709244521616304}
m_CullTransparentMesh: 1
--- !u!114 &8898467712372273739
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6847709244521616304}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &8167470961996667984
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6847709244521616304}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8898467712372273739}
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 5696601840968024233}
m_TargetAssemblyTypeName: BrewMonster.Scripts.ChatUI.ChatPanelUI, Assembly-CSharp
m_MethodName: OnHandlerChatButton
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!1 &7245224818833188742
GameObject:
m_ObjectHideFlags: 0
@@ -958,6 +1245,7 @@ GameObject:
- component: {fileID: 1112302134216474585}
- component: {fileID: 4776314709450025773}
- component: {fileID: 7685278791772274093}
- component: {fileID: 5696601840968024233}
m_Layer: 5
m_Name: ChatCanvas
m_TagString: Untagged
@@ -977,6 +1265,7 @@ RectTransform:
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4963429530816417249}
- {fileID: 1983722419643715407}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -1048,6 +1337,24 @@ MonoBehaviour:
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &5696601840968024233
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7245224818833188742}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5790dcd71bafcec4697f10b5366bec2c, type: 3}
m_Name:
m_EditorClassIdentifier:
scrollRect: {fileID: 5786096855155260472}
chatPanelUIGO: {fileID: 4190358850504021446}
content: {fileID: 6422370174043654984}
messagePrefab: {fileID: 1976417251556044024, guid: dcc75569599675f46a99bc66a87efc9a, type: 3}
maxVisibleMessages: 30
maxStoredMessages: 2000
--- !u!1 &7942261885143240354
GameObject:
m_ObjectHideFlags: 0
@@ -1252,8 +1559,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 15}
m_SizeDelta: {x: 0, y: -30}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7307691078876554531
CanvasRenderer:
@@ -0,0 +1,46 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1735791580658873054
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1508221814777778566}
- component: {fileID: 5062616109990871891}
m_Layer: 0
m_Name: ChatThreadDispatcher
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1508221814777778566
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1735791580658873054}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5062616109990871891
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1735791580658873054}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 00392315a7ee47e7a9527eda504fb312, type: 3}
m_Name:
m_EditorClassIdentifier:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 820475c3b63a5de4b9b311fe9e0de9b5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+216 -5
View File
@@ -1,19 +1,41 @@
using UnityEngine;
using UnityEngine;
using TMPro;
using BrewMonster;
using BrewMonster.Network;
using CSNetwork.GPDataType;
using EditorAttributes;
using System;
using System.Collections.Generic;
using System.Text;
using BrewMonster.Scripts.Task;
namespace BrewMonster.Scripts.ChatUI
{
/// <summary>
/// Handles chat input from TMP_InputField and provides task-sharing functionality.
/// Converted from CDlgTaskTrace::OnCommand_Chat (DlgTaskTrace.cpp line 888).
/// </summary>
public class ChatInputHandler : MonoBehaviour
{
[Header("UI References")]
[Header("UI References")]
public TMP_InputField inputField; // Ô gõ text
[Header("Chat Settings")]
[Tooltip("Giới hạn số ký tự tối đa cho mỗi tin nhắn chat")]
public int maxChatLength = 256;
// ===== Constants from C++ source =====
private const string INDENTATION = " ";
private const int MAX_ITEM_WANTED = 10;
private const int MAX_MONSTER_WANTED = 3;
private const int MAX_PLAYER_WANTED = MAX_MONSTER_WANTED;
private void Start()
{
inputField.onSubmit.AddListener(OnSubmit);
// Giới hạn ký tự trực tiếp trên InputField
if (inputField.characterLimit <= 0 || inputField.characterLimit > maxChatLength)
inputField.characterLimit = maxChatLength;
}
private void OnDestroy()
@@ -21,7 +43,7 @@ namespace BrewMonster.Scripts.ChatUI
inputField.onSubmit.RemoveListener(OnSubmit);
}
// Khi nhấn Enter
// ===== Khi nhấn Enter =====
private void OnSubmit(string text)
{
if (string.IsNullOrWhiteSpace(text))
@@ -30,9 +52,15 @@ namespace BrewMonster.Scripts.ChatUI
HandleUserInput(text);
}
// Hàm xử lý input (gửi server)
// ===== Hàm xử lý input (gửi server) =====
private void HandleUserInput(string text)
{
// Áp dụng giới hạn ký tự
text = TruncateText(text, maxChatLength);
// Lọc từ cấm (bad words filter)
CECUIManager.Instance?.FilterBadWords(ref text);
UnityGameSession.SendChatData(
(byte)ChatChannel.GP_CHAT_LOCAL,
text,
@@ -52,5 +80,188 @@ namespace BrewMonster.Scripts.ChatUI
HandleUserInput(text);
}
// =================================================================
// SECTION: OnCommand_Chat - Chia sẻ trạng thái nhiệm vụ lên chat
// Converted from CDlgTaskTrace::OnCommand_Chat (DlgTaskTrace.cpp)
// =================================================================
/// <summary>
/// Builds a chat message describing task progress and sends it to the appropriate channel.
/// Gọi hàm này khi người chơi bấm nút "Chia sẻ nhiệm vụ" trên giao diện Task Trace.
/// </summary>
/// <param name="idTask">ID của nhiệm vụ cần chia sẻ.</param>
public void OnCommand_Chat(uint idTask)
{
// TODO: Replace with actual EC_Game Unity Wrapper when available
// CECHostPlayer pHost = EC_Game.GetGameRun()?.GetHostPlayer();
// CECTaskInterface pTask = pHost.GetTaskInterface();
// ATaskTempl pTemp = pTask.GetTaskTemplMan().GetTaskTemplByID(idTask);
// --- MOCK DATA FOR COMPILATION ---
bool bActiveTask = true;
StringBuilder sb = new StringBuilder();
// string strName = pTemp.GetName();
string strName = $"[Task_{idTask}]";
strName = DeleteColorStr(strName);
sb.Append(strName);
/*
// --- Lấy thông tin trạng thái task ---
Task_State_info tsi = new Task_State_info();
pTask.GetTaskStateInfo(idTask, ref tsi, bActiveTask);
// --- NPC trao thưởng (nếu có thể hoàn thành task) ---
int nANPC = (int)pTemp.GetAwardNPC();
if (nANPC > 0 && pTask.CanFinishTask(idTask))
...
*/
// --- MOCK LOGIC TO APPEND PROGRESS ---
sb.Append(INDENTATION);
sb.AppendFormat(GetStringFromTable(248), GetItemName(123), 2, 10); // Mock item progress
// --- Cắt bớt trailing '\r' ---
string strText = sb.ToString().TrimEnd('\r');
// --- Áp dụng giới hạn ký tự ---
strText = TruncateText(strText, maxChatLength);
// --- Xác định kênh chat: Team nếu có đội, Local nếu không ---
byte channel = (byte)ChatChannel.GP_CHAT_LOCAL;
// --- Gửi lên server ---
UnityGameSession.SendChatData(channel, strText, 0, 0);
Debug.Log($"[ChatInputHandler] OnCommand_Chat: Sent mock task {idTask} to channel {channel}, length={strText.Length}");
}
// =================================================================
// SECTION: Helper methods
// =================================================================
/// <summary>
/// Cắt chuỗi nếu vượt quá giới hạn ký tự, thêm dấu "..." ở cuối.
/// </summary>
private static string TruncateText(string text, int maxLength)
{
if (string.IsNullOrEmpty(text) || maxLength <= 0)
return text;
if (text.Length <= maxLength)
return text;
// Trừ 3 ký tự để chèn "..."
if (maxLength > 3)
return text.Substring(0, maxLength - 3) + "...";
else
return text.Substring(0, maxLength);
}
/// <summary>
/// Xóa các mã màu "^xxxxxx" ra khỏi chuỗi.
/// Tương đương CDlgTaskTrace::DeleteColorStr trong C++.
/// </summary>
private static string DeleteColorStr(string str)
{
if (string.IsNullOrEmpty(str))
return str;
StringBuilder result = new StringBuilder(str.Length);
for (int i = 0; i < str.Length; i++)
{
if (str[i] == '^' && i + 6 < str.Length)
{
// Kiểm tra 6 ký tự tiếp theo có phải hex không
bool isColorCode = true;
for (int j = 1; j <= 6; j++)
{
char c = str[i + j];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
{
isColorCode = false;
break;
}
}
if (isColorCode)
{
i += 6; // bỏ qua 6 ký tự hex
continue;
}
}
result.Append(str[i]);
}
return result.ToString();
}
/// <summary>
/// Format thời gian cho task (tương đương CDlgTask::FormatTime trong C++).
/// </summary>
private static string FormatTime(int nSec, string format, int totalTime)
{
int hours = nSec / 3600;
int minutes = (nSec % 3600) / 60;
int seconds = nSec % 60;
string timeStr;
if (hours > 0)
timeStr = $"{hours:D2}:{minutes:D2}:{seconds:D2}";
else
timeStr = $"{minutes:D2}:{seconds:D2}";
if (!string.IsNullOrEmpty(format))
{
try { return string.Format(format, timeStr); }
catch { return timeStr; }
}
return timeStr;
}
/// <summary>
/// Lấy string từ bảng String Table (StringFromTable).
/// Tương đương pUIMan->GetStringFromTable(id) trong C++.
/// </summary>
private static string GetStringFromTable(int id)
{
// Sử dụng hệ thống StringTable có sẵn trong project
var gameUI = CECUIManager.Instance?.GetInGameUIMan();
string result = gameUI?.GetStringFromTable(id);
return result ?? $"[STR_{id}]";
}
/// <summary>
/// Lấy tên NPC theo ID từ ElementDataMan.
/// </summary>
private static string GetNPCName(int npcId)
{
// TODO: Kết nối với ElementDataMan của bạn khi đã port xong
// var pDataMan = EC_Game.GetElementDataMan();
// return pDataMan?.GetNPCName(npcId);
return $"NPC_{npcId}";
}
/// <summary>
/// Lấy tên Monster theo ID từ ElementDataMan.
/// </summary>
private static string GetMonsterName(int monsterId)
{
// TODO: Kết nối với ElementDataMan của bạn khi đã port xong
// var pDataMan = EC_Game.GetElementDataMan();
// return pDataMan?.GetMonsterName(monsterId);
return $"Monster_{monsterId}";
}
/// <summary>
/// Lấy tên Item theo ID từ ElementDataMan.
/// </summary>
private static string GetItemName(int itemId)
{
// TODO: Kết nối với ElementDataMan của bạn khi đã port xong
// var pDataMan = EC_Game.GetElementDataMan();
// return pDataMan?.GetItemName(itemId);
return $"Item_{itemId}";
}
}
}