Files
2026-04-30 16:29:22 +07:00

929 lines
39 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using BrewMonster.Assets.PerfectWorld.Scripts.UI.GamePlay;
using BrewMonster.Managers;
using BrewMonster.Scripts.Task;
using BrewMonster.Scripts.Task.UI;
using BrewMonster.Scripts.UI;
using System.Collections.Generic;
using System.Linq;
using BrewMonster.Network;
using BrewMonster.Scripts;
using CSNetwork;
using CSNetwork.GPDataType;
using PerfectWorld.UI.MiniMap;
using UnityEngine;
using BrewMonster.PerfectWorld.Scripts.UI;
using BrewMonster.Scripts.Chat;
using BrewMonster.Scripts.Chat.EmotionData;
namespace BrewMonster.UI
{
public class CECGameUIMan : AUIManager
{
public const int LAYOUTDATA_VERSION = 15;
DlgNPC m_pDlgNPC;
CDlgPetList m_pDlgPetList;
public NPC_ESSENCE? m_pCurNPCEssence;
public int m_idCurFinishTask = -1;
private DlgTask m_pDlgTask;
private Dictionary<byte, (string, Sprite[])> m_IconMap;
private const string SKILL_ICONLIST_NAME = "iconlist_skill_multisprite";
private const string ACTION_ICONLIST_NAME = "ActionIcon/iconlist_action_multisprite";
private const string INVENTORY_ICONLIST_NAME = "UI/IconSprites/iconlist_ivtrm_multisprite";
private const string STATE_ICONLIST_NAME = "iconlist_state";
private const string SKILL_GROUP_ICON_NAME = "SkillGroupIcon/iconlist_skillgrp_multisprite";
public CDlgMiniMap m_pDlgMiniMap;
//TODO: There're some managers here. We probably need to implement them in the future.
// CECCustomizeMgr *m_CustomizeMgr;
// CECHomeDlgsMgr *m_HomeDlgsMgr;
// CECMiniBarMgr *m_pMiniBarMgr;
private CECMapDlgsMgr m_pMapDlgsMgr;
// CECShortcutMgr *m_pShortcutMgr;
// CECIconStateMgr *m_pIconStateMgr;
private readonly ChatEmotionDisplayPipeline _chatEmotionPipeline = new ChatEmotionDisplayPipeline();
/// <summary>
/// Gọi từ <see cref="CECUIManager"/> (MonoBehaviour) trước <see cref="Init"/> — CECGameUIMan không phải component nên không kéo SO trên Inspector được.
/// Called from CECUIManager before Init; plain class cannot use Inspector SerializeField.
/// </summary>
public void SetEmotionSpriteMap(IEmotionSpriteMap map)
{
if (map is EmotionLibrarySpriteMap libMap)
libMap.EnsureCachedTmpSpriteAssetNames();
_chatEmotionPipeline.SetSpriteMap(map);
}
/// <summary>
/// 服务器下发的 wire 正文 → TMP(表情/坐标/物品等内联)— 供 GameSession 等不经过 AddChatMessage 的 UI 路径。
/// Server wire body → TMP (inline emotion/coord/item) — for UI paths that do not go through AddChatMessage.
/// </summary>
public string ConvertWireChatBodyForDisplay(string wireBody, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return wireBody;
string f = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
return _chatEmotionPipeline.ConvertInlineItemsToTmp(f);
}
/// <summary>
/// 仅正文 wire(无 printf 包装)→ 头顶气泡 TMP — 与 AddChatMessage 内 FilterBadWords + 内联转 TMP 一致。
/// Wire body only (no printf wrapper) → head-bubble TMP — same as AddChatMessage FilterBadWords + inline to TMP.
/// </summary>
public string ConvertWireBodyForHeadBubble(string wireBody, ChatChannel cChannel, int idPlayer, int cEmotion)
{
if (string.IsNullOrEmpty(wireBody))
return wireBody;
string s = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion);
if (string.IsNullOrEmpty(s))
return s;
bool isPlayerChannel = cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_FACTION
|| cChannel == ChatChannel.GP_CHAT_WHISPER
|| cChannel == ChatChannel.GP_CHAT_TRADE
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
if (isPlayerChannel && idPlayer > 0)
CECUIManager.Instance.FilterBadWords(ref s);
return _chatEmotionPipeline.ConvertInlineItemsToTmp(s);
}
/// <summary>
/// Kênh có bong bóng trên đầu trong AddChatMessage — dùng để tránh double-publish với PROTOCOL_WORLDCHAT.
/// Channels that get head bubble from AddChatMessage — avoids double-publish with PROTOCOL_WORLDCHAT.
/// </summary>
public static bool ChannelShowsChatBubbleAboveHead(ChatChannel cChannel)
{
return cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
}
// Layout/settings flags for GetUserLayout (saved to server)
// 布局/设置标志,用于 GetUserLayout(保存到服务器)
private bool m_bAutoReply;
private bool m_bOnlineNotify;
private bool m_bSaveHistory;
private bool m_bShowItemDescCompare = true;
private bool m_bShowLowHP = true;
private bool m_bShowTargetOfTarget = true;
// TODO: System module quick bar dialog GetMiniMode() not yet wired in C#
private AUIDialog m_pDlgSysModuleQuickBar;
// TODO: System bar dialog IsShow() for bMenuMode
private AUIDialog m_pDlgSystemb;
public static bool TALKPROC_IS_TERMINAL(uint id)
{
return ((id & 0x80000000u) != 0) && ((id & 0x40000000u) != 0);
}
public static bool TALKPROC_IS_FUNCTION(uint id)
{
return ((id) & 0x80000000) != 0;
}
public static uint TALKPROC_GET_FUNCTION_ID(uint id)
{
return ((id) & 0x7FFFFFFF);
}
public void PopupNPCDialog(NPC_ESSENCE pEssence)
{
var dialogueNpc = CECUIManager.Instance.ShowUI("DialogNPC");
if (m_pDlgNPC == null)
{
if (dialogueNpc == null)
{
BMLogger.LogError("can not get dialogue npc");
return;
}
m_pDlgNPC = dialogueNpc.GetComponent<DlgNPC>();
m_pDlgNPC.SetAUIManager(this);
}
m_pDlgNPC.PopupDialog(pEssence);
}
public void PopupNPCDialog(talk_proc pTalk)
{
var dialogueNpc = CECUIManager.Instance.ShowUI("DialogNPC");
if (m_pDlgNPC == null)
{
if (dialogueNpc == null)
{
BMLogger.LogError("can not get dialogue npc");
return;
}
m_pDlgNPC = dialogueNpc.GetComponent<DlgNPC>();
m_pDlgNPC.SetAUIManager(this);
}
m_pDlgNPC.PopupNPCDialog(pTalk);
}
/// <summary>
/// For what: This is for QuickBar (Skill + Action + Combo)
/// </summary>
public CECShortcutSet GetSCSByDlg(int indexPanel)
{
CECHostPlayer pHost = CECGameRun.Instance.GetHostPlayer();
CDlgQuickBar cDlgQuickBar = CECUIManager.Instance.GetCDlgQuickBar();
CECShortcutSet pSCS = null;
int index = (0);
if (indexPanel == 1)
{
int panel = (index < 0 ? cDlgQuickBar.GetCurPanel1() : index) - 1;
pSCS = pHost.GetShortcutSet1(0);
}
else
{
int panel = (index < 0 ? cDlgQuickBar.GetCurPanel2() : index) - 1;
pSCS = pHost.GetShortcutSet2(panel);
}
return pSCS;
}
// 弹出任务完成对话框(到达/离开地点等触发) // Popup task-finish dialog (reach/leave site, etc.)
// C++: pTask->PopupTaskFinishDialog(taskId, &awardTalk); then OnUIDialogEnd() notifies server.
public bool PopupTaskFinishDialog(uint taskId, talk_proc pTalk)
{
if (pTalk.num_window == 0) return false;
var dialogueNpc = CECUIManager.Instance.ShowUI("DialogNPC");
if (m_pDlgNPC == null)
{
if (dialogueNpc == null)
{
BMLogger.LogError("can not get dialogue npc");
return false;
}
m_pDlgNPC = dialogueNpc.GetComponent<DlgNPC>();
m_pDlgNPC.SetAUIManager(this);
}
m_idCurFinishTask = (int)taskId;
m_pDlgNPC.PopupNPCDialog(pTalk);
m_pDlgNPC.SetData(DlgNPC.NPC_DIALOG.NPC_DIALOG_TASK_TALK, "");
return true;
}
public void EndNPCService()
{
m_pCurNPCEssence = null;
//EC_Game.GetGameRun().GetHostPlayer().EndNPCService();
EC_ManMessageMono.Instance.EC_ManPlayer.GetHostPlayer().EndNPCService();
}
public bool UpdateTask(uint idTask, int reason)
{
DlgTaskTrace pDlg = GetDialog("Win_QuestMinion").GetComponent<DlgTaskTrace>();
if (pDlg) {
//pDlg->SetBtnUnTraceY(-1, 0);
pDlg.UpdateContributionTask();
if (reason == TaskTemplConstants.TASK_SVR_NOTIFY_NEW)
pDlg.OnTaskNew(idTask);
}
// TODO
// ´
// if (reason == TaskTemplConstants.TASK_SVR_NOTIFY_NEW)
// {
// m_pDlgQuestionTask.AddQuestionTask(idTask);
// }
// else if (reason == TaskTemplConstants.TASK_SVR_NOTIFY_COMPLETE || reason == TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP)
// {
// m_pDlgQuestionTask.RemoveQuestionTask(idTask);
// }
if (reason == TaskTemplConstants.TASK_SVR_NOTIFY_STORAGE)
{
// TODO
// CDlgTaskList* pDlg = (CDlgTaskList*)GetDialog("Win_QuestList");
// if (pDlg && pDlg.IsShow())
// {
// // refresh data in OnShow()
// pDlg.RefreshTaskList();
// }
return true;
}
else
{
// zhangyitian 20140521
// ʱɽбҲҪ£˿ɽбµ
return m_pDlgTask.UpdateQuestView();
}
}
/// <summary>
/// Call after CECTaskInterface.Init completes (TASK_DATA). Restores/applies quest trace via DlgTask.SyncTrace and refreshes minimion.
/// 在 CECTaskInterface.InitTASK_DATA)完成后调用,通过 DlgTask.SyncTrace 应用任务追踪并刷新小窗。
/// </summary>
public void OnHostTaskDataInitialized(CECTaskInterface pTask)
{
if (m_pDlgTask == null || pTask == null)
return;
m_pDlgTask.SyncTraceAfterTaskDataInit(pTask);
}
public DialogScriptTableObject GetDialogResource()
{
return m_dialogResouce;
}
public Canvas GetCanvas()
{
return m_canvas;
}
public void EnableUI(bool bEnable)
{
}
/// <summary>
/// Get user layout data for saving to server. Fills USER_LAYOUT and optionally writes to pData.
/// 获取用户布局数据用于保存到服务器。填充 USER_LAYOUT,并可选写入 pData。
/// </summary>
public void GetUserLayout(byte[] pData, int startIndex, ref uint dwUISize)
{
CECHostPlayer pHost = EC_Game.GetGameRun()?.GetHostPlayer();
if (pHost == null)
{
dwUISize = 0;
return;
}
// Build and fill USER_LAYOUT (ul)
USER_LAYOUT ul = new USER_LAYOUT();
// Initialize array fields (C# class does not auto-init these)
ul.bChecked1 = new byte[HostCfgConstants.NUM_HOSTSCSETS1];
ul.bChecked2 = new byte[HostCfgConstants.NUM_HOSTSCSETS2];
ul.a_Mark = new SAVE_MARK[EC_GameUIMan_Constants.CECGAMEUIMAN_MAX_MARKS];
ul.idGroup = new int[EC_GameUIMan_Constants.CECGAMEUIMAN_MAX_GROUPS];
ul.clrGroup = new uint[EC_GameUIMan_Constants.CECGAMEUIMAN_MAX_GROUPS];
ul.a_MarkMapID = new short[EC_GameUIMan_Constants.CECGAMEUIMAN_MAX_MARKS];
for (int k = 0; k < ul.a_Mark.Length; k++)
{
ul.a_Mark[k].szName = new ushort[EC_GameUIMan_Constants.CECGAMEUIMAN_MARK_NAME_LEN + 1];
ul.a_MarkMapID[k] = 1; // C++ inits to 1 per element
}
ul.nVersion = (sbyte)LAYOUTDATA_VERSION;
ul.nMapMode = (sbyte)(m_pDlgMiniMap != null ? m_pDlgMiniMap.GetMode() : 0);
// TODO: Win_QuickbarPetV dialog bQuickbarPetMode not wired in C# yet
// TODO: 快捷栏宠物模式对话框尚未在 C# 中接入
ul.bQuickbarPetMode = false;
// TODO : check later bQuickbar1Mode and bChecked1 from Win_Quickbar*Ha_* dialogs
// string dlgName = $"Win_Quickbar{HostCfgConstants.SIZE_HOSTSCSET1}Ha";
// AUIDialog dlgQuickbar1Ha = GetDialog(dlgName);
// ul.bQuickbar1Mode = dlgQuickbar1Ha != null && dlgQuickbar1Ha.IsShow();
ul.bQuickbar1Mode = false;
for (int i = 0; i < HostCfgConstants.NUM_HOSTSCSETS1; i++)
{
// TODO: check later Chk_Normal checkbox per quickbar panel
// TODO: 后续核对 每个快捷栏面板的 Chk_Normal 勾选状态
ul.bChecked1[i] = 0;
}
// TODO: check later bQuickbar2Mode and bChecked2 from Win_Quickbar*Hb_* dialogs
ul.bQuickbar2Mode = false;
for (int i = 0; i < HostCfgConstants.NUM_HOSTSCSETS2; i++)
ul.bChecked2[i] = 0;
if (m_pDlgTask != null)
m_pDlgTask.SyncTrace(ul, false);
CDlgQuickBar dlgQuickBar = CECUIManager.Instance?.GetCDlgQuickBar();
if (dlgQuickBar != null)
{
ul.bQuickbarShowAll1 = dlgQuickBar.m_bShowAll1;
ul.bQuickbarShowAll2 = dlgQuickBar.m_bShowAll2;
ul.nQuickbarCurPanel1 = dlgQuickBar.m_nCurPanel1;
ul.nQuickbarCurPanel2 = dlgQuickBar.m_nCurPanel2;
ul.nQuickbarDisplayPanels1 = dlgQuickBar.m_nDisplayPanels1;
ul.nQuickbarDisplayPanels2 = dlgQuickBar.m_nDisplayPanels2;
}
// TODO: chat window size/color m_pDlgChat not wired in C#
ul.nChatWinSize = 1;
ul.nCurChatColor = 1;
// MiniMap marks
if (m_pDlgMiniMap != null)
{
var marks = m_pDlgMiniMap.GetMarks();
int markCount = Mathf.Min(marks.Count, EC_GameUIMan_Constants.CECGAMEUIMAN_MAX_MARKS);
for (int i = 0; i < markCount; i++)
{
var m = marks[i];
ul.a_Mark[i].nNPC = m.nNPC;
ul.a_Mark[i].vecPos = m.vecPos;
ul.a_MarkMapID[i] = (short)m.mapID;
CopyStringToUshortArray(ul.a_Mark[i].szName, m.strName ?? "", EC_GameUIMan_Constants.CECGAMEUIMAN_MARK_NAME_LEN);
}
}
// TODO: friend group layout CECFriendMan / GetFriendMan() not available in C# yet
// TODO: 好友分组布局 C# 中尚未提供 CECFriendMan / GetFriendMan()
// Placeholder: idGroup / clrGroup remain zeroed (already initialized)
ul.bAutoReply = m_bAutoReply;
ul.bOnlineNotify = m_bOnlineNotify;
ul.bSaveHistory = m_bSaveHistory;
// TODO: current system module shortcut set index GetCurSysModShortcutSetIndex() not in C# host
// TODO: 当前使用的系统模块快捷栏索引 – C# 宿主中暂无 GetCurSysModShortcutSetIndex()
ul.ucCurSystemModuleSC = 0;
// TODO: m_pDlgSysModuleQuickBar GetMiniMode() not wired
ul.bSystemModuleQuickBarMini = m_pDlgSysModuleQuickBar != null && GetDlgSysModuleQuickBarMiniMode();
// TODO: m_pDlgSystemb system bar show state
ul.bMenuMode = m_pDlgSystemb != null && m_pDlgSystemb.IsShow();
ul.bShowCompareDesc = m_bShowItemDescCompare;
ul.bShowLowHP = m_bShowLowHP;
ul.bShowTargetOfTarget = m_bShowTargetOfTarget;
dwUISize = EC_GameUIMan_Constants.USER_LAYOUT_SIZE; // Match C++ sizeof(USER_LAYOUT)=344
if (pData != null)
{
// Serialize USER_LAYOUT to binary (344 bytes) and copy into pData (match C++ memcpy)
byte[] layoutBytes = ul.ToBytes();
Array.Copy(layoutBytes, 0, pData, startIndex, (int)dwUISize);
}
}
/// <summary>Copy string into fixed ushort[] (for SAVE_MARK.szName).</summary>
private static void CopyStringToUshortArray(ushort[] dest, string str, int maxLen)
{
if (dest == null || dest.Length < maxLen + 1) return;
int len = System.Math.Min(str?.Length ?? 0, maxLen);
for (int i = 0; i < len; i++)
dest[i] = (ushort)str[i];
for (int i = len; i < dest.Length; i++)
dest[i] = 0;
}
/// <summary>TODO: Wire to system module quick bar mini mode when dialog is available.</summary>
private bool GetDlgSysModuleQuickBarMiniMode()
{
return false;
}
/// <summary>Quest main dialog (Win_Quest), created in Init. / 任务主界面,Init 时创建。</summary>
public DlgTask GetDlgTask()
{
return m_pDlgTask;
}
public override void Init()
{
base.Init();
m_IconMap = new Dictionary<byte, (string, Sprite[])>();
m_pDlgTask = GetDialog(CECUIHelper.DlgTaskName).GetComponent<DlgTask>();
m_pDlgTask.Show(false);
m_pDlgMiniMap = GameObject.FindObjectsByType<CDlgMiniMap>( FindObjectsSortMode.None).FirstOrDefault();
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILL] = (SKILL_ICONLIST_NAME, Resources.LoadAll<Sprite>(SKILL_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_ACTION] = (ACTION_ICONLIST_NAME, Resources.LoadAll<Sprite>(ACTION_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_INVENTORY] = (INVENTORY_ICONLIST_NAME, Resources.LoadAll<Sprite>(INVENTORY_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_STATE] = (STATE_ICONLIST_NAME, Resources.LoadAll<Sprite>(STATE_ICONLIST_NAME));
m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILLGRP] = (SKILL_GROUP_ICON_NAME, Resources.LoadAll<Sprite>(SKILL_GROUP_ICON_NAME));
m_pMapDlgsMgr = new CECMapDlgsMgr();
}
public CECMapDlgsMgr GetMapDlgsMgr()
{
return m_pMapDlgsMgr;
}
public AUIImagePictureBase SetCover(AUIImagePictureBase pImgPic, string nameImage, EC_GAMEUI_ICONS iCONS_TYPE)
{
if (pImgPic == null)
return pImgPic;
if (m_IconMap == null || !m_IconMap.TryGetValue((byte)iCONS_TYPE, out var tuple) || tuple.Item2 == null)
{
pImgPic.Clear();
return pImgPic;
}
var sprite = tuple.Item2.FirstOrDefault(s => s != null && s.name == nameImage);
if (sprite != null)
pImgPic.SetImage(sprite);
else
pImgPic.Clear();
return pImgPic;
}
public Sprite GetIcon(string nameImage, EC_GAMEUI_ICONS iCONS_TYPE)
{
if (m_IconMap == null || !m_IconMap.TryGetValue((byte)iCONS_TYPE, out var tuple) || tuple.Item2 == null)
{
return null;
}
return tuple.Item2.FirstOrDefault(s => s != null && s.name == nameImage);
}
/// <summary>Refresh team UI (Arrange Team dialog). Called after team join/leave/member data.</summary>
public bool UpdateTeam(bool bUpdateNear = false)
{
var dlg = GetDialog("Win_ArrangeTeam");
if (dlg is DlgArrangeTeam arr)
{
return arr.UpdateTeam(bUpdateNear);
}
return true;
}
public string GetRealmName(int realmLevel)
{
string strRealm = string.Empty;
if (realmLevel > 0){
int layer = CECHostPlayer.GetRealmLayer(realmLevel);
int subLevel = CECHostPlayer.GetRealmSubLevel(realmLevel);
strRealm = string.Format(GetStringFromTable(11100), GetStringFromTable(11100+layer), GetStringFromTable(11120+subLevel));
}
return strRealm;
}
public void PopupPetListDialog()
{
var dlg = CECUIManager.Instance.ShowUI("DlgPetList") as CDlgPetList;
dlg?.OnInitDialog();
}
public void PopupFriendListDialog()
{
var dlg = CECUIManager.Instance.ShowUI("Win_FriendList") as CDlgFriendList;
dlg?.OnInitDialog();
}
public void TraceTask(uint ulTaskId)
{
if (m_pDlgTask != null)
{
m_pDlgTask.TraceTask(ulTaskId);
}
}
/// <summary>
/// TASK_SVR_NOTIFY_COMPLETE (reason 2): refresh quest trace after server advances subtasks.
/// 服务端任务完成/推进通知后刷新任务追踪。
/// </summary>
public void RetraceTaskAfterServerNotifyComplete(ushort taskId)
{
if (m_pDlgTask == null)
return;
bool showTrace = IsDialogShow("Win_QuestMinion");
m_pDlgTask.RetraceAfterTaskServerNotifyComplete((uint)taskId, showTrace);
}
public void ShowErrorMsg(string pszMsg, string pszName)
{
CECUIManager.Instance.ShowMessageBoxYes(pszName, pszMsg, null,null);
}
/*CDlgPopMsg m_pDlgPopMsg;
void AddHeartBeatHint(string pszMsg)
{
m_pDlgPopMsg->Add(pszMsg);
}*/
/// <summary>
/// Adds a message to the chat system.
/// Mirrors CECGameUIMan::AddChatMessage in C++ (EC_GameUIMan.cpp).
/// </summary>
/// <param name="pszMsg">Message text.</param>
/// <param name="cChannel">Chat channel.</param>
/// <param name="idPlayer">Sender role ID (-1 = system).</param>
/// <param name="szName">Sender name (optional).</param>
/// <param name="byFlag">Message flag byte (CHANNEL_FRIEND, CHANNEL_GAMETALK, etc.).</param>
/// <param name="cEmotion">Emotion set ID; high-bit indicates king speaker on GP_CHAT_COUNTRY.</param>
/// <param name="pItem">Linked item (not fully supported yet).</param>
/// <param name="pszMsgOrigion">Original unformatted message (for logging/history).</param>
public void AddChatMessage(string pszMsg, ChatChannel cChannel, int idPlayer = -1, string szName = null,
byte byFlag = 0, int cEmotion = 0, object pItem = null, string pszMsgOrigion = null)
{
// C++:
// bool bIsKing = false;
// if( cChannel == GP_CHAT_COUNTRY && (cEmotion & 0x80) ) { cEmotion &= ~0x80; bIsKing = true; }
bool bIsKing = false;
if (cChannel == ChatChannel.GP_CHAT_COUNTRY && (cEmotion & 0x80) != 0)
{
cEmotion &= ~0x80;
bIsKing = true;
}
// C++: ACString strModified = FilterEmotionSet(pszMsg, cEmotion);
// 修正表情 — Correct emotion set for this channel
string strModified = _chatEmotionPipeline.ApplyChannelEmotionFilter(pszMsg, cEmotion);
// C++: 修正给GM的额外信息 (Fix extra info for GM)
// (Skipped in Unity for now)
// C++: 考虑本地化对某些内容不显示的要求 ... (Hide if empty)
if (string.IsNullOrEmpty(strModified))
return;
// C++: 标明来自GT频道的消息 (Mark GT channel message)
// if (byFlag == CHANNEL_GAMETALK) strModified += GetStringFromTable(9312);
pszMsg = strModified;
// C++: if( PlayerIsBlack(idPlayer) ) return;
// (Blacklist check skipped)
// C++: if( cChannel == GP_CHAT_SYSTEM && a_stricmp(pszMsg, GetStringFromTable(809)) == 0 ) return;
// (System message filter skipped)
// C++: if( byFlag == CHANNEL_FRIEND || byFlag == CHANNEL_FRIEND_RE || byFlag == CHANNEL_GAMETALK)
// AddFriendMessage(...)
// C++: else if( byFlag == CHANNEL_USERINFO ) ... (Refresh friend info)
// C++: FilterBadWords for Player messages
// if( cChannel == GP_CHAT_LOCAL || cChannel == GP_CHAT_FARCRY || ... )
// if (ISPLAYERID(idPlayer)) g_pGame->GetGameRun()->GetUIManager()->FilterBadWords(msg.strMsg);
bool isPlayerChannel = cChannel == ChatChannel.GP_CHAT_LOCAL
|| cChannel == ChatChannel.GP_CHAT_FARCRY
|| cChannel == ChatChannel.GP_CHAT_TEAM
|| cChannel == ChatChannel.GP_CHAT_FACTION
|| cChannel == ChatChannel.GP_CHAT_WHISPER
|| cChannel == ChatChannel.GP_CHAT_TRADE
|| cChannel == ChatChannel.GP_CHAT_SUPERFARCRY
|| cChannel == ChatChannel.GP_CHAT_BATTLE
|| cChannel == ChatChannel.GP_CHAT_COUNTRY;
if (isPlayerChannel && idPlayer > 0) // idPlayer > 0 is equivalent to C++ ISPLAYERID(id)
{
CECUIManager.Instance.FilterBadWords(ref pszMsg);
}
// TMP Conversion: 将内部item格式转换为TMP rich text标签
// Unmarshal inline items (emotion/coord/ivtritem) and convert to TMP tags
pszMsg = _chatEmotionPipeline.ConvertInlineItemsToTmp(pszMsg);
// C++: Booth Message check (cChannel == GP_CHAT_WHISPER && pszMsg ends with "!#")
// (Skipped)
// C++: TransformNameColor(pItem, strName, clrName);
// C++: msgWithColor += ... Color assignments ...
/*if( ISNPCID(idPlayer) ) msgWithColor += _AL("^C8FF64");
else if (cChannel == ChatChannel.GP_CHAT_COUNTRY && bIsKing)
{
msgWithColor += CDlgChat::m_pszKingColor;
}
else
{
msgWithColor += CDlgChat::GetChatColor(cChannel, idPlayer);
}*/
string colorHex;
if (GPDataTypeHelper.ISNPCID(idPlayer))
{
colorHex = DlgChat.NPC_COLOR;
}
else if (cChannel == ChatChannel.GP_CHAT_COUNTRY && bIsKing)
{
colorHex = DlgChat.KING_COLOR;
}
else
{
colorHex = DlgChat.GetChatColor(cChannel, idPlayer);
}
// C++: msgWithColor += GetChatChannelImage(cChannel);
string channelImage = DlgChat.GetChatChannelImage(cChannel);
// C++: if( cChannel == GP_CHAT_COUNTRY && bIsKing ) msgWithColor += GetStringFromTable(10310);
// Map the final string structure
// In C++, the FixedMsg adds &name& or ^&name^& formats to color player names.
// When building the string, C++ prefixes the color code (colorHex) before the message,
// which usually means the name is colored. But the content might have its own color reset.
// We use a regular expression to find `&...&` and wrap ONLY the name in reality.
string parsedMsg = pszMsg;
if (parsedMsg.Contains("&"))
{
// Convert &Cuong& into <color=#HexColor><link="idPlayer|Cuong">Cuong</link></color>
parsedMsg = System.Text.RegularExpressions.Regex.Replace(
parsedMsg,
@"&([^&]+)&",
$"<color=#{colorHex}><u><link=\"{idPlayer}|$1\">$1</link></u></color>"
);
}
else
{
// If there's no &, we just colorize the whole string by default (e.g., system messages)
parsedMsg = $"<color=#{colorHex}>{parsedMsg}</color>";
}
// Channel icon prefix + message text
string formattedMsg = $"{channelImage}{parsedMsg}";
// C++: Write to m_pDlgChatWhisper, Chat1, Chat2, SuperFarCry
// Unity equivalent: Dispatch to EventBus for ChatPanelUI to handle
EventBus.Publish(new GameSession.ChatMessageEvent(formattedMsg, (byte)cChannel));
// C++: AddChatMessage also handles head bubble via pPlayer->SetLastSaidWords
// Unity equivalent: Publish EventChatMessageOnTopPlayer
bool showsAboveHead = ChannelShowsChatBubbleAboveHead(cChannel);
if (showsAboveHead && idPlayer > 0)
{
// C++: pPlayer->SetLastSaidWords(strTemp, ...) — chỉ thân tin (không tên); bản wire pszMsgOrigion phải → TMP giống khung chat.
// C++: SetLastSaidWords — body only (no name); wire pszMsgOrigion must become TMP like chat panel.
string bubbleText = !string.IsNullOrEmpty(pszMsgOrigion)
? ConvertWireBodyForHeadBubble(pszMsgOrigion, cChannel, idPlayer, cEmotion)
: pszMsg;
EventBus.PublishChannel(idPlayer, new EventChatMessageOnTopPlayer(idPlayer, bubbleText));
}
// C++: if( cChannel == GP_CHAT_BROADCAST && byFlag == 0 ) SetMarqueeMsg(strConverted);
if (cChannel == ChatChannel.GP_CHAT_BROADCAST && byFlag == 0)
{
// TODO: SetMarqueeMsg(pszMsg); when marquee UI is available
}
// C++: AutoReply Logic
// else if( gs.bAutoReply && cChannel == GP_CHAT_WHISPER ... )
// (Skipped)
}
// bool LoadIconSet()
// {
// bool bval;
// int dwRead;
// AFileImage fi;
// int h, i, j, nIndex;
// A3DRECT *a_rc[ICONS_MAX];
// char szFile[MAX_PATH], szLine[AFILE_LINEMAXLEN];
// int W = 32, H = 32, a_nCountX[ICONS_MAX], a_nCountY[ICONS_MAX];
// GNET::RoleInfo Info = g_pGame->GetGameRun()->GetSelectedRoleInfo();
// char a_szIconFile[ICONS_MAX][40] = { "Action", "Skill", "Ivtr", "State", "SkillGrp", "Guild", "Pet", "ELF", "Suite", "Calendar", "PQ" };
// if( 0 == Info.gender )
// strcat(a_szIconFile[ICONS_INVENTORY], "M");
// else
// strcat(a_szIconFile[ICONS_INVENTORY], "F");
// for( h = 0; h < ICONS_MAX; h++ )
// {
// sprintf(szFile, "%s\\Surfaces\\IconSet\\IconList_%s.txt",
// af_GetBaseDir(), a_szIconFile[h]);
// bval = fi.Open(szFile, AFILE_OPENEXIST | AFILE_TEXT );
// if( !bval ) return AUI_ReportError(__LINE__, __FILE__);
// fi.ReadLine(szLine, sizeof(szLine), &dwRead);
// W = atoi(szLine);
// fi.ReadLine(szLine, sizeof(szLine), &dwRead);
// H = atoi(szLine);
// fi.ReadLine(szLine, sizeof(szLine), &dwRead);
// a_nCountY[h] = atoi(szLine);
// fi.ReadLine(szLine, sizeof(szLine), &dwRead);
// a_nCountX[h] = atoi(szLine);
// a_rc[h] = (A3DRECT*)a_malloc(sizeof(A3DRECT)*(a_nCountX[h] * a_nCountY[h]));
// if( !a_rc[h] ) return AUI_ReportError(__LINE__, __FILE__);
// for( i = 0; i < a_nCountY[h]; i++ )
// {
// for( j = 0; j < a_nCountX[h]; j++ )
// {
// nIndex = i * a_nCountX[h] + j;
// a_rc[h][nIndex].SetRect(j * W, i * H, j * W + W, i * H + H);
// bval = fi.ReadLine(szLine, sizeof(szLine), &dwRead);
// if( dwRead > 0 && strlen(szLine) > 0 )
// m_IconMap[h][AString(szLine)] = nIndex;
// }
// }
// fi.Close();
// m_pA2DSpriteIcons[h] = new A2DSprite;
// if( !m_pA2DSpriteIcons[h] )
// {
// a_free(a_rc[h]);
// return AUI_ReportError(__LINE__, __FILE__);
// }
// sprintf(szFile, "IconSet\\IconList_%s.dds", a_szIconFile[h]);
// bval = m_pA2DSpriteIcons[h]->Init(m_pA3DDevice, szFile, AUI_COLORKEY);
// if( !bval )
// {
// a_free(a_rc[h]);
// return AUI_ReportError(__LINE__, __FILE__);
// }
// m_vecIconList.push_back( m_pA2DSpriteIcons[h] ); // add for imaged hints
// }
// SetImageList(&m_vecIconList); // add for imaged hints
// for( h = 0; h < ICONS_MAX; h++ )
// {
// bval = m_pA2DSpriteIcons[h]->ResetItems(
// a_nCountX[h] * a_nCountY[h], a_rc[h]);
// a_free(a_rc[h]);
// if( !bval ) return AUI_ReportError(__LINE__, __FILE__);
// }
// m_pA2DSpriteMask = new A2DSprite;
// if( !m_pA2DSpriteMask ) return AUI_ReportError(__LINE__, __FILE__);
// bval = m_pA2DSpriteMask->Init(m_pA3DDevice,
// "InGame\\IconHighlight.dds", AUI_COLORKEY);
// if( !bval ) return AUI_ReportError(__LINE__, __FILE__);
// // load the expire icon cover
// m_pA2DSpriteItemExpire = new A2DSprite;
// if( !m_pA2DSpriteItemExpire ) return AUI_ReportError(__LINE__, __FILE__);
// bval = m_pA2DSpriteItemExpire->Init(m_pA3DDevice, "InGame\\IconItemExpire.dds", AUI_COLORKEY);
// if( !bval )
// {
// delete m_pA2DSpriteItemExpire;
// m_pA2DSpriteItemExpire = NULL;
// AUI_ReportError(__LINE__, "CECGameUIMan::LoadIconSet(), failed to load InGame\\IconItemExpire.dds");
// }
// // ذPVPͼ
// const char *szFactionPVPMineBase[FACTION_PVP_ICON_NUM] = {
// "InGame\\_С.tga",
// "InGame\\_С_.tga",
// "InGame\\.tga",
// "InGame\\_.tga",
// "InGame\\_״.tga",
// };
// for (i = 0; i < FACTION_PVP_ICON_NUM; ++ i){
// if (!LoadSprite(szFactionPVPMineBase[i], m_pFactionPVPMineBaseSprite[i])){
// AUI_ReportError(__LINE__, 1, "CECGameUIMan::LoadIconSet(), failed to load %s", szFactionPVPMineBase[i]);
// }
// }
// const char *szFactionPVPMine[FACTION_PVP_ICON_NUM] = {
// "InGame\\_С.tga",
// "InGame\\_С_.tga",
// "InGame\\.tga",
// "InGame\\_.tga",
// "InGame\\_״.tga",
// };
// for (i = 0; i < FACTION_PVP_ICON_NUM; ++ i){
// if (!LoadSprite(szFactionPVPMine[i], m_pFactionPVPMineSprite[i])){
// AUI_ReportError(__LINE__, 1, "CECGameUIMan::LoadIconSet(), failed to load %s", szFactionPVPMine[i]);
// }
// }
// const char *szFactionPVPHasMine = "InGame\\пʾ.tga";
// if (!LoadSprite(szFactionPVPHasMine, m_pA2DSpriteFactionPVPHasMine)){
// AUI_ReportError(__LINE__, 1, "CECGameUIMan::LoadIconSet(), failed to load %s", szFactionPVPHasMine);
// }
// // Emotions.Ѽ
// // Images
// char a_szImageFile[GP_CHAT_MAX][40] = { "ͨ.tga", ".tga", ".tga", ".tga", ".tga", "", "", ".tga", "", "ϵͳ.tga", "", "", "Ž.tga", "ս.tga", ".tga"};
// for (i = 0; i < GP_CHAT_MAX; ++ i)
// {
// AString strFile = AString("InGame\\") + a_szImageFile[i];
// AString strFullPath = AString("Surfaces\\") + strFile;
// if (af_IsFileExist(strFullPath))
// {
// A2DSprite *pSprite = new A2DSprite;
// if (!pSprite) return AUI_ReportError(__LINE__, __FILE__);
// if (pSprite->Init(m_pA3DDevice, strFile, AUI_COLORKEY))
// {
// m_pA2DSpriteImage.push_back(pSprite);
// continue;
// }
// else
// {
// A3DRELEASE(pSprite);
// }
// }
// // ռλ
// m_pA2DSpriteImage.push_back(NULL);
// }
// PAUIDIALOG pShow = GetDialog("Win_Popface");
// pShow->SetData(AUIMANAGER_MAX_EMOTIONGROUPS);
// pShow = GetDialog("Win_Popface01");
// pShow->SetData(AUIMANAGER_MAX_EMOTIONGROUPS);
// pShow = GetDialog("Win_Popface02");
// pShow->SetData(AUIMANAGER_MAX_EMOTIONGROUPS);
// AScriptFile sf;
// if( sf.Open("Configs\\Iconsound.txt") )
// {
// int idSubType;
// AString strWave;
// while( sf.GetNextToken(true) )
// {
// idSubType = atoi(sf.m_szToken);
// sf.GetNextToken(true);
// strWave = sf.m_szToken;
// m_IconSound[idSubType] = strWave;
// }
// sf.Close();
// }
// for (i=0;i<CECSCSysModule::FM_NUM;i++)
// {
// const char* pIconFile = res_SysModuleIconFile(i);
// A2DSprite *pSprite = new A2DSprite;
// if (!pSprite) return AUI_ReportError(__LINE__, __FILE__);
// if (!pSprite->Init(m_pA3DDevice, pIconFile, AUI_COLORKEY))
// {
// A3DRELEASE(pSprite);
// a_LogOutput(1, "CECGameUIMan::LoadIconSet , %s",pIconFile);
// continue;
// }
// pSprite->SetLinearFilter(true);
// m_pSpriteIconSysModule.push_back(pSprite);
// }
// return true;
// }
}
public enum EC_GAMEUI_ICONS : byte
{
ICONS_ACTION = 0,
ICONS_SKILL,
ICONS_INVENTORY,
ICONS_STATE,
ICONS_SKILLGRP,
ICONS_GUILD,
ICONS_PET,
ICONS_ELF,
ICONS_SUITE,
ICONS_CALENDAR,
ICONS_PQ,
ICONS_MAX
};
}