using System.Collections.Generic; using BrewMonster.Network; using BrewMonster.Managers; using BrewMonster.Scripts.Task; using BrewMonster.Scripts; using CSNetwork.GPDataType; using CSNetwork; namespace BrewMonster.Scripts.UI { public class CECUIHelper { public static string DlgTaskName = "Win_Quest"; public static A3DVECTOR3 GetTaskObjectCoordinates(int id, ref bool in_table) { in_table = false; // 0) If the id is actually a runtime object id (npc/player/matter), fetch it from world directly. // IMPORTANT: GPDataTypeHelper.ISPLAYERID returns true for ANY positive int, so we must not use it to // decide whether something is an "object id". Use a heuristic instead. // 0) 如果这个id其实是运行时对象ID(NPC/玩家/物品),直接从World取坐标。 // 重要:ISPLAYERID 对任何正整数都为 true,不能用它判断“是否对象ID”,这里用启发式判断。 bool isLikelyRuntimeObjectId = GPDataTypeHelper.ISNPCID(id) || GPDataTypeHelper.ISMATTERID(id) || (id > 100000000); // player ids are typically huge; template ids are usually small A3DVECTOR3 ret = new A3DVECTOR3(0); var world = EC_Game.GetGameRun()?.GetWorld(); if (world != null && isLikelyRuntimeObjectId) { var obj = world.GetObject(id, 0); if (obj != null) { var objPos = obj.transform.position; in_table = true; BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates isLikelyRuntimeObjectId: objPos={objPos.x},{objPos.y},{objPos.z}, inTable={in_table}"); ret.Set(objPos.x, objPos.y, objPos.z); return ret; } } List TargetTemp = new List(); ret = EC_Game.GetGameRun()?.GetHostPlayer().GetObjectCoordinates(id, out TargetTemp, ref in_table) ?? new A3DVECTOR3(0); // 1) Try live NPC/Monster in scene by template id (best match to "click name -> go to that entity") // 1) 先尝试在场景中按模板ID查找活体NPC/怪物(最符合“点名字就去找它”) //This only work in Major map not A61. Skip for now. // if(!in_table) // { // var npcMan = EC_ManMessageMono.Instance != null ? EC_ManMessageMono.Instance.CECNPCMan : null; // if (npcMan != null) // { // var npc = npcMan.FindNPCByTemplateID(id); // if (npc != null) // { // UnityEngine.Vector3 npcPos = npc.transform.position; // in_table = true; // BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates npcMan: npcPos={npcPos.x},{npcPos.y},{npcPos.z}, inTable={in_table}"); // ret.Set(npcPos.x, npcPos.y, npcPos.z); // } // } // } // else // { // BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates in_table: ret={ret.x},{ret.y},{ret.z}, inTable={in_table}"); // } if(ret.x != 0 && ret.y != 0 && ret.z != 0) { BMLogger.Log($"[CECUIHelper] GetHostPlayer GetObjectCoordinates True ret={ret.x},{ret.y},{ret.z}, inTable={in_table}"); return ret; } else { BMLogger.Log($"[CECUIHelper] GetHostPlayer TryGetFirstObjectCoord False ret={ret.x},{ret.y},{ret.z}, inTable={in_table}"); } // Fallback to task_npc table (C++: ATaskTemplMan::GetTaskNPCInfo) // 回退到task_npc表(C++:ATaskTemplMan::GetTaskNPCInfo) ATaskTemplMan pMan = EC_Game.GetTaskTemplateMan(); if (pMan != null && pMan.TryGetTaskNPCInfo((uint)id, out NPC_INFO info)) { // NOTE: Keep original PW coordinate mapping: ret.Set(x, z, y) // 注意:保持原版坐标映射:ret.Set(x, z, y) in_table = true; BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetTaskNPCInfo: info.x={info.x}, info.z={info.z}, info.y={info.y}, inTable={in_table}"); return new A3DVECTOR3(info.x, info.z, info.y); } else{ BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetTaskNPCInfo: not found for id={id}"); } // Fallback to coord_data.txt (C++: Configs/Coord_data.txt via CECGame::GetObjectCoord) // 回退到 coord_data.txt(C++:Configs/Coord_data.txt,通过 CECGame::GetObjectCoord) if (BrewMonster.Network.EC_Game.TryGetFirstObjectCoord(id.ToString(), out var coordPos, out var mapName)) { in_table = true; BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates TryGetFirstObjectCoord: coordPos.x={coordPos.x}, coordPos.y={coordPos.y}, coordPos.z={coordPos.z}, inTable={in_table}"); return new A3DVECTOR3(coordPos.x, coordPos.y, coordPos.z); } UnityEngine.Debug.LogWarning($"[CECUIHelper] GetTaskObjectCoordinates: Not found for id={id} (isLikelyRuntimeObjectId={isLikelyRuntimeObjectId})."); BMLogger.Log($"[CECUIHelper] GetTaskObjectCoordinates default return ret={ret.x},{ret.y},{ret.z}, inTable={in_table}"); return ret; // TODO: Implement this method properly // A3DVECTOR3 ret(0.f); // in_table = false; // CECGame::ObjectCoords TargetTemp; // ret = g_pGame->GetGameRun()->GetHostPlayer()->GetObjectCoordinates( // id, TargetTemp, in_table); // if (!in_table && MAJOR_MAP == g_pGame->GetGameRun()->GetWorld()->GetInstanceID()) { // ATaskTemplMan *pMan = g_pGame->GetTaskTemplateMan(); // const NPC_INFO* pInfo = pMan->GetTaskNPCInfo(id); // if(pInfo) { // ret.Set(pInfo->x, pInfo->z, pInfo->y); // in_table = true; // } // } // return ret; } // Follow coord like C++ CECUIHelper::FollowCoord(enumEICoord, taskId) // 像C++的CECUIHelper::FollowCoord(enumEICoord, taskId)一样跟随坐标 public static bool FollowCoord(int id, int taskId) { // If this task is a force-navigate task (or has force_navigate mapping), use bezier-force navigate // instead of normal auto-move (which looks like a straight line). // 如果该任务是强制导航任务(或在 force_navigate 中有映射),则使用贝塞尔强制导航, // 而不是普通自动移动(看起来像直线)。 if (taskId > 0) { CECHostPlayer hostPlayer = EC_Game.GetGameRun()?.GetHostPlayer(); if (hostPlayer != null) { bool shouldForceNavigate = false; // 1) Prefer template flag (C++: enumTMSimpleClientTaskForceNavi) // 1) 优先使用任务模板标记(C++:enumTMSimpleClientTaskForceNavi) var taskMan = EC_Game.GetTaskTemplateMan(); var templ = taskMan != null ? taskMan.GetTaskTemplByID((uint)taskId) : null; if (templ != null && templ.m_FixedData.m_enumMethod == (uint)TaskCompletionMethod.enumTMSimpleClientTaskForceNavi) { shouldForceNavigate = true; } // 2) Fallback: if force_navigate.txt contains this task, treat it as force-navigate even if template flag is missing. // 2) 回退:如果 force_navigate.txt 中存在该任务映射,即使模板标记缺失,也按强制导航处理。 if (!shouldForceNavigate) { var np = hostPlayer.GetNavigatePlayer(); var ctrl = np != null ? np.GetNavigateCtrl() : null; if (ctrl != null) { var tmp = new BrewMonster.Scripts.CECNavigateCtrl.INFO(); if (ctrl.GetNavigateInfo(taskId, ref tmp)) { shouldForceNavigate = true; } } } if (shouldForceNavigate) { UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: taskId={taskId} => force navigate (bezier) instead of normal auto-move"); hostPlayer.OnNaviageEvent(taskId, (int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_PREPARE); hostPlayer.OnNaviageEvent(taskId, (int)BrewMonster.Scripts.CECNavigateCtrl.NavigateEvent.EM_BEGIN); return true; } } } // Resolve coordinates bool inTable = false; A3DVECTOR3 vPos = GetTaskObjectCoordinates(id, ref inTable); BMLogger.Log($"[CECUIHelper] FollowCoord: vPos={vPos.x},{vPos.y},{vPos.z}, inTable={inTable}"); if (!inTable) { // Fallback: use task regions if available (move to the center of the first region) // 回退:如果任务有区域信息,则移动到第一个区域的中心 if (taskId > 0) { var taskMan = EC_Game.GetTaskTemplateMan(); var templ = taskMan != null ? taskMan.GetTaskTemplByID((uint)taskId) : null; var world = EC_Game.GetGameRun()?.GetWorld(); int curWorldId = world != null ? world.GetInstanceID() : 0; if (templ != null) { // Helper local function: pick first region center if in current world bool TryUseRegion(uint worldId, uint cnt, BrewMonster.Scripts.Task.Task_Region[] regions, string tag, out A3DVECTOR3 pos) { pos = new A3DVECTOR3(0); if (cnt == 0 || regions == null || regions.Length == 0) return false; if (curWorldId != 0 && worldId != 0 && worldId != (uint)curWorldId) return false; var r = regions[0]; float cx = (r.zvMin.x + r.zvMax.x) * 0.5f; float cy = (r.zvMin.y + r.zvMax.y) * 0.5f; float cz = (r.zvMin.z + r.zvMax.z) * 0.5f; pos = new A3DVECTOR3(cx, cy, cz); return true; } // 1) Deliver zone (often where quest giver is) if (templ.m_FixedData.m_bDelvInZone && TryUseRegion(templ.m_FixedData.m_ulDelvWorld, templ.m_FixedData.m_ulDelvRegionCnt, templ.m_FixedData.m_pDelvRegion, "DelvInZone", out vPos)) { inTable = true; } // 2) Reach-site regions (for reach-site tasks / also can be used as guidance) else if (TryUseRegion(templ.m_FixedData.m_ulReachSiteId, templ.m_FixedData.m_ulReachSiteCnt, templ.m_FixedData.m_pReachSite, "ReachSite", out vPos)) { inTable = true; } // 3) Enter region / leave region zones (some tasks use these to define where objectives happen) else if (TryUseRegion(templ.m_FixedData.m_ulEnterRegionWorld, templ.m_FixedData.m_ulEnterRegionCnt, templ.m_FixedData.m_pEnterRegion, "EnterRegion", out vPos)) { inTable = true; } else if (TryUseRegion(templ.m_FixedData.m_ulLeaveRegionWorld, templ.m_FixedData.m_ulLeaveRegionCnt, templ.m_FixedData.m_pLeaveRegion, "LeaveRegion", out vPos)) { inTable = true; } } } if (!inTable) { UnityEngine.Debug.LogWarning($"[CECUIHelper] FollowCoord: No coordinates for id={id}, taskId={taskId} (will not move)"); return false; } } // Start auto-move work to destination (this is what actually moves the player in this project) // 启动自动移动工作(这才是本项目中真正驱动角色移动的系统) CECHostPlayer host = EC_Game.GetGameRun()?.GetHostPlayer(); if (host == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Host player is null"); return false; } CECHPWorkMan wm = host.GetWorkMan(); if (wm == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: WorkMan is null"); return false; } CECHPWorkMove work = wm.CreateWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS) as CECHPWorkMove; if (work == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Failed to create WORK_MOVETOPOS"); return false; } // Prefer AutoPF intelligent route (original PW behavior) instead of naive straight-line. // 优先使用 AutoPF 智能寻路(原版 PW 行为),而不是简单直线移动。 work.SetDestination(CECHPWorkMove.DestTypes.DEST_AUTOPF, vPos); // If this coord link is to an NPC template id, store it so WorkMove can switch to WorkTrace near the NPC. // 如果该坐标链接指向 NPC 模板ID,保存下来,以便 WorkMove 接近 NPC 时切换到 WorkTrace。 if (taskId > 0) { work.SetTaskNPCInfo(id, taskId); } wm.StartWork_p2(work); return true; } // Follow coord using target coordinate list and trace name // 使用目标坐标列表和追踪名称跟随坐标 public static bool FollowCoord(List m_TargetCoord, string m_strTraceName) { // Validate inputs // 验证输入 if (m_TargetCoord == null || m_TargetCoord.Count == 0) { UnityEngine.Debug.LogWarning($"[CECUIHelper] FollowCoord: m_TargetCoord is null or empty, traceName={m_strTraceName} (will not move)"); return false; } // Use the first coordinate from the list // 使用列表中的第一个坐标 OBJECT_COORD targetCoord = m_TargetCoord[0]; A3DVECTOR3 vPos = targetCoord.vPos; // Log map information if available (for debugging) // 如果可用,记录地图信息(用于调试) if (!string.IsNullOrEmpty(targetCoord.strMap)) { UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Target map='{targetCoord.strMap}', traceName={m_strTraceName}"); } // Start auto-move work to destination (this is what actually moves the player in this project) // 启动自动移动工作(这才是本项目中真正驱动角色移动的系统) CECHostPlayer host = EC_Game.GetGameRun()?.GetHostPlayer(); if (host == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Host player is null, traceName={m_strTraceName}"); return false; } CECHPWorkMan wm = host.GetWorkMan(); if (wm == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: WorkMan is null, traceName={m_strTraceName}"); return false; } CECHPWorkMove work = wm.CreateWork(CECHPWork.Host_work_ID.WORK_MOVETOPOS) as CECHPWorkMove; if (work == null) { UnityEngine.Debug.LogError($"[CECUIHelper] FollowCoord: Failed to create WORK_MOVETOPOS, traceName={m_strTraceName}"); return false; } // Prefer AutoPF intelligent route (original PW behavior) instead of naive straight-line. // 优先使用 AutoPF 智能寻路(原版 PW 行为),而不是简单直线移动。 work.SetDestination(CECHPWorkMove.DestTypes.DEST_AUTOPF, vPos); // If trace name is provided and there are multiple coordinates, we might want to trace through them // 如果提供了追踪名称且有多个坐标,我们可能想要追踪它们 // Note: SetTaskNPCInfo might not be applicable here since we don't have taskId/id // 注意:SetTaskNPCInfo 可能不适用于此,因为我们没有 taskId/id wm.StartWork_p2(work); UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Started auto-move to ({vPos.x},{vPos.y},{vPos.z}) map={targetCoord.strMap}, traceName={m_strTraceName}, coordCount={m_TargetCoord.Count}"); return true; } public static void AutoMoveStartComplex(A3DVECTOR3 dst, int targetId = 0, int taskId = 0) { UnityEngine.Debug.Log($"[CECUIHelper] AutoMoveStartComplex: dst={dst}, targetId={targetId}, taskId={taskId}"); // TODO: Implement this method properly // if( CECAutoPolicy.Instance.IsAutoPolicyEnabled() ) // return; ECMSG msg = new ECMSG(); msg.iManager = 0; msg.iSubID = 0; msg.dwParam1 = (uint)dst.x; msg.dwParam2 = (uint)dst.y; msg.dwParam3 = (uint)dst.z; msg.dwParam4 = new MsgDataAutoMove(0, targetId, taskId); EC_ManMessage.PostMessage(0, 0, 0, msg); } } }