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 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(); /// /// Gọi từ (MonoBehaviour) trước — 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. /// public void SetEmotionSpriteMap(IEmotionSpriteMap map) { if (map is EmotionLibrarySpriteMap libMap) libMap.EnsureCachedTmpSpriteAssetNames(); _chatEmotionPipeline.SetSpriteMap(map); } /// /// 服务器下发的 wire 正文 → TMP(表情/坐标/物品等内联)— 供 GameSession 等不经过 AddChatMessage 的 UI 路径。 /// Server wire body → TMP (inline emotion/coord/item) — for UI paths that do not go through AddChatMessage. /// public string ConvertWireChatBodyForDisplay(string wireBody, int cEmotion) { if (string.IsNullOrEmpty(wireBody)) return wireBody; string f = _chatEmotionPipeline.ApplyChannelEmotionFilter(wireBody, cEmotion); return _chatEmotionPipeline.ConvertInlineItemsToTmp(f); } /// /// 仅正文 wire(无 printf 包装)→ 头顶气泡 TMP — 与 AddChatMessage 内 FilterBadWords + 内联转 TMP 一致。 /// Wire body only (no printf wrapper) → head-bubble TMP — same as AddChatMessage FilterBadWords + inline to TMP. /// 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); } /// /// 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. /// 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(); 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(); m_pDlgNPC.SetAUIManager(this); } m_pDlgNPC.PopupNPCDialog(pTalk); } /// /// For what: This is for QuickBar (Skill + Action + Combo) /// 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(); 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(); 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(); } } /// /// Call after CECTaskInterface.Init completes (TASK_DATA). Restores/applies quest trace via DlgTask.SyncTrace and refreshes minimion. /// 在 CECTaskInterface.Init(TASK_DATA)完成后调用,通过 DlgTask.SyncTrace 应用任务追踪并刷新小窗。 /// 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) { } /// /// Get user layout data for saving to server. Fills USER_LAYOUT and optionally writes to pData. /// 获取用户布局数据用于保存到服务器。填充 USER_LAYOUT,并可选写入 pData。 /// 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); } } /// Copy string into fixed ushort[] (for SAVE_MARK.szName). 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; } /// TODO: Wire to system module quick bar mini mode when dialog is available. private bool GetDlgSysModuleQuickBarMiniMode() { return false; } /// Quest main dialog (Win_Quest), created in Init. / 任务主界面,Init 时创建。 public DlgTask GetDlgTask() { return m_pDlgTask; } public override void Init() { base.Init(); m_IconMap = new Dictionary(); m_pDlgTask = GetDialog(CECUIHelper.DlgTaskName).GetComponent(); m_pDlgTask.Show(false); m_pDlgMiniMap = GameObject.FindObjectsByType( FindObjectsSortMode.None).FirstOrDefault(); m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILL] = (SKILL_ICONLIST_NAME, Resources.LoadAll(SKILL_ICONLIST_NAME)); m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_ACTION] = (ACTION_ICONLIST_NAME, Resources.LoadAll(ACTION_ICONLIST_NAME)); m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_INVENTORY] = (INVENTORY_ICONLIST_NAME, Resources.LoadAll(INVENTORY_ICONLIST_NAME)); m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_STATE] = (STATE_ICONLIST_NAME, Resources.LoadAll(STATE_ICONLIST_NAME)); m_IconMap[(byte)EC_GAMEUI_ICONS.ICONS_SKILLGRP] = (SKILL_GROUP_ICON_NAME, Resources.LoadAll(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); } /// Refresh team UI (Arrange Team dialog). Called after team join/leave/member data. 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); } } /// /// TASK_SVR_NOTIFY_COMPLETE (reason 2): refresh quest trace after server advances subtasks. /// 服务端任务完成/推进通知后刷新任务追踪。 /// 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); }*/ /// /// Adds a message to the chat system. /// Mirrors CECGameUIMan::AddChatMessage in C++ (EC_GameUIMan.cpp). /// /// Message text. /// Chat channel. /// Sender role ID (-1 = system). /// Sender name (optional). /// Message flag byte (CHANNEL_FRIEND, CHANNEL_GAMETALK, etc.). /// Emotion set ID; high-bit indicates king speaker on GP_CHAT_COUNTRY. /// Linked item (not fully supported yet). /// Original unformatted message (for logging/history). 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 Cuong parsedMsg = System.Text.RegularExpressions.Regex.Replace( parsedMsg, @"&([^&]+)&", $"$1" ); } else { // If there's no &, we just colorize the whole string by default (e.g., system messages) parsedMsg = $"{parsedMsg}"; } // 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;iInit(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 }; }