Files
test/Assets/PerfectWorld/Scripts/UI/EC_UIHelper.cs
T
2026-01-29 17:42:34 +07:00

324 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.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
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;
return new A3DVECTOR3(objPos.x, objPos.y, objPos.z);
}
}
// 1) Try live NPC/Monster in scene by template id (best match to "click name -> go to that entity")
// 1) 先尝试在场景中按模板ID查找活体NPC/怪物(最符合“点名字就去找它”)
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;
return new A3DVECTOR3(npcPos.x, npcPos.y, npcPos.z);
}
}
// 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;
return new A3DVECTOR3(info.x, info.z, info.y);
}
// Fallback to coord_data.txt (C++: Configs/Coord_data.txt via CECGame::GetObjectCoord)
// 回退到 coord_data.txtC++Configs/Coord_data.txt,通过 CECGame::GetObjectCoord
if (BrewMonster.Network.EC_Game.TryGetFirstObjectCoord(id.ToString(), out var coordPos, out var mapName))
{
UnityEngine.Debug.Log($"[CECUIHelper] GetTaskObjectCoordinates: Resolved id={id} via coord_data.txt map={mapName} pos=({coordPos.x:F2},{coordPos.y:F2},{coordPos.z:F2})");
in_table = true;
return new A3DVECTOR3(coordPos.x, coordPos.y, coordPos.z);
}
UnityEngine.Debug.LogWarning($"[CECUIHelper] GetTaskObjectCoordinates: Not found for id={id} (isLikelyRuntimeObjectId={isLikelyRuntimeObjectId}).");
return new A3DVECTOR3(0);
// 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);
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);
UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Fallback {tag} region center=({cx:F2},{cy:F2},{cz:F2}) worldId={worldId} curWorldId={curWorldId}");
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);
UnityEngine.Debug.Log($"[CECUIHelper] FollowCoord: Started auto-move to ({vPos.x},{vPos.y},{vPos.z}) for id={id}, taskId={taskId}");
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);
}
}
}