352 lines
18 KiB
C#
352 lines
18 KiB
C#
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<OBJECT_COORD> TargetTemp = new List<OBJECT_COORD>();
|
||
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<OBJECT_COORD> 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);
|
||
}
|
||
}
|
||
} |