using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using BrewMonster.Managers; using BrewMonster.Scripts.Managers; using BrewMonster.Network; using BrewMonster.Scripts.Task; using BrewMonster.Scripts.UI; using BrewMonster.UI; using CSNetwork.GPDataType; using ModelRenderer.Scripts.Common; using ModelRenderer.Scripts.GameData; using NUnit.Framework; using PerfectWorld.Scripts.Task; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; namespace BrewMonster.Scripts.Task.UI { /// /// This is DlgTask.cpp /// public class DlgTask : AUIDialog { #if UNITY_EDITOR [ContextMenu("Generate Tasks")] public void TestUpdateTask() { UpdateTask(-1); } #endif // Keep original macro as constant for array sizing public const int CDLGTASK_AWARDITEM_MAX = 8; public const int TickRate = 1000; // ===== Nested structs (converted from C++), keep naming, public with explicit layout ===== // [中文] 任务目标位置 // [English] Task object position [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TASK_OBJECT_POS { public int x; public int y; public int z; public int mapid; } // [中文] 任务完成时间 // [English] Task finished time [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TASK_FINISHED_TIME { public int iTaskID; public uint dwTime; // DWORD -> uint } // [中文] ������������ // [English] Type grouping node [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TypeNode { public uint type; // DWORD -> uint public GameObject item; // P_AUITREEVIEW_ITEM -> GameObject } // [中文] ��������ȼ����� // [English] Level priority node [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct LevelNode { public int level; public GameObject item; // P_AUITREEVIEW_ITEM -> GameObject } // ===== Converted member variables (keep original naming) ===== // protected: protected int m_idLastTask; protected int m_idSelTask; protected bool m_bTraceNew; protected bool m_bShowTrace; protected int m_iType; [SerializeField] protected TMP_Text _nameTaskText; [SerializeField] protected TMP_Text m_pTxt_QuestNO; // PAUILABEL -> TMP_Text [SerializeField] protected TaskTreeView m_pTv_Quest; // PAUITREEVIEW -> GameObject container [SerializeField] protected TMP_Text m_pTxt_Content; // PAUITEXTAREA -> TMP_Text [SerializeField] protected TMP_Text m_pTxt_QuestItem; // PAUITEXTAREA -> TMP_Text [SerializeField] protected Button m_pBtn_Abandon; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Button m_pBtn_MainQuest; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Button m_pBtn_NormalQuest; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Button m_pBtn_SearchQuest; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Button m_pBtn_HaveQuest; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Toggle m_pTog_bShowTrace; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected Button m_pBtn_FinishTask; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected TMP_Text m_pTxt_BaseAward; // PAUILABEL -> TMP_Text [Space(10)] [SerializeField] protected Button Btn_TreasureMap; [SerializeField] protected Button Btn_Focus; [SerializeField] protected GameObject Lab_QuestNO; // the title label of m_pTxt_QuestNO // PAUIIMAGEPICTURE m_pImg_Item[CDLGTASK_AWARDITEM_MAX]; // Use fixed-size array semantics via initialization length // [中文] 奖励物品图片数组 // [English] Award item images array [MarshalAs(UnmanagedType.ByValArray, SizeConst = CDLGTASK_AWARDITEM_MAX)] [SerializeField] protected Image[] m_pImg_Item = new Image[CDLGTASK_AWARDITEM_MAX]; protected uint m_ImgCount => (uint)m_pImg_Item.Length; // unsigned int -> uint [SerializeField] protected Button m_pBtn_GotoNPC; // PAUISTILLIMAGEBUTTON -> Button [SerializeField] protected GameObject m_pQuickBuyTrigger; // CECQuickBuyPopActivityTrigger* -> GameObject // private: private static List m_vecTasksUnFinish = new List(); private static List m_vecTasksCanFinish = new List(); // [中文] 目标坐标集合 // [English] Target coordinates collection private static List m_TargetCoord = new List(); private static string m_strTraceName = string.Empty; // ACString -> string // [中文] 任务相关矿点映射 // [English] Mine map related to tasks private static Dictionary m_TaskMines = new Dictionary(); // MINE_ESSENCE* -> object // [中文] 任务跟踪计时器 // [English] Task trace counter private CECCounter m_TaskTraceCounter = new (); // CECCounter -> object placeholder // ===== Time-gated task UI refresh (search list) ===== // Timetable/time-window tasks become available/unavailable as server time moves. // The original C++ client periodically re-evaluates prerequisites; in this port we refresh the search list // at a low frequency while the Search view is open so players can see time-gated tasks appear/disappear. private uint _lastSearchRefreshMinuteKey = uint.MaxValue; private uint _pendingReselectTaskId = 0; // Active-task timer refresh (wait-time / time-limit / protect-time) private float _nextActiveTimerUiRefreshAt = 0f; #region Unity METHODS private new void OnEnable() { OnShowDialog(); OnCommand_havequest(); } private new void OnDisable() { OnHideDialog(); } private new void Awake() { EventBus.Subscribe(evt => { OnEventLButtonDown_Tv_Quest(evt.Data); }); m_pBtn_HaveQuest.onClick.AddListener(OnCommand_havequest); m_pBtn_SearchQuest.onClick.AddListener(OnCommand_searchquest); m_pBtn_Abandon.onClick.AddListener(OnCommand_abandon); Btn_Focus.onClick.AddListener(() => OnCommand_focus()); if (m_pTog_bShowTrace) { m_pTog_bShowTrace.onValueChanged.AddListener((state) => OnCommand_showtrace(null,state)); m_pTog_bShowTrace.isOn = false; } // Convert exactly like C++ OnEventLButtonDown_Txt_QuestItem // 完全按照C++ OnEventLButtonDown_Txt_QuestItem转换 // C++ uses WM_LBUTTONDOWN event, we use EventTrigger PointerClick // C++使用WM_LBUTTONDOWN事件,我们使用EventTrigger PointerClick if (m_pTxt_QuestItem != null) { // Enable raycast for TMP link detection (like C++ GetItemLinkItemOn needs link info) // 为TMP链接检测启用射线投射(如C++ GetItemLinkItemOn需要链接信息) m_pTxt_QuestItem.raycastTarget = true; // Add EventTrigger for PointerClick (like WM_LBUTTONDOWN in C++) // 为PointerClick添加EventTrigger(如C++中的WM_LBUTTONDOWN) EventTrigger trigger = m_pTxt_QuestItem.GetComponent(); if (trigger == null) { trigger = m_pTxt_QuestItem.gameObject.AddComponent(); } // Clear existing triggers // 清除现有触发器 trigger.triggers.Clear(); // Add PointerClick event (like WM_LBUTTONDOWN) // 添加PointerClick事件(如WM_LBUTTONDOWN) EventTrigger.Entry entry = new EventTrigger.Entry(); entry.eventID = EventTriggerType.PointerClick; entry.callback.AddListener((data) => { PointerEventData pointerData = (PointerEventData)data; OnEventLButtonDown_Txt_QuestItem(pointerData); }); trigger.triggers.Add(entry); } OnInitDialog(); } // Convert exactly like C++ OnEventLButtonDown_Txt_QuestItem // 完全按照C++ OnEventLButtonDown_Txt_QuestItem转换 // C++: void CDlgTask::OnEventLButtonDown_Txt_QuestItem(WPARAM wParam, LPARAM lParam, AUIObject *pObj) private void OnEventLButtonDown_Txt_QuestItem(PointerEventData eventData) { if (m_pTxt_QuestItem == null) return; const string LINK_CLICK_VER = "DlgTaskLinkClickCamFix_v2"; UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: {LINK_CLICK_VER} pressEventCamera={(eventData.pressEventCamera != null ? eventData.pressEventCamera.name : "null")}"); // C++: int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); // C++: int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( m_pTxt_QuestItem.rectTransform, eventData.position, eventData.pressEventCamera, out localPoint); UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: Click at localPoint={localPoint}, screenPos={eventData.position}"); // C++: GetItemLinkItemOn(x, y, pObj, &Item); // C++: GetItemLinkItemOn(x, y, pObj, &Item); // Find which link was clicked (like C++ GetItemLinkItemOn checks vecItemLink[i].rc.PtInRect) // 查找点击了哪个链接(如C++ GetItemLinkItemOn检查vecItemLink[i].rc.PtInRect) m_pTxt_QuestItem.ForceMeshUpdate(); // Debug: verify TMP parsed tags and we have linkInfo int linkCount = m_pTxt_QuestItem.textInfo?.linkCount ?? -1; UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: TMP linkCount={linkCount}, raycastTarget={m_pTxt_QuestItem.raycastTarget}, richText={m_pTxt_QuestItem.richText}"); if (linkCount <= 0) { string text = m_pTxt_QuestItem.text ?? string.Empty; string preview = text.Substring(0, Mathf.Min(300, text.Length)); UnityEngine.Debug.LogWarning($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: No links parsed. Text preview: {preview}"); return; } // Get camera for link detection // 获取用于链接检测的相机 Camera camera = null; Canvas canvas = m_pTxt_QuestItem.GetComponentInParent(); if (canvas != null) { // IMPORTANT: TMP_TextUtilities.FindIntersectingLink expects camera=null for ScreenSpaceOverlay. // 重要:ScreenSpaceOverlay 必须传 camera=null,否则会算错导致 linkIndex = -1。 UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: Canvas renderMode={canvas.renderMode}, worldCamera={(canvas.worldCamera != null ? canvas.worldCamera.name : "null")}, pressEventCamera={(eventData.pressEventCamera != null ? eventData.pressEventCamera.name : "null")}"); if (canvas.renderMode == RenderMode.ScreenSpaceOverlay) { camera = null; } else { // ScreenSpaceCamera / WorldSpace camera = eventData.pressEventCamera != null ? eventData.pressEventCamera : canvas.worldCamera; } } else { UnityEngine.Debug.LogWarning($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: {LINK_CLICK_VER} Canvas not found in parents of m_pTxt_QuestItem!"); // No canvas found; fall back to pressEventCamera (may be null) then main camera = eventData.pressEventCamera != null ? eventData.pressEventCamera : Camera.main; } // Find intersecting link (like C++ checks vecItemLink[i].rc.PtInRect(x, y)) // 查找相交的链接(如C++检查vecItemLink[i].rc.PtInRect(x, y)) int linkIndexCam = TMP_TextUtilities.FindIntersectingLink(m_pTxt_QuestItem, eventData.position, camera); int linkIndexNull = TMP_TextUtilities.FindIntersectingLink(m_pTxt_QuestItem, eventData.position, null); UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: {LINK_CLICK_VER} FindIntersectingLink(cam={(camera != null ? camera.name : "null")})={linkIndexCam}, FindIntersectingLink(null)={linkIndexNull}, screenPos={eventData.position}"); int linkIndex = linkIndexCam != -1 ? linkIndexCam : linkIndexNull; UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: FindIntersectingLink => linkIndex={linkIndex}, camera={(camera != null ? camera.name : "null")}, screenPos={eventData.position}"); // Dump all links for debugging for (int i = 0; i < m_pTxt_QuestItem.textInfo.linkCount; i++) { TMP_LinkInfo li = m_pTxt_QuestItem.textInfo.linkInfo[i]; UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: Link[{i}] id={li.GetLinkID()} text={li.GetLinkText()}"); } // C++: if( Item.m_pItem != NULL ) // C++: if( Item.m_pItem != NULL ) if (linkIndex != -1 && linkIndex < m_pTxt_QuestItem.textInfo.linkCount) { TMP_LinkInfo linkInfo = m_pTxt_QuestItem.textInfo.linkInfo[linkIndex]; string linkID = linkInfo.GetLinkID(); UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: Found link - linkID={linkID}"); // C++: if( Item.m_pItem->GetType() == enumEICoord ) // C++: if( Item.m_pItem->GetType() == enumEICoord ) // Check if this is a coordinate link (NPC, monster, item, target - all use enumEICoord in C++) // 检查这是否是坐标链接(NPC、怪物、物品、目标 - 在C++中都使用enumEICoord) if (linkID.StartsWith("coord_")) { // C++: if (IsTreasureMapSelected()){ OnCommand_TreasureMap(NULL); } // C++: if (IsTreasureMapSelected()){ OnCommand_TreasureMap(NULL); } // TODO: Implement IsTreasureMapSelected check if needed // TODO: 如果需要,实现IsTreasureMapSelected检查 // C++: else { CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask); } // C++: else { CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask); } // Extract entity ID from link (NPC, monster, item, target - all handled the same) // 从链接中提取实体ID(NPC、怪物、物品、目标 - 处理方式相同) string entityIdStr = linkID.Substring(6); // Remove "coord_" prefix if (int.TryParse(entityIdStr, out int entityID)) { // Use currently selected task ID (like m_idSelTask in C++ FollowCoord call) // 使用当前选中的任务ID(如C++ FollowCoord调用中的m_idSelTask) int currentTaskID = m_idSelTask; UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: Coordinate link clicked, EntityID={entityID} (NPC/Monster/Item/Target), task={currentTaskID}"); // C++: CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask); // C++: CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask); // FollowCoord triggers auto-move to the coordinates (for NPC, monster, item, target) // FollowCoord触发自动移动到坐标(适用于NPC、怪物、物品、目标) // This works for all entity types - NPC names, monster names, items, targets // 这适用于所有实体类型 - NPC名称、怪物名称、物品、目标 if (currentTaskID > 0) { CECHostPlayer hostPlayer = GetHostPlayer(); if (hostPlayer != null) { CECTaskInterface taskInterface = hostPlayer.GetTaskInterface(); if (taskInterface != null) { // This matches C++: CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask) // 这匹配C++:CECUIHelper::FollowCoord(Item.m_pItem, m_idSelTask) bool ok = CECUIHelper.FollowCoord(entityID, currentTaskID); UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: FollowCoord(entity={entityID}, task={currentTaskID}) => {ok}"); } } } else { // Even without a task, still follow coord // 即使没有任务,也照样跟随坐标 bool ok = CECUIHelper.FollowCoord(entityID, 0); UnityEngine.Debug.Log($"[DlgTask] OnEventLButtonDown_Txt_QuestItem: FollowCoord(entity={entityID}, task=0) => {ok}"); } } } } // C++: ChangeFocus(NULL); // C++: ChangeFocus(NULL); // TODO: Implement ChangeFocus if needed // TODO: 如果需要,实现ChangeFocus } public static void SetTracePosition(List targetPos, string targetName) { CECUIHelper.FollowCoord(targetPos, targetName); } public static void SetTraceNpc(int entityID, int taskID) { CECUIHelper.FollowCoord(entityID, taskID); } #endregion #region PUBLIC METHODS public void OnCommand_searchquest() { // if (m_szName != "Win_Quest") return; m_iType = 1; // TODO // PAUIOBJECT pObj = GetDlgItem("Img_New"); // if (pObj && IsShow()) pObj->Show(false); // if(GetDlgItem("Lab_Trace")) GetDlgItem("Lab_Trace")->Show(false); m_pBtn_Abandon.gameObject.SetActive(false); Btn_Focus.gameObject.SetActive(false); Lab_QuestNO.gameObject.SetActive(false); m_pTxt_QuestNO.gameObject.SetActive(false); m_pBtn_SearchQuest.interactable = false; m_pBtn_HaveQuest.interactable = true; SearchForTask(); } public void OnCommand_havequest() { m_iType = 0; // TODO: if(GetDlgItem("Lab_Trace")) GetDlgItem("Lab_Trace")->Show(true); m_pBtn_Abandon.gameObject.SetActive(true); Btn_Focus.gameObject.SetActive(true); Lab_QuestNO.gameObject.SetActive(true); m_pTxt_QuestNO.gameObject.SetActive(true); m_pBtn_SearchQuest.interactable = true; m_pBtn_HaveQuest.interactable = false; UpdateTask(); } public void OnCommand_showtrace(string szCommand,bool state) { m_bShowTrace = state; // hide the trace window // Get AUIManager, fallback to GetGameUIMan() if m_pAUIManager is not set yet // 获取AUIManager,如果m_pAUIManager尚未设置则回退到GetGameUIMan() AUIManager auiManager = GetAUIManager(); if (auiManager == null) { // Try to get it from GetGameUIMan() as fallback // 尝试从GetGameUIMan()获取作为回退 CECGameUIMan gameUIMan = GetGameUIMan(); auiManager = gameUIMan; } AUIDialog pTrace = auiManager.GetDialog("Win_QuestMinion"); int index = this.transform.GetSiblingIndex(); int pTraceIndex = pTrace.transform.GetSiblingIndex(); if(pTraceIndex > index) { pTrace.transform.SetSiblingIndex(index); } if (pTrace) { pTrace.Show(m_bShowTrace); } } public void OnCommand_focus(string szCommand="") { var pTree = m_pTv_Quest; var pItem = pTree?.GetSelectedItem(); if (pItem == null) { BMLogger.LogWarning("Cannot focus task: No task is currently selected"); return; } var idTask = pTree?.GetItemData(pItem); if (idTask == 0) { BMLogger.LogWarning("Cannot focus task: Selected item has no valid task ID"); return; } m_pTog_bShowTrace.onValueChanged.Invoke(true); SwitchTaskTrace((int)idTask); } void SwitchTaskTrace(int idTask) { var pDlg = m_pAUIManager?.GetDialog("Win_QuestMinion"); pDlg?.Show(true); if (IsPQTaskOrSubTask(idTask)) return; if(!IsTaskTraceable(idTask)) return; var pTask = GetHostPlayer()?.GetTaskInterface(); bool bFinishedTask = pTask?.CanFinishTask((uint)idTask) ?? false; // Toggle behavior: if task is already in a list, remove it; otherwise add it bool isInCanFinish = m_vecTasksCanFinish.Contains(idTask); bool isInUnFinish = m_vecTasksUnFinish.Contains(idTask); if (isInCanFinish || isInUnFinish) { // Second click: remove from both lists (toggle off) m_vecTasksCanFinish.Remove(idTask); m_vecTasksUnFinish.Remove(idTask); } else { // First click: add to appropriate list (toggle on) if(bFinishedTask) { m_vecTasksCanFinish.Add(idTask); m_vecTasksUnFinish.Remove(idTask); // Remove from other list if present } else { m_vecTasksUnFinish.Add(idTask); m_vecTasksCanFinish.Remove(idTask); // Remove from other list if present } } } bool IsTaskTraceable(int idTask) { var pMan = EC_Game.GetTaskTemplateMan(); var pTemp = pMan?.GetTaskTemplByID((uint)idTask); // ��������׷�� if (IsQuestionTask(pTemp)) { return false; } Task_State_info tsi = default; var pTask = GetHostPlayer()?.GetTaskInterface(); pTask?.GetTaskStateInfo((uint)idTask, ref tsi); bool bTrace = tsi.m_ulTimeLimit > 0 || tsi.m_ulProtectTime > 0 || tsi.m_ulNPCToProtect > 0 || tsi.m_MonsterWanted[0].m_ulMonstersToKill > 0 || tsi.m_PlayerWanted[0].m_ulPlayersToKill > 0 || tsi.m_ItemsWanted[0].m_ulItemId > 0 || tsi.m_ulWaitTime > 0 || tsi.m_TaskCharArr._finish > 0 || tsi.m_ulReachLevel > 0 || tsi.m_ulReachRealm > 0 || tsi.m_ulReachReincarnation > 0; // check the condition from template if(!bTrace) { if (pTemp.m_FixedData.m_ulReachSiteCnt > 0 || (pTemp.m_FixedData.m_ulAwardNPC > 0 && (pTask?.CanFinishTask((uint)idTask) ?? false))){ bTrace = true; } } return bTrace; } bool IsPQTaskOrSubTask(int idTask) { var pMan = EC_Game.GetTaskTemplateMan(); var pTemp = pMan?.GetTaskTemplByID((uint)idTask); return (pTemp!=null && (pTemp.m_FixedData.m_bPQTask || pTemp.m_FixedData.m_bPQSubTask)); } public void OnCommand_abandon() { // [中文] 放弃任务:发送通知到服务器 // [English] Abandon task: send notification to server // Get the currently selected task from the tree view var pTree = m_pTv_Quest; var pSelectedItem = pTree?.GetSelectedItem(); if (pSelectedItem == null) { BMLogger.LogWarning("Cannot abandon task: No task is currently selected"); return; } // Get the task ID from the selected item uint selectedTaskId = pTree.GetItemData(pSelectedItem); if (selectedTaskId == 0) { BMLogger.LogWarning("Cannot abandon task: Selected item has no task ID"); return; } CECTaskInterface pTask = GetHostPlayer()?.GetTaskInterface(); if (pTask == null) { BMLogger.LogError("Cannot abandon task: TaskInterface is null"); return; } // Check if the task is actually active before allowing abandon // This prevents trying to abandon a task that's already been abandoned if (!pTask.HasTask(selectedTaskId)) { BMLogger.LogWarning($"Cannot abandon task: Task {selectedTaskId} is not active"); return; } // Get the task template to find the top-level task ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); ATaskTempl pTempl = pMan?.GetTaskTemplByID(selectedTaskId); if (pTempl == null) { BMLogger.LogError($"Cannot abandon task: Task template {selectedTaskId} not found"); return; } // Get the top-level task ID (tasks can have subtasks) ATaskTempl pTopTask = pTempl.GetTopTask(); uint topTaskId = pTopTask != null ? pTopTask.GetID() : selectedTaskId; // Verify the top-level task is also active if (!pTask.HasTask(topTaskId)) { BMLogger.LogWarning($"Cannot abandon task: Top-level task {topTaskId} is not active"); return; } // Remove task from trace lists immediately (optimistic update) // This prevents the abandoned task from showing in the task trace UI int taskIdInt = (int)topTaskId; m_vecTasksCanFinish.Remove(taskIdInt); m_vecTasksUnFinish.Remove(taskIdInt); // Send notification to server to abandon the currently selected task TaskClient._notify_svr(pTask, (byte)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_GIVEUP, (ushort)topTaskId); // Refresh UI immediately to reflect the change // The server confirmation will trigger another refresh, but this gives immediate feedback RefreshTaskTrace(); // Clear selection if the abandoned task was selected if (m_idSelTask == (int)topTaskId) { m_idSelTask = 0; } } public void OnCommand_CANCEL(string szCommand) {} public void OnCommand_TreasureMap(string szCommand) {} public void OnCommand_FinishTask(string szCommand) {} public void OnCommand_GotoNPC(string szCommand) {} public void OnEventLButtonDown_Tv_Quest(uint itemData) { // UpdateTask((int)itemData); // POINT ptPos = pObj->GetPos(); // A3DVIEWPORTPARAM *p = m_pA3DEngine->GetActiveViewport()->GetParam(); // int x = GET_X_LPARAM(lParam) - ptPos.x - p->X; // int y = GET_Y_LPARAM(lParam) - ptPos.y - p->Y; // PAUITREEVIEW pTree = (PAUITREEVIEW)pObj; // // if( AUI_PRESS(VK_SHIFT) && m_iType == 0) // { // P_AUITREEVIEW_ITEM pItem = pTree->HitTest(x, y); // // if( pItem ) OnCommand_focus("focus"); // } // P_AUITREEVIEW_ITEM pItem = pTree->GetSelectedItem(); // var pItem = m_pTv_Quest.GetItemByData(itemData); int idTask = (int)itemData; // int idTask(0); // if( pItem && pTree->GetParentItem(pItem) != pTree->GetRootItem()) // idTask = pTree->GetItemData(pItem); // if (idTask == 0) return; // // if (m_szName == "Win_Quest" && CDlgAutoHelp::IsAutoHelp()) // { // if(pTree->GetHitArea(x,y) == AUITREEVIEW_RECT_FRAME) // CDlgWikiShortcut::PopQuestWiki(GetGameUIMan(),idTask); // } m_idSelTask = idTask; } // void OnEventMouseMove_Txt_QuestItem(WPARAM wParam, LPARAM lParam, AUIObject *pObj); // void OnEventLButtonDown_Txt_QuestItem(WPARAM wParam, LPARAM lParam, AUIObject *pObj); // void OnEventLButtonDown_Award_Item(WPARAM wParam, LPARAM lParam, AUIObject *pObj); // // void GetItemLinkItemOn(int x, int y, PAUIOBJECT pObj, AUITEXTAREA_EDITBOX_ITEM *pLink); // // // get formatted data // static ACString FormatTaskText(const ACHAR* szText, A3DCOLOR background); Color GetTaskColor(int idType) { // TODO: Map task type to color. Default white. if (idType < (int)ENUM_TASK_TYPE.enumTTDaily || idType >= (int)ENUM_TASK_TYPE.enumTTEnd) { // ASSERT(false && "wrong task type"); return Color.white; } Color result; if (!EC_Utility.STRING_TO_A3DCOLOR(GetStringFromTable(idType - (int)ENUM_TASK_TYPE.enumTTDaily + 3121), out result)) { // 解析颜色失败,返回白色 // [English] Failed to parse color, return white result = Color.white; } return result; } // static A3DCOLOR GetTaskColor(const ATaskTempl *pTempl); // static ACString FormatTime(int nSec, const ACString& desc, int timeLimit); private string GetTaskNameWithColor(ATaskTempl pTempl) { if (pTempl == null) return string.Empty; var type = (ENUM_TASK_TYPE)pTempl.m_FixedData.m_ulType; string rawName = ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(pTempl.m_FixedData.m_szName); if (type == ENUM_TASK_TYPE.enumTTQiShaList && !string.IsNullOrEmpty(rawName) && rawName[0] == '^') { // 如果是七杀榜任务且已经加了颜色,则颜色不变 // If QiShaList task already has color, keep it return rawName; } string strTaskName = GetTaskNameWithOutColor(pTempl); string strColorPreFix = A3DColorToString(GetTaskColor(pTempl)); return strColorPreFix + strTaskName; } private string GetTaskNameWithColor(ATaskTempl pTempl, out Color color) { if (pTempl == null) { color = Color.white; return string.Empty; } var type = (ENUM_TASK_TYPE)pTempl.m_FixedData.m_ulType; string rawName = ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(pTempl.m_FixedData.m_szName); if (type == ENUM_TASK_TYPE.enumTTQiShaList && !string.IsNullOrEmpty(rawName) && rawName[0] == '^') { // 如果是七杀榜任务且已经加了颜色,则颜色不变 // If QiShaList task already has color, keep it color = GetTaskColor(pTempl); return rawName; } string strTaskName = GetTaskNameWithOutColor(pTempl); color = GetTaskColor(pTempl); return strTaskName; } // static ACString GetTaskNameWithOutColor(const ATaskTempl* pTempl); private static string GetTaskNameWithOutColor(ATaskTempl pTempl) { if (pTempl == null) return string.Empty; string name = ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(pTempl.m_FixedData.m_szName); if (!string.IsNullOrEmpty(name) && name[0] == '^') { // 去掉颜色前缀(假设格式为 ^RRGGBB) // Strip color prefix (assume ^RRGGBB) if (name.Length > 7) return name.Substring(7); return string.Empty; } return name; } private Color GetTaskColor(ATaskTempl pTempl) { // TODO: Map task type/flags to color. Default white. // [English] Map task type/flags to color. Default white. if (pTempl == null) return UnityEngine.Color.white; int idType = (int)(ENUM_TASK_TYPE)pTempl.m_FixedData.m_ulType; if (idType < (int)ENUM_TASK_TYPE.enumTTDaily || idType >= (int)ENUM_TASK_TYPE.enumTTEnd) { // ASSERT(false && "wrong task type") // [English] wrong task type return UnityEngine.Color.white; } Color result; EC_Utility.STRING_TO_A3DCOLOR(GetStringFromTable(idType - (int)ENUM_TASK_TYPE.enumTTDaily + 3121), out result); return result; // return UnityEngine.Color.white; } private static string A3DColorToString(UnityEngine.Color c) { // 原代码将颜色转换为字符串前缀,这里返回空前缀以保持UI简洁 // Return empty prefix for TMP rich text compatibility return string.Empty; } // public bool Tick() { RefreshTaskTrace(); // Time-window task refresh: while in Search view, refresh the list when server time crosses a minute boundary. // This is throttled to avoid rebuilding large task lists every frame. if (m_iType == 1) { var host = GetHostPlayer(); var task = host != null ? host.GetTaskInterface() : null; if (task != null) { uint now = task.GetCurTime(); uint minuteKey = now / 60u; if (minuteKey != _lastSearchRefreshMinuteKey) { _lastSearchRefreshMinuteKey = minuteKey; // Preserve current selection if any, so refreshing doesn't feel disruptive. var curItem = m_pTv_Quest != null ? m_pTv_Quest.GetSelectedItem() : null; _pendingReselectTaskId = (curItem != null) ? m_pTv_Quest.GetItemData(curItem) : 0u; // Rebuild available task list according to current time-based prerequisites. SearchForTask(-1); // Restore selection best-effort (TaskTreeView selection is driven by EventBus). if (_pendingReselectTaskId != 0u) { EventBus.Publish(new TaskItemClickEvent { Data = _pendingReselectTaskId }); } } } } // Active view: refresh selected task detail periodically so countdown UI updates in real time. // (Wait-time tasks depend on m_ulTimePassed which changes with server time; without this, UI looks "stuck".) if (m_iType == 0 && Time.unscaledTime >= _nextActiveTimerUiRefreshAt) { _nextActiveTimerUiRefreshAt = Time.unscaledTime + 0.5f; // 2 Hz is plenty for countdown text var pTree = m_pTv_Quest; var pItem = pTree != null ? pTree.GetSelectedItem() : null; if (pItem != null && pTree.transform != pItem.transform.parent) { uint selectedTaskId = pTree.GetItemData(pItem); if (selectedTaskId > 0) { var task = GetHostPlayer()?.GetTaskInterface(); if (task != null) { Task_State_info tsi = default; task.GetTaskStateInfo(selectedTaskId, ref tsi, true); if (tsi.m_ulWaitTime > 0 || tsi.m_ulTimeLimit > 0 || tsi.m_ulProtectTime > 0) { UpdateTask((int)selectedTaskId); } } } } } // if( m_szName == "Win_Quest" && IsShow() ) { var pTree = m_pTv_Quest; var pItem = pTree.GetSelectedItem(); if( pItem ) { for( int i = 0; i < m_ImgCount; i++ ) m_pImg_Item[i].gameObject.SetActive(false); m_pTxt_BaseAward.gameObject.SetActive(false); // if( pTree->GetParentItem(pItem) != pTree->GetRootItem() ) if( pTree.transform != pItem.transform.parent ) { if (m_iType == 0) { UpdateTask((int)pTree.GetItemData(pItem)); } else if (m_iType == 1) { SearchForTask((int)pTree.GetItemData(pItem)); } } else { m_idLastTask = -2; m_pTxt_Content.SetText(""); m_pTxt_QuestItem.SetText(""); _nameTaskText.SetText(""); m_pBtn_Abandon.interactable = false; UpdateTaskConfirm(0, false); Btn_TreasureMap.interactable = false; } } // TODO // UpdateGotoNPC(); } // return CDlgBase::Tick(); return true; } public void TickTaskTrace() { if (m_TaskTraceCounter.IncCounter(EC_Game.GetRealTickTime())) { m_TaskTraceCounter.Reset(); RefreshTaskTrace(); } } public void RefreshTaskTrace() { // Get AUIManager, fallback to GetGameUIMan() if m_pAUIManager is not set yet // 获取AUIManager,如果m_pAUIManager尚未设置则回退到GetGameUIMan() AUIManager auiManager = GetAUIManager(); if (auiManager == null) { CECGameUIMan gameUIMan = GetGameUIMan(); if (gameUIMan == null) { return; } auiManager = gameUIMan; } DlgTaskTrace pDlgTaskTrace = auiManager.GetDialog("Win_QuestMinion") as DlgTaskTrace; if (pDlgTaskTrace == null) return; if (m_pTog_bShowTrace && !m_bShowTrace) return; ShowType showType = pDlgTaskTrace.GetShowType(); // bool bShow = showType == ShowType.ST_TRACED || showType == ShowType.ST_TITLE; bool bShow =true;//for test if (bShow) { CECHostPlayer host = GetHostPlayer(); CECTaskInterface pTask = host.GetTaskInterface(); if( m_vecTasksCanFinish.Count > 0 ) { for(int i = 0; i < m_vecTasksCanFinish.Count; i++ ) { int idTask = m_vecTasksCanFinish[i]; bool bCanFinish = pTask.CanFinishTask((uint)idTask); bool bHasTask = pTask.HasTask((uint)idTask); if(!bHasTask || !bCanFinish) { // 任务未完成,状态变化要删除 m_vecTasksCanFinish.RemoveAt(i); i--; if (bHasTask && !bCanFinish) { if (!m_vecTasksUnFinish.Contains(idTask)) m_vecTasksUnFinish.Add(idTask); } } } } if( m_vecTasksUnFinish.Count > 0 ) { for(int i = 0; i < m_vecTasksUnFinish.Count; i++ ) { int idTask = m_vecTasksUnFinish[i]; bool bCanFinish = pTask.CanFinishTask((uint)idTask); bool bHasTask = pTask.HasTask((uint)idTask); if(!bHasTask || bCanFinish) { // �������û���ˣ�����״̬�仯Ҫ�Ƴ����� m_vecTasksUnFinish.RemoveAt(i); i--; if (bHasTask && bCanFinish) { if (!m_vecTasksCanFinish.Contains(idTask)) m_vecTasksCanFinish.Add(idTask); } } } } List tasks = new List(); tasks.Capacity = m_vecTasksCanFinish.Count + m_vecTasksUnFinish.Count; tasks.AddRange(m_vecTasksCanFinish); tasks.AddRange(m_vecTasksUnFinish); List titlle_list = new List(); List titletask_list = new List(); //Dictionary title_map = new Dictionary(); if (pDlgTaskTrace.GetShowType() == ShowType.ST_TITLE) { ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); //pMan.GetTitleTasks(pTask, titlle_list); for (int i = 0; i < titlle_list.Count; i++) { if (IsTaskTraceable(titlle_list[i].GetID())) titletask_list.Add((int)titlle_list[i].GetID()); } } // Always refresh task trace, even when lists are empty, to clear/update the display if (!(GetHostPlayer().IsDead() || GetHostPlayer().IsTrading() || 0 != GetHostPlayer().GetBoothState())) { pDlgTaskTrace.RefreshTaskTrace(tasks.ToArray(), tasks.Count, titletask_list.ToArray(), titletask_list.Count, true); } } else if (showType == ShowType.ST_CONTRIBUTION) { pDlgTaskTrace.UpdateContributionTask(); } } public bool IsTaskTraceable(uint idTask) { ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); ATaskTempl pTemp = pMan.GetTaskTemplByID(idTask); // ��������׷�� if (IsQuestionTask(pTemp)) { return false; } Task_State_info tsi = new Task_State_info(); CECTaskInterface pTask = GetHostPlayer().GetTaskInterface(); pTask.GetTaskStateInfo(idTask, ref tsi); bool bTrace = tsi.m_ulTimeLimit > 0 || tsi.m_ulProtectTime > 0 || tsi.m_ulNPCToProtect > 0 || tsi.m_MonsterWanted[0].m_ulMonstersToKill > 0 || tsi.m_PlayerWanted[0].m_ulPlayersToKill > 0 || tsi.m_ItemsWanted[0].m_ulItemId > 0 || tsi.m_ulWaitTime > 0 || tsi.m_TaskCharArr._finish > 0 || tsi.m_ulReachLevel > 0 || tsi.m_ulReachRealm > 0 || tsi.m_ulReachReincarnation > 0; // check the condition from template if(!bTrace) { if (pTemp.m_FixedData.m_ulReachSiteCnt > 0 || (pTemp.m_FixedData.m_ulAwardNPC > 0 && pTask.CanFinishTask((uint)idTask))){ bTrace = true; } } return bTrace; } public bool IsQuestionTask(ATaskTempl pTemp){ return pTemp.m_FixedData.m_ulType == (uint)ENUM_TASK_TYPE.enumTTQuestion; } public bool UpdateTask(int idTask = -1) { // Only rebuild the list if viewing "Have Quest" (m_iType == 0) // But always allow updating specific task details regardless of view type if (idTask < 0 && m_iType != 0) { return true; } // ATaskTemplMan *pMan = GetGame()->GetTaskTemplateMan(); ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); // Host player can be null during early UI bootstrap (before InitCharacter/self-info). // In that case just no-op; quest UI will populate once the player exists and the dialog is reopened/refreshed. var host = EC_Game.GetGameRun()?.GetHostPlayer(); if (host == null) return true; CECTaskInterface pTask = host.GetTaskInterface(); if (pTask == null) { BMLogger.LogError("No CECTaskInterface found !!!"); return false; } // PAUITEXTAREA pTextDesc = m_pTxt_Content; var pTextDesc = m_pTxt_Content; // PAUITEXTAREA pTextItem = m_pTxt_QuestItem; var pTextItem = m_pTxt_QuestItem; string strNewTextItem = ""; string strNewHintItem = ""; bool bLastTaskChanged = false; // PAUIOBJECT pObj = GetDlgItem("Txt_Contribution"); // if (pObj) { // ACString strText; // strText.Format(_AL("%d"), GetHostPlayer()->GetWorldContribution()); // pObj->SetText(strText); // } if ( idTask >= 0) { ATaskTempl pTemp = pMan.GetTaskTemplByID((uint)idTask); if (pTemp != null) { // Only update description and name if task changed if( idTask != m_idLastTask ) { _nameTaskText.SetText(EC_Utility.FormatForTextMeshPro(GetTaskNameWithColor(pTemp))); //pTextDesc->SetText(FormatTaskText(pTemp->GetDescription(), pTextDesc->GetColor())); pTextDesc.SetText(EC_Utility.FormatForTextMeshPro(pTemp.GetDescription())); m_idLastTask = idTask; bLastTaskChanged = true; } m_pBtn_Abandon.interactable = pMan.CanGiveUpTask((uint)idTask); // Always refresh task state info to get latest progress data // This ensures real-time updates when task progress changes // When viewing "Have Quest" (m_iType == 0), tasks are active, so pass true to read kill counts Task_State_info tsi = new Task_State_info(); pTask.GetTaskStateInfo((uint)idTask, ref tsi, m_iType == 0); // Clear first strNewTextItem = ""; // Base desc UpdateTaskBaseDesc(ref strNewTextItem, tsi); // Award NPC int nANPC = (int)pTemp.GetAwardNPC(); UpdateAwardNPC(ref strNewTextItem, nANPC, idTask); // Complete condition - always refresh to show updated progress UpdateCompleteCondition(ref strNewTextItem, ref strNewHintItem, tsi); // Wanted Item - always refresh to show updated item counts UpdateItemWanted(ref strNewTextItem, tsi, idTask); // Treasure Map UpdateTreasureMap(ref strNewTextItem); // Task Confirm - always refresh to update button state UpdateTaskConfirm(idTask, pTemp.m_FixedData.m_enumFinishType == (uint)TaskFinishType.enumTFTConfirm); // Award - always refresh to show updated award preview Task_Award_Preview award = default; pTask.GetTaskAwardPreview((uint)idTask, ref award); UpdateBaseAward(award); UpdateItemAward(award); // GameObject pObj = GetDlgItem("Btn_TreasureMap"); if (Btn_TreasureMap != null) { Btn_TreasureMap.gameObject. SetActive(pTemp.m_FixedData.m_enumMethod == (uint)TaskCompletionMethod.enumTMReachTreasureZone); } } else { Debug.LogError($"Task {idTask} not found ATaskTempl !!!"); } } else { ClearContent(true); if (m_pBtn_FinishTask) m_pBtn_FinishTask.gameObject.SetActive(false); for (int i = 0; i < pTask.GetTaskCount(); i++) { int id = (int)pTask.GetTaskId((uint)i); AddTaskNode(id); } SortTaskNodeByType(); // Resolve all prefabs after quest loading is complete (after SetLastItem() calls are done) // 在任务加载完成后解析所有预制体(在SetLastItem()调用完成后) m_pTv_Quest.ResolveAllPrefabs(); string strTemp; int iMaxTaskCount = TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN; strTemp = $"{pTask.GetTaskCount()}/{iMaxTaskCount}"; if (m_pTxt_QuestNO != null) m_pTxt_QuestNO.text = strTemp; } // GetGameUIMan()->ReplaceColor(&strNewTextItem, A3DCOLORRGB(255, 255, 255), pTextItem->GetColor()); SetTextItemText(strNewTextItem, pMan.GetTaskTemplByID((uint)idTask) != null && !bLastTaskChanged, strNewHintItem); return true; } // // Guard: only handle search list when current UI type is 1 (search) public bool SearchForTask(int idTask = -1) { // Only process search list when in search view (m_iType == 1) // This prevents clearing the wrong list when updating if (m_iType != 1) { return true; } // Setup managers and UI references ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); CECTaskInterface pTask = GetHostPlayer()?.GetTaskInterface(); var pTextDesc = m_pTxt_Content; var pTextItem = m_pTxt_QuestItem; // Track composed text buffers and change flag string strNewTextItem = ""; string strNewHintItem = ""; bool bLastTaskChanged = false; // Simplified: assume we are in quest UI context if tree exists bool bQuestUI = m_pTv_Quest != null; // When a concrete task id is provided if (idTask >= 0) { ATaskTempl pTemp = pMan != null ? pMan.GetTaskTemplByID((uint)idTask) : null; if (pTemp != null) { // Update description when the selected task changes if (idTask != m_idLastTask) { _nameTaskText.SetText(EC_Utility.FormatForTextMeshPro(GetTaskNameWithColor(pTemp))); Debug.Log("[DlgTask] SearchForTask name task: " + _nameTaskText.text); if (pTextDesc != null) pTextDesc.SetText(EC_Utility.FormatForTextMeshPro(pTemp.GetDescription())); m_idLastTask = idTask; bLastTaskChanged = true; } // Optional: update tree item text if needed (skip if API not available) if (bQuestUI) { var pItem = m_pTv_Quest.GetSelectedItem(); if (pItem != null) { uint id = m_pTv_Quest.GetItemData(pItem); // NOTE: ATaskTemplMan.IsTaskToPush may be conditionally compiled; avoid hard dependency // If needed, uncomment when method is available: // if (pMan.IsTaskToPush((int)id)) m_pTv_Quest.SetItemText(pItem, GetTaskNameWithColor(pTemp)); } } // Get task state info Task_State_info tsi = default; if (pTask != null) pTask.GetTaskStateInfo((uint)idTask, ref tsi, false); // Reset composed text buffer strNewTextItem = ""; // Append: base description UpdateTaskBaseDesc(ref strNewTextItem, tsi); // Append: deliver NPC UpdateDeliverNPC(ref strNewTextItem, (int)pTemp.GetDeliverNPC(), idTask); // Append: award NPC int nANPC = (int)pTemp.GetAwardNPC(); UpdateAwardNPC(ref strNewTextItem, nANPC, idTask); // Append: completion conditions UpdateCompleteCondition(ref strNewTextItem, ref strNewHintItem, tsi); // Append: wanted items UpdateItemWanted(ref strNewTextItem, tsi, idTask); // Preview and show awards Task_Award_Preview award = default; if (pTask != null) pTask.GetTaskAwardPreview((uint)idTask, ref award); UpdateBaseAward(award); UpdateItemAward(award); } else { // No template found for id: clear content m_idLastTask = -2; if (m_pTxt_Content != null) m_pTxt_Content.SetText(""); if (m_pTxt_QuestItem != null) m_pTxt_QuestItem.SetText(""); } } else { // zhangyitian 20140521 先将可接任务列表清空,再判断是否有可接任务 // zhangyitian 20140521 First clear the available tasks list, then check if there are available tasks // 修正了原先没有可接任务时,可接任务列表显示已接任务的问题 // Fix: prevent accepted tasks from showing when there are no available tasks ClearContent(false); // TaskTemplLst ttl; List ttl = new List(); // TaskTemplLst -> List pMan.GetAvailableTasks(pTask, ttl); if( ttl.Count <= 0 ) return true; for(int i = 0; i < ttl.Count; i++ ) { int id = (int)ttl[i].GetID(); AddTaskNode(id); } SortTaskNodeByType(); // Resolve all prefabs after quest loading is complete (after SetLastItem() calls are done) // 在任务加载完成后解析所有预制体(在SetLastItem()调用完成后) m_pTv_Quest.ResolveAllPrefabs(); // string strTemp; // ActiveTaskList pLst = (ActiveTaskList)pTask.GetActiveTaskList(); // int iMaxTaskCount = pLst->GetMaxSimultaneousCount(); // strTemp.Format(_AL("%d/%d"), pTask->GetTaskCount(), iMaxTaskCount); // m_pTxt_QuestNO->SetText(strTemp); } // Apply colors and set composed text into UI bool hasTempl = idTask >= 0 && pMan != null && pMan.GetTaskTemplByID((uint)idTask) != null; SetTextItemText(strNewTextItem, hasTempl && !bLastTaskChanged, strNewHintItem); // Done return true; } // // //�������������б��������ɽ�������ѽ�������ѽ����� zhangyitian // When task updates, the available task list also needs to be updated, otherwise the available task list won't update public bool UpdateQuestView() { // Refresh the list for the current view type // This ensures that when tasks are taken/completed/abandoned, the visible list is updated bool result = true; if (m_iType == 0) { // Refresh "Have Quest" list (taken tasks) result = UpdateTask(-1); } else if (m_iType == 1) { // Refresh "Search Quest" list (available tasks) result = SearchForTask(-1); } // Refresh the currently selected task details if one is selected // This ensures task progress updates are reflected in real-time var pTree = m_pTv_Quest; var pItem = pTree?.GetSelectedItem(); if (pItem != null && pTree.transform != pItem.transform.parent) { uint selectedTaskId = pTree.GetItemData(pItem); if (selectedTaskId > 0) { if (m_iType == 0) { // Refresh the selected task's details to show updated progress UpdateTask((int)selectedTaskId); } else if (m_iType == 1) { // For search view, refresh the selected task SearchForTask((int)selectedTaskId); } } } return result; } // // bool IsPQTaskOrSubTask(int idTask); // bool IsTreasureMapTask(int idTask); // // bool TraceTask(int idTask); // void SyncTrace(void* pData, bool fromServer); // bool IsShowTrace(){return m_bShowTrace;} // // typedef CECGame::ObjectCoords ObjectCoords; // static const ObjectCoords& GetObjectCoords() { return m_TargetCoord; } // static const ACString& GetTraceName() { return m_strTraceName; } // static void SetTraceObjects(const ObjectCoords& objs, const ACString& name); // static const MINE_ESSENCE* SearchTaskMine(int idTask); // // ACString GetKillPlayerRequirements(const Task_State_info& tsi,int iIndex); // // void SwitchTaskTrace(int idTask); // void OnTaskPush(); // ���µĿɽ����� // void OnTaskProcessUpdated(int idTask); // �ѽ�����������Ҫ��ǰ��ʾ�� // void OnTaskItemGained(int idItem); public static MINE_ESSENCE SearchTaskMine(int idTask, out bool found) { if(idTask <= 0) { found = false; return default; } if (m_TaskMines.Count == 0) { var pDataMan = ElementDataManProvider.GetElementDataMan(); foreach (var kv in pDataMan.essence_id_data_type_map) { if (kv.Value == DATA_TYPE.DT_MINE_ESSENCE && kv.Key != 0) { DATA_TYPE dt = DATA_TYPE.DT_INVALID; var pMine = (MINE_ESSENCE)pDataMan.get_data_ptr(kv.Key, ID_SPACE.ID_SPACE_ESSENCE, ref dt); m_TaskMines[(int)pMine.task_in] = pMine; } } // avoid duplicated init m_TaskMines[0] = null; } var itr = m_TaskMines.TryGetValue(idTask, out var value); found =itr; return itr ? (MINE_ESSENCE)value : default; } #endregion #region PRIVATE METHODS private bool OnInitDialog() { // m_pTxt_QuestNO = (PAUILABEL)GetDlgItem("Txt_QuestNO"); // m_pTv_Quest = (PAUITREEVIEW)GetDlgItem("Tv_Quest"); // m_pTxt_Content = dynamic_cast(GetDlgItem("Txt_Content")); // m_pTxt_QuestItem = dynamic_cast(GetDlgItem("Txt_QuestItem")); // m_pBtn_Abandon = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_Abandon"); // m_pTxt_BaseAward = (PAUILABEL)GetDlgItem("Txt_BaseAward"); // m_pBtn_SearchQuest = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_SearchQuest"); // m_pBtn_HaveQuest = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_HaveQuest"); // m_pBtn_bShowTrace = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_ShowTrace"); // m_pBtn_FinishTask = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_FinishTask"); // m_pBtn_GotoNPC = (PAUISTILLIMAGEBUTTON)GetDlgItem("Btn_GotoNPC"); if (m_pBtn_FinishTask) m_pBtn_FinishTask.gameObject.SetActive(false); if (m_pBtn_GotoNPC) m_pBtn_GotoNPC.gameObject.SetActive(false); // TODO: Set button pushed state // if (m_pBtn_HaveQuest != null) { /* set pushed state if needed */ } GameObject pObj = GetDlgItem("Btn_TreasureMap"); if (pObj != null) { pObj.SetActive(false); } pObj = GetDlgItem("Img_New"); if (pObj != null) { pObj.SetActive(false); } if (m_pTxt_QuestNO != null) { m_pTxt_QuestNO.text = "0"; } m_TaskTraceCounter.SetPeriod(950); return true; } private T GetDlgItem(string name) { var t = transform.Find(name); if (t != null) return t.GetComponent(); return default(T); } public new CECHostPlayer GetHostPlayer() { if(EC_Game.GetGameRun() == null) { BMLogger.LogError("EC_Game.GetGameRun() is null !!!"); return null; } if (EC_Game.GetGameRun().GetHostPlayer() == null) { BMLogger.LogError("EC_Game.GetHostPlayer() is null !!!"); return null; } return EC_Game.GetGameRun().GetHostPlayer(); } // // virtual bool OnInitDialog(); void OnShowDialog() { OnCommand_showtrace(null,m_bShowTrace); if (m_idSelTask != 0) UpdateTask(); } void OnHideDialog() { if (m_szName != "Win_Quest") return; var pTree = m_pTv_Quest; var pItem = pTree?.GetSelectedItem(); if( pItem ) m_idSelTask = (int)pTree.GetItemData(pItem); else m_idSelTask = 0; } // virtual void OnHideDialog(); // virtual bool OnChangeLayout(PAUIOBJECT pMine, PAUIOBJECT pTheir); // virtual void OnChangeLayoutEnd(bool bAllDone); // private void InsertTaskChildren(TaskTreeViewHolder pRoot, uint idTask, bool bExpand, bool bKey) { var pTreeTask = m_pTv_Quest; var pMan = EC_Game.GetTaskTemplateMan(); var pTask = GetHostPlayer()?.GetTaskInterface(); if (pTreeTask == null || pMan == null || pTask == null) return; ATaskTempl parentTempl = pMan.GetTaskTemplByID(idTask); if (parentTempl == null) return; ATaskTempl child = parentTempl.m_pFirstChild; while (child != null) { uint id = child.m_FixedData.m_ID; Color disPlayColor=Color.white; string text = GetTaskNameWithColor(child,out disPlayColor); var pItem = pTreeTask.InsertItem(text, pRoot, null); pItem.SetItemTextColor(disPlayColor); if (pItem != null) { pTreeTask.SetItemData(pItem, id); if ((int)id == m_idSelTask) { if (m_pBtn_Abandon != null) m_pBtn_Abandon.interactable = true; UpdateTask((int)id); } // Optional: colorize key tasks if UI supports it } InsertTaskChildren(pItem, id, bExpand, bKey); child = child.m_pNextSibling; } } // [中文] 仅插入“已接任务(Active)”中的子任务(基于 ActiveTaskList 的 Child/NextSbl 索引) // [English] Insert only active subtasks (based on ActiveTaskList Child/NextSbl indices) private void InsertActiveTaskChildren(TaskTreeViewHolder pRoot, uint idTask) { var pTreeTask = m_pTv_Quest; var pMan = EC_Game.GetTaskTemplateMan(); var pTask = GetHostPlayer()?.GetTaskInterface(); if (pTreeTask == null || pMan == null || pTask == null) return; ActiveTaskList pList = pTask.GetActiveTaskList(); if (pList == null) return; ActiveTaskEntry parentEntry = pList.GetEntry(idTask); if (parentEntry == null) return; char idx = parentEntry.m_ChildIndex; while (idx != (char)0xff) { int childIndex = (byte)idx; if (childIndex < 0 || childIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN) break; ActiveTaskEntry childEntry = pList.m_TaskEntries[childIndex]; if (childEntry == null || childEntry.m_ID == 0) break; uint childId = childEntry.m_ID; ATaskTempl childTempl = pMan.GetTaskTemplByID(childId); Color disPlayColor=Color.white; string text = childTempl != null ? GetTaskNameWithColor(childTempl,out disPlayColor) : $"Task {childId}"; var pItem = pTreeTask.InsertItem(text, pRoot, null); pItem.SetItemTextColor(disPlayColor); if (pItem != null) { pTreeTask.SetItemData(pItem, childId); if ((int)childId == m_idSelTask) { if (m_pBtn_Abandon != null) m_pBtn_Abandon.interactable = true; UpdateTask((int)childId); } // recurse into active children InsertActiveTaskChildren(pItem, childId); } idx = childEntry.m_NextSblIndex; } } private void SetTextItemText(string strNewTextItem, bool keepScrollPos, string strNewHintItem) { var pTextItem = m_pTxt_QuestItem; if (pTextItem == null) return; // Preserve scroll position if inside a ScrollRect // UnityEngine.UI.ScrollRect scrollRect = pTextItem.GetComponentInParent(); // float oldNormPos = scrollRect != null ? scrollRect.verticalNormalizedPosition : 0f; string formatted = EC_Utility.FormatForTextMeshPro(strNewTextItem ?? string.Empty); if (!string.Equals(formatted, pTextItem.text)) { pTextItem.text = formatted; // TODO: apply hint to a tooltip UI if available (strNewHintItem) } // if (keepScrollPos && scrollRect != null) // { // // Restore previous scroll position // scrollRect.verticalNormalizedPosition = oldNormPos; // } } // void SetTaskText(PAUIOBJECT pObj, ACString* pStr); // int GetTaskIndex(int idTask); // // bool IsQuest()const; // bool IsShowHaveQuest()const; // int GetSelectedTaskFromUI(); // bool IsTreasureMapSelected(); // // // update task content in dialog // update task content in dialog (converted from C++) private void UpdateBaseAward(Task_Award_Preview award) { // Ported from original C++: strTemp.Format(GetStringFromTable(3201), award.m_ulGold); var sb = new System.Text.StringBuilder(); int colCount = 0; const int col = 3; string strTemp; if (award.m_ulGold > 0) { strTemp = Format(GetStringFromTable(3201), award.m_ulGold); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (award.m_ulExp > 0) { strTemp = Format(GetStringFromTable(3202), award.m_ulExp); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (award.m_ulSP > 0) { strTemp = Format(GetStringFromTable(3203), award.m_ulSP); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (award.m_ulRealmExp > 0) { strTemp = Format(GetStringFromTable(3207), award.m_ulRealmExp); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (award.m_iForceContrib > 0) { strTemp = Format(GetStringFromTable(3205), award.m_iForceContrib); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (award.m_iForceRepu > 0) { strTemp = Format(GetStringFromTable(3206), award.m_iForceRepu); sb.Append(strTemp); if ((++colCount) % col == 0) sb.Append("\n"); } if (sb.Length > 0 && m_pTxt_BaseAward != null) { m_pTxt_BaseAward.text = EC_Utility.FormatForTextMeshPro(sb.ToString()); m_pTxt_BaseAward.gameObject.SetActive(true); } } private void UpdateItemAward(Task_Award_Preview award) { bool bShowItem = false; if (award.m_bHasItem) { if (!award.m_bItemKnown) { if (m_pTxt_BaseAward != null && m_pTxt_BaseAward.gameObject.activeSelf) { string strAward = (GetStringFromTable(3204) ?? string.Empty) + "\n" + (m_pTxt_BaseAward.text ?? string.Empty); m_pTxt_BaseAward.text = EC_Utility.FormatForTextMeshPro(strAward); } else if (m_pTxt_BaseAward != null) { m_pTxt_BaseAward.text = EC_Utility.FormatForTextMeshPro(GetStringFromTable(3204) ?? string.Empty); m_pTxt_BaseAward.gameObject.SetActive(true); } } else { int max = m_pImg_Item != null ? m_pImg_Item.Length : 0; for (int i = 0; i < max; i++) { if (i < award.m_ulItemTypes) { var img = m_pImg_Item[i]; if (img == null) continue; var sprite = EC_IvtrItemUtils.Instance.ResolveItemIconSprite((int)award.m_ItemsId[i]); if (sprite != null) img.sprite = sprite; img.color = Color.white; img.gameObject.SetActive(true); var countLabel = img.GetComponentInChildren(true); if (countLabel != null) countLabel.text = award.m_ItemsNum[i].ToString(); bShowItem = true; } else if (m_pImg_Item[i] != null) { m_pImg_Item[i].gameObject.SetActive(false); } } } } // adjust the label position relative to item icons (approximate) if (m_pTxt_BaseAward != null && m_pImg_Item != null && m_pImg_Item.Length > 0 && m_pImg_Item[0] != null) { var txtRT = m_pTxt_BaseAward.rectTransform; var imgRT = m_pImg_Item[0].rectTransform; var pos = imgRT.anchoredPosition; var sz = imgRT.sizeDelta; float margin = 2f; if (bShowItem) txtRT.anchoredPosition = new Vector2(pos.x, pos.y - (sz.y + margin)); else txtRT.anchoredPosition = pos; } } private void UpdateTaskBaseDesc(ref string strText, Task_State_info tsi) { // Build the base description text from task state var sb = new System.Text.StringBuilder(); // NOTE: Original appended each entry in tsi.m_TaskCharArr (vector) // In C#, this array is not directly available; content is already localized elsewhere. // Append error message if any if (tsi.m_ulErrCode != 0) { string szMsg = GetFixedMsg(tsi.m_ulErrCode); if (!string.IsNullOrEmpty(szMsg)) { sb.Append(""); sb.Append(szMsg); string strTemp; if (tsi.m_ulErrCode == TaskInterfaceConstants.TASK_AWARD_FAIL_LEVEL_CHECK) strTemp = Format(GetStringFromTable(7637), tsi.m_ulPremLevelMin); else strTemp = GetStringFromTable(807); sb.Append(strTemp); sb.AppendLine(""); } } // Time limit and remaining time if (tsi.m_ulTimeLimit > 0) { int nSec = (int)tsi.m_ulTimeLimit; sb.Append(FormatTime(nSec, GetStringFromTable(245), 0)); int remain = System.Math.Max(0, (int)tsi.m_ulTimeLimit - (int)tsi.m_ulTimePassed); sb.Append(FormatTime(remain, GetStringFromTable(246), 0)); } // Wait time if (tsi.m_ulWaitTime > 0) { int nSec = System.Math.Max(0, (int)tsi.m_ulWaitTime - (int)tsi.m_ulTimePassed); sb.Append(FormatTime(nSec, GetStringFromTable(199), 0)); } // Protect NPC info and timers if (tsi.m_ulNPCToProtect > 0) { // Fallback text with NPC id; detailed name lookup omitted sb.Append(Format(GetStringFromTable(257) ?? "Protect NPC: %d", tsi.m_ulNPCToProtect)); sb.Append(FormatTime((int)tsi.m_ulProtectTime, GetStringFromTable(258), 0)); int remain = System.Math.Max(0, (int)tsi.m_ulProtectTime - (int)tsi.m_ulTimePassed); sb.Append(FormatTime(remain, GetStringFromTable(259), 0)); } // Apply to content text // if (m_pTxt_Content != null) // { // m_pTxt_Content.text += sb.ToString(); // } strText += sb.ToString(); } public static string FormatTime(int nSec, string desc, int timeLimit) { var ts = System.TimeSpan.FromSeconds(System.Math.Max(0, nSec)); string label = string.IsNullOrEmpty(desc) ? string.Empty : desc; return $"{label}{ts:hh\\:mm\\:ss}\n"; } // private static string GetStringFromTable(int id) // { // // TODO: return AUIManager.GetStringFromTable(id); // // // HARD CODED STRINGS // switch (id) // { // case 3101: // return "Hàng ngày"; // case 3102: // return "Tu chân"; // case 3103: // return "Chủ tuyến"; // case 3104: // return "Phụ tuyến"; // case 3105: // return "Event"; // case 3106: // return "7 Killer List"; // case 3107: // return "Bang hội"; // case 3108: // return "Management"; // case 3109: // return "Huyền thoại"; // case 3110: // return "Câu hỏi"; // case 3201: // return "Gold:"; // case 3202: // return "EXP:"; // case 3203: // return "SP:"; // case 3207: // return "Realm EXP:"; // case 3205: // return "Contribution:"; // case 3206: // return "Reputation:"; // case 7621: // return "NPC giao nhiệm vụ: "; // default: // return $"UnKnown_{id} "; // } // } private string GetStringFromTable(uint id) { // return AUIManager.GetStringFromTable(id); return GetStringFromTable((int)id); } private static string GetFixedMsg(uint id) { return BrewMonster.Network.EC_Game.GetFixedMsgs()?.GetWideString((int)id) ?? string.Empty; } private void UpdateDeliverNPC(ref string strText, int nDNPC, int idTask = 0) { // [中文] 交付NPC // [English] Deliver NPC if (nDNPC == 0) { return; } // [中文] 从元素数据中查找NPC // [English] Lookup NPC from element data string npcName = string.Empty; var edm = BrewMonster.ElementDataManProvider.GetElementDataMan(); if (edm != null) { if (edm.essence_id_data_type_map.TryGetValue((uint)nDNPC, out var dtype) && dtype == DATA_TYPE.DT_NPC_ESSENCE && edm.essence_id_data_map.TryGetValue((uint)nDNPC, out var obj) && obj is NPC_ESSENCE npc) { npcName = npc.Name; } } if (string.IsNullOrEmpty(npcName)) npcName = nDNPC.ToString(); // [中文] 追加到内容文本,如果找到坐标则添加可点击链接(如C++中的enumEICoord) // [English] Append to content text, add clickable link if coordinates found (like enumEICoord in C++) var sb = new System.Text.StringBuilder(); sb.Append(GetStringFromTable(7620)); // Always create clickable link (even if coords aren't known yet). // 总是创建可点击链接(即使暂时不知道坐标)。 sb.Append($"{npcName}"); sb.Append("\n"); strText += sb.ToString(); } private A3DVECTOR3 UpdateAwardNPC(ref string strText, int nANPC, int idTask = 0) { A3DVECTOR3 ret = new A3DVECTOR3(0f); // Award NPC if (nANPC == 0) { return ret; } // Lookup NPC name from element data string npcName = string.Empty; var edm = BrewMonster.ElementDataManProvider.GetElementDataMan(); if (edm != null) { if (edm.essence_id_data_type_map.TryGetValue((uint)nANPC, out var dtype) && dtype == DATA_TYPE.DT_NPC_ESSENCE && edm.essence_id_data_map.TryGetValue((uint)nANPC, out var obj) && obj is NPC_ESSENCE npc) { npcName = npc.Name; } } if (string.IsNullOrEmpty(npcName)) npcName = nANPC.ToString(); // Append to content, add clickable link if coordinates found (like enumEICoord in C++) // 追加到内容,如果找到坐标则添加可点击链接(如C++中的enumEICoord) var sb = new System.Text.StringBuilder(); sb.Append(GetStringFromTable(7621)); // Add NPC name as clickable link if coordinates found (like enumEICoord in C++) // 如果找到坐标,将NPC名称添加为可点击链接(如C++中的enumEICoord) // In C++, it always creates a clickable link if coordinates are found // 在C++中,如果找到坐标,它总是创建一个可点击链接 // For force navigate tasks, we'll trigger navigation when clicked // 对于强制导航任务,点击时将触发导航 // Always create clickable link (even if coords aren't known yet). // 总是创建可点击链接(即使暂时不知道坐标)。 sb.Append($"{npcName}"); sb.Append("\n"); strText += sb.ToString(); return ret; } // Update completion conditions (monsters, players, gold, level/reincarnation/realm) private void UpdateCompleteCondition(ref string strText, ref string strHint, Task_State_info tsi) { // Setup host reference // 设置宿主引用 var pHost = GetHostPlayer(); // Monster kill requirements // 怪物击杀条件 for (int i = 0; i < TaskInterfaceConstants.MAX_MONSTER_WANTED; i++) { if (tsi.m_MonsterWanted[i].m_ulMonsterId == 0) break; uint id = tsi.m_MonsterWanted[i].m_ulMonsterId; if (tsi.m_MonsterWanted[i].m_ulMonstersKilled > 0 || tsi.m_MonsterWanted[i].m_ulMonstersToKill > 0) { // Resolve monster name // 解析怪物名称 string strName = "????"; var edm = BrewMonster.ElementDataManProvider.GetElementDataMan(); if (edm != null && edm.essence_id_data_type_map.TryGetValue(id, out var dtype) && dtype == DATA_TYPE.DT_MONSTER_ESSENCE && edm.essence_id_data_map.TryGetValue(id, out var obj) && obj is MONSTER_ESSENCE pMonster) { // strName = ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(me.name); // Always create clickable link (even if coords aren't known yet). // 总是创建可点击链接(即使暂时不知道坐标)。 string monsterName = ByteToStringUtils.UshortArrayToUnicodeString(pMonster.name); strName = $"{monsterName}"; } // Build description for this monster requirement // 构建该怪物需求描述 string strTemp = ""; if (tsi.m_MonsterWanted[i].m_ulMonstersToKill > 0) { strTemp += Format(GetStringFromTable(7624), strName, tsi.m_MonsterWanted[i].m_ulMonstersKilled, tsi.m_MonsterWanted[i].m_ulMonstersToKill); } else { strTemp += Format(GetStringFromTable(256), tsi.m_MonsterWanted[i].m_ulMonstersKilled); } // Prefix label for first/next item // 首项/后续项的前缀标签 strText += (i == 0) ? GetStringFromTable(7622) : GetStringFromTable(7626); strText += strTemp; } } // Player kill requirements // 击杀玩家条件 for (int i = 0; i < TaskInterfaceConstants.MAX_PLAYER_WANTED; i++) { if (tsi.m_PlayerWanted[i].m_ulPlayersToKill == 0) break; if (tsi.m_ItemsWanted[i].m_ulItemId > 0) continue; strText += (i == 0) ? GetStringFromTable(7630) : GetStringFromTable(7626); strText += GetKillPlayerRequirements(tsi, i); } // Gold requirement // 金币需求 if (tsi.m_ulGoldWanted != 0) { string strTemp = Format(GetStringFromTable(7636), tsi.m_ulGoldWanted); strText += strTemp; } // Reincarnation requirement // 转生次数需求 if (tsi.m_ulReachReincarnation != 0) { int iLevel = GetReincarnationCount(pHost); string strColor = (iLevel < (int)tsi.m_ulReachReincarnation) ? "" : ""; if (iLevel < (int)tsi.m_ulReachReincarnation) { strHint += Format(GetStringFromTable(11144), iLevel); } strText += strColor; strText += Format(GetStringFromTable(11141), tsi.m_ulReachReincarnation); } // Level requirement // 等级需求 if (tsi.m_ulReachLevel != 0 && pHost != null) { int iLevel = pHost.GetBasicProps().iLevel; string strColor = (iLevel < (int)tsi.m_ulReachLevel) ? "" : ""; if (iLevel < (int)tsi.m_ulReachLevel) { strHint += Format(GetStringFromTable(11143), iLevel); } strText += strColor; strText += Format(GetStringFromTable(11140), tsi.m_ulReachLevel); } // Realm requirement // 境界等级需求 if (tsi.m_ulReachRealm != 0 && pHost != null) { int iLevel = GetRealmLevel(pHost); string strColor = (iLevel < (int)tsi.m_ulReachRealm) ? "" : ""; if (iLevel < (int)tsi.m_ulReachRealm) { strHint += Format(GetStringFromTable(11145), iLevel); } strText += strColor; strText += Format(GetStringFromTable(11142), (int)tsi.m_ulReachRealm); } } // Build text for player kill requirements // 构建击杀玩家需求的文本 private string GetKillPlayerRequirements(Task_State_info tsi, int index) { uint killed = tsi.m_PlayerWanted[index].m_ulPlayersKilled; uint toKill = tsi.m_PlayerWanted[index].m_ulPlayersToKill; return $" {killed}/{toKill}\n"; } // Get host reincarnation count (fallback implementation) // 获取宿主转生次数(回退实现) private int GetReincarnationCount(CECHostPlayer host) { return 0; // TODO: Replace with actual value when available } // Get host realm level via basic props level2 // 通过二级等级获取宿主境界等级 private int GetRealmLevel(CECHostPlayer host) { return host.GetBasicProps().iLevel2; } // Update wanted items section // 更新需要的物品部分 private void UpdateItemWanted(ref string strText, Task_State_info tsi, int idTask) { // Resolve task template // 获取任务模板 var pMan = EC_Game.GetTaskTemplateMan(); var pTempl = pMan != null ? pMan.GetTaskTemplByID((uint)idTask) : null; if (pTempl == null) return; // Iterate wanted items // 遍历需要的物品 for (int i = 0; i < TaskInterfaceConstants.MAX_ITEM_WANTED; i++) { if (tsi.m_ItemsWanted[i].m_ulItemId == 0) break; // Resolve item name // 解析物品名称 int itemTid = unchecked((int)tsi.m_ItemsWanted[i].m_ulItemId); string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(itemTid); if (string.IsNullOrEmpty(itemName)) itemName = $"Item {itemTid}"; // Find coordinates for item (like C++ GetTaskObjectCoordinates) // 查找物品的坐标(如C++ GetTaskObjectCoordinates) int search_id = 0; if (pTempl.m_FixedData.m_enumMethod != (uint)TaskCompletionMethod.enumTMKillPlayer) { int id = (int)tsi.m_ItemsWanted[i].m_ulMonsterId; if (id > 0) { search_id = id; } else { // TODO: Search for mine essence if needed // TODO: 如果需要,搜索矿点精华 // const MINE_ESSENCE* pMine = SearchTaskMine(idTask); // if(pMine) search_id = pMine->id; } } // Always create clickable link for the target (search_id). // 总是为目标(search_id)创建可点击链接。 string displayName = itemName; if (search_id > 0) { displayName = $"{itemName}"; } // Compose line: name and progress (gained/toGet) // 组合文本:名称与进度(已获得/所需) string strTemp = Format(GetStringFromTable(7625), displayName, tsi.m_ItemsWanted[i].m_ulItemsGained, tsi.m_ItemsWanted[i].m_ulItemsToGet); // Prefix for first or subsequent entries // 首项或后续项前缀 strText += (i == 0) ? GetStringFromTable(7623) : GetStringFromTable(7626); strText += strTemp; // If task is KillPlayer, also add player requirements line // 若为击杀玩家任务,同时追加玩家需求行 if (pTempl.m_FixedData.m_enumMethod == (uint)TaskCompletionMethod.enumTMKillPlayer && i < TaskInterfaceConstants.MAX_PLAYER_WANTED) { strText += GetKillPlayerRequirements(tsi, i); } } } private void UpdateTreasureMap(ref string strText) { // if (IsTreasureMapSelected()) // { // EditBoxItemBase item(enumEICoord); // item.SetName(GetStringFromTable(7629)); // item.SetInfo(GetStringFromTable(7629)); // item.SetColor(A3DCOLORRGB(0, 255, 0)); // // strText += (ACHAR)AUICOMMON_ITEM_CODE_START + item.Serialize(); // } } private void UpdateTaskConfirm(int idTask, bool bFinishType) { CECTaskInterface pTask = GetHostPlayer()?.GetTaskInterface(); if (m_pBtn_FinishTask != null && pTask != null && bFinishType) { m_pBtn_FinishTask.gameObject.SetActive(true); // TODO: Enable/disable based on task readiness // m_pBtn_FinishTask->Enable(pTask->IsTaskReadyToConfirm(idTask)); m_pBtn_FinishTask.interactable = pTask.IsTaskReadyToConfirm(idTask); } else m_pBtn_FinishTask.gameObject.SetActive(false); } // void UpdateGotoNPC(); // void ClearGotoNPC(); // // clear the task content in dialog void ClearContent(bool clearNPC) { m_idLastTask = -2; m_pTxt_Content.SetText(""); m_pTxt_BaseAward.gameObject.SetActive(false); for( int j = 0; j < m_ImgCount; j++ ) { m_pImg_Item[j].gameObject.SetActive(false); // TODO: Clear image data // m_pImg_Item[j]->SetData(0); } // TODO: Clear Tree quest view m_pTv_Quest.DeleteAllItems(); } // // add node to task tree void AddTaskNode(int id) { var pTreeTask = m_pTv_Quest; ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); ATaskTempl pTemp = pMan.GetTaskTemplByID((uint)id); if( pTemp == null ) { return; } uint nTaskType = pTemp.m_FixedData.m_ulType; if (pTemp.m_FixedData.m_DynTaskType != 0) nTaskType = (uint)ENUM_TASK_TYPE.enumTTEvent; uint nAfterType = 0; TaskTreeViewHolder pAfter = null, pParent = null; // P_AUITREEVIEW_ITEM pItem = pTreeTask->GetFirstChildItem(pTreeTask->GetRootItem()); // var pItem = pTreeTask.transform.parent.GetChild(0).GetComponent(); var pItem = pTreeTask.GetFirstChild(); while( pItem ) { uint nType = pItem.GetItemData(); if( nType == nTaskType ){ pParent = pItem; break; } else if (nType < nTaskType && nType > nAfterType){ nAfterType = nType; pAfter = pItem; } pItem = pTreeTask.GetNextSiblingItem(pItem); } // add Biggest node if not exist string strItem = GetTaskNameWithColor(pTemp, out Color color); if(pParent ==null) { pParent = pTreeTask.InsertItem(GetStringFromTable(3101 + nTaskType - 100), null, pAfter); pParent.SetItemTextColor(color); // TODO: Expand tree node // pTreeTask.Expand(pParent, AUITREEVIEW_EXPAND_EXPAND); pTreeTask.SetItemData(pParent, nTaskType); //if(nTaskType == enumTTLevel2) // pTreeTask.SetItemTextColor(pParent, GetTaskColor(pTemp)); } CECTaskInterface pTask = GetHostPlayer()?.GetTaskInterface(); if (pTask == null) return; bool bTaskPushed = pMan.IsTaskToPush(id) && !pTask.HasTask((uint)id); if (bTaskPushed) { strItem += GetStringFromTable(3100); } pItem = pTreeTask.InsertItem(strItem, pParent, null); pItem.SetItemTextColor(color); if( pTemp.IsKeyTask() ) { pItem.SetItemTextColor(GetTaskColor((int)ENUM_TASK_TYPE.enumTTLevel2)); } pTreeTask.SetItemData(pItem, (uint)id); // HaveQuest view: children should reflect ActiveTaskList, not template tree (otherwise they never disappear on completion) if (m_iType == 0) InsertActiveTaskChildren(pItem, (uint)id); else InsertTaskChildren(pItem, (uint)id, true, pTemp.IsKeyTask()); if( (int)id == m_idSelTask ) { // TODO : select the item in UI // pTreeTask.SelectItem(pItem); // m_pBtn_Abandon->Enable(true); UpdateTask(id); } } private void SortTaskNodeByType() { var pTreeTask = m_pTv_Quest; if (pTreeTask == null) return; // Collect direct children under the tree root (this component's transform) int childCount = pTreeTask.transform.childCount; var items = new List<(uint type, TaskTreeViewHolder item)>(childCount); for (int i = 0; i < childCount; i++) { var child = pTreeTask.transform.GetChild(i).GetComponent(); if (child == null) continue; uint nType = pTreeTask.GetItemData(child); items.Add((nType, child)); } // Sort by type ascending items.Sort((a, b) => a.type.CompareTo(b.type)); // Reorder siblings to match sorted order for (int i = 0; i < items.Count; i++) { items[i].item.transform.SetSiblingIndex(i); } } // // whether the task can be traced // bool IsTaskTraceable(int idTask); string Format( string formatStr, params object[] args ) { // Ported from original C++ ACString::Format() which uses vsprintf/vswprintf // Original implementation: AString& AString::Format(const char* szFormat, ...) { vsprintf(m_pStr, szFormat, argList); } // This converts printf-style format specifiers to C# format specifiers if (string.IsNullOrEmpty(formatStr)) return formatStr; // Process format string character by character to convert %d, %s, etc. to {0}, {1}, etc. // This matches the original vsprintf behavior exactly var sb = new System.Text.StringBuilder(); int paramIndex = 0; for (int i = 0; i < formatStr.Length; i++) { if (formatStr[i] == '%' && i + 1 < formatStr.Length) { // Check for %% (literal %) if (formatStr[i + 1] == '%') { sb.Append('%'); i++; // Skip the second % continue; } // Parse format specifier: %[flags][width][.precision][length]type int startPos = i; i++; // Skip the % // Track flags bool hasMinus = false; bool hasPlus = false; bool hasZero = false; // Parse flags: +, -, 0, space while (i < formatStr.Length && (formatStr[i] == '+' || formatStr[i] == '-' || formatStr[i] == '0' || formatStr[i] == ' ')) { if (formatStr[i] == '-') hasMinus = true; if (formatStr[i] == '+') hasPlus = true; if (formatStr[i] == '0') hasZero = true; i++; } // Parse width: digits int width = 0; int widthStart = i; while (i < formatStr.Length && char.IsDigit(formatStr[i])) { i++; } if (i > widthStart) { int.TryParse(formatStr.Substring(widthStart, i - widthStart), out width); } // Skip precision: .digits if (i < formatStr.Length && formatStr[i] == '.') { i++; while (i < formatStr.Length && char.IsDigit(formatStr[i])) { i++; } } // Skip length modifier: h, l, L, etc. while (i < formatStr.Length && (formatStr[i] == 'h' || formatStr[i] == 'l' || formatStr[i] == 'L')) { i++; } // Get type specifier if (i < formatStr.Length) { char typeChar = formatStr[i]; // Common types: d, i, u, o, x, X, f, e, E, g, G, c, s, p, n if ("diouxXeEfFgGaAcspn".IndexOf(typeChar) >= 0) { // Extract the full format specifier for special handling string fullSpec = formatStr.Substring(startPos, i - startPos + 1); // Check for special formats string csharpFormatSpec = ""; // Handle %-10d, %-5d, etc. (left-aligned with width) - check this FIRST // Also handle %-d (left-aligned without explicit width, but width might be 0) if (hasMinus && (typeChar == 'd' || typeChar == 'i' || typeChar == 'u' || typeChar == 's' || typeChar == 'c')) { if (width > 0) { // C# left alignment with width: {0,-10} (comma for alignment, negative for left-align) csharpFormatSpec = $",-{width}"; } else { // Just left alignment flag, no width specified - use default csharpFormatSpec = ""; } } // Handle %02d, %03d, etc. (zero-padded integers) - check before regular width else if (hasZero && width > 0 && (typeChar == 'd' || typeChar == 'i' || typeChar == 'u')) { csharpFormatSpec = $":D{width}"; } // Handle %10d, %5d, etc. (right-aligned with width, no zero-padding) else if (width > 0 && (typeChar == 'd' || typeChar == 'i' || typeChar == 'u' || typeChar == 's' || typeChar == 'c')) { // C# right alignment: {0,10} (comma for alignment) csharpFormatSpec = $",{width}"; } // Handle %+d (signed format) else if (hasPlus && (typeChar == 'd' || typeChar == 'i')) { csharpFormatSpec = ":+0;-0"; } // Handle %.2f, %.3f, etc. (float precision) else if (fullSpec.Contains(".") && (typeChar == 'f' || typeChar == 'F' || typeChar == 'e' || typeChar == 'E' || typeChar == 'g' || typeChar == 'G')) { int dotPos = fullSpec.IndexOf('.'); if (dotPos >= 0 && dotPos + 1 < fullSpec.Length) { int precStart = dotPos + 1; int precEnd = precStart; while (precEnd < fullSpec.Length - 1 && char.IsDigit(fullSpec[precEnd])) { precEnd++; } if (precEnd > precStart && int.TryParse(fullSpec.Substring(precStart, precEnd - precStart), out int precNum)) { if (typeChar == 'f' || typeChar == 'F') { csharpFormatSpec = $":F{precNum}"; } else if (typeChar == 'e' || typeChar == 'E') { csharpFormatSpec = $":E{precNum}"; } else if (typeChar == 'g' || typeChar == 'G') { csharpFormatSpec = $":G{precNum}"; } } } } // Build replacement - always replace recognized format specifiers // C# format: {index,alignment} for width, {index:format} for format specifiers if (csharpFormatSpec.Length > 0) { // If format spec starts with comma (alignment) or colon (format), use it directly // Otherwise, assume it's a format specifier and add colon if (csharpFormatSpec.StartsWith(",") || csharpFormatSpec.StartsWith(":")) { sb.Append($"{{{paramIndex}{csharpFormatSpec}}}"); } else { sb.Append($"{{{paramIndex}:{csharpFormatSpec}}}"); } } else { sb.Append($"{{{paramIndex}}}"); } paramIndex++; // Note: i currently points to the type character (e.g., 'd' in "%-10d") // The for loop will increment i, so we've consumed the entire format specifier } else { // Not a recognized format specifier, keep as-is sb.Append(formatStr[startPos]); i = startPos; // Reset to process next character } } else { // Incomplete format specifier, keep the % sb.Append('%'); i = startPos; // Reset to process next character } } else { sb.Append(formatStr[i]); } } // Use C#'s string.Format (equivalent to original vsprintf) // Handle case where we might have more format specifiers than arguments try { return string.Format(sb.ToString(), args); } catch (System.FormatException) { // If format fails (e.g., not enough arguments), return the original format string // This can happen if GetStringFromTable returns an unexpected format BMLogger.LogWarning($"Format failed for string: {formatStr}, expected {paramIndex} args, got {args.Length}"); return formatStr; } } #endregion } }