Fix unstable router, water and air route

This commit is contained in:
HungDK
2026-04-18 13:51:02 +07:00
parent f3544be841
commit 5bfe81facd
@@ -70,6 +70,15 @@ namespace BrewMonster.Scripts
protected bool m_bResetAutoPF;
/// <summary>
/// RMap marks deep water as blocked; AutoPF snaps goals to nearest land. If the real mission point
/// (m_vMoveDest) is still farther horizontally (e.g. in water), continue with DEST_2D after AutoPF ends.
/// </summary>
const float AutoPF_WorldDestContinueDistH = 4.0f;
/// <summary>Horizontal radius to treat DEST_2D as arrived (swim/fly overshoot logic often never fires).</summary>
const float Dest2D_ArrivalDistH = 2.5f;
public CECHPWorkMove(CECHPWorkMan pWorkMan) : base(Host_work_ID.WORK_MOVETOPOS, pWorkMan)
{
m_dwMask = Work_mask.MASK_MOVETOPOS;
@@ -111,15 +120,19 @@ namespace BrewMonster.Scripts
}
else if (iDestType == Types.DEST_2D || iDestType == Types.DEST_3D)
{
m_vCurDir = vMoveDest - new A3DVECTOR3(m_pHost.transform.position.x, m_pHost.transform.position.y, m_pHost.transform.position.z);
m_vCurDir = vMoveDest - m_pHost.GetPos();
m_vCurDir.y = 0.0f;
m_vCurDir.Normalize();
if (m_vCurDir.Normalize() > 1e-4f)
OrientHostHorizontal(m_vCurDir);
}
else if (IsAutoPF())
{
m_vCurDir = CECIntelligentRoute.Instance().GetCurDest() - m_pHost.GetPos();
// Search() has not run yet — GetCurDest() is wrong here; use goal direction until first waypoint.
// Search() 尚未执行时 GetCurDest() 不可靠;先用目标方向,寻路成功后再对准第一个节点。
m_vCurDir = vMoveDest - m_pHost.GetPos();
m_vCurDir.y = 0.0f;
m_vCurDir.Normalize();
if (m_vCurDir.Normalize() > 1e-4f)
OrientHostHorizontal(m_vCurDir);
if (m_bUseAutoMoveDialog)
{
// ˴ m_bUseAutoMoveDialog SetUseAutoMoveDialog ˵
@@ -132,6 +145,13 @@ namespace BrewMonster.Scripts
}
}
// Swim/fly velocity carry-over skews the first AirWaterMove/GroundMove frame on new click.
if (iDestType == Types.DEST_2D || iDestType == Types.DEST_3D || iDestType == Types.DEST_AUTOPF)
{
m_pHost.m_vVelocity.x = 0f;
m_pHost.m_vVelocity.z = 0f;
}
// TO DO: fix later
//if (m_pHost.m_pMoveTargetGFX)
//{
@@ -161,18 +181,12 @@ namespace BrewMonster.Scripts
{
if (global::BrewMonster.Scripts.CECIntelligentRoute.Instance().IsIdle())
{
// C++: AutoPF not ready yet, wait next tick.
// C++自动寻路未就绪,等待下一帧
return true;
}
if (m_pHost.IsFlying())
{
// C++: if flying, reset and switch back to DEST_2D.
// C++:飞行状态下重置并切回 DEST_2D。
global::BrewMonster.Scripts.CECIntelligentRoute.Instance().ResetSearch();
m_bSwitchTo2D = true;
// C++: AutoPF not ready yet, wait next tick for SwitchTo2D (EC_HPWorkMove.cpp).
// C++智能寻路模式未成功时,等待下个 Tick 切换到 DEST_2D 模式
return true;
}
// Unity extension: do not clear AutoPF when flying — Tick_FlySwim follows waypoints in air/water.
// 原版 C++ 此处会 Reset+DEST_2D;空中/水中目标需要保留路径并在 Tick_FlySwim 沿路径移动。
}
#endif
@@ -411,12 +425,24 @@ namespace BrewMonster.Scripts
public void SetUseAutoMoveDialog(bool bUseAutoMoveDialog)
{
m_bUseAutoMoveDialog = bUseAutoMoveDialog;
}
public bool GetUseAutoMoveDialog() { return m_bUseAutoMoveDialog; }
/// <summary>Matches C++ CECHPWorkMove::GetAutoMove — must not always return true or input stays "auto" forever.</summary>
public bool GetAutoMove()
{
return true;
if (m_bUseAutoMoveDialog)
return true;
#if ENABLE_CEC_INTELLIGENT_ROUTE
if (IsAutoPF())
{
var r = global::BrewMonster.Scripts.CECIntelligentRoute.Instance();
return r.IsUsageMove() && !r.IsIdle();
}
#endif
return false;
}
void SetAutoLand(bool bAutoLand) { m_bAutoLand = bAutoLand; }
@@ -475,6 +501,73 @@ namespace BrewMonster.Scripts
SetTaskNPCInfo(tid, taskid);
SetUseAutoMoveDialog(true);
}
#if ENABLE_CEC_INTELLIGENT_ROUTE
/// <returns>True if switched to direct 2D leg — caller must not Finish() this frame.</returns>
bool TryContinueAutoPFToWorldDestDirect()
{
A3DVECTOR3 p = m_pHost.GetPos();
float dx = m_vMoveDest.x - p.x;
float dz = m_vMoveDest.z - p.z;
if (dx * dx + dz * dz <= AutoPF_WorldDestContinueDistH * AutoPF_WorldDestContinueDistH)
return false;
int tid = m_iNPCTempleId;
int taskid = m_iTaskId;
global::BrewMonster.Scripts.CECIntelligentRoute.Instance().ResetSearch();
SetDestination(DestTypes.DEST_2D, m_vMoveDest);
SetTaskNPCInfo(tid, taskid);
SetUseAutoMoveDialog(true);
return true;
}
#endif
/// <summary>
/// Swim/fly DEST_2D often never satisfies the overshoot branch; ground can miss when vTPNormal is zero.
/// Finishes the move work so GetAutoMove() goes false and the player can steer again.
/// </summary>
bool TryFinishDest2DIfArrived(A3DVECTOR3 vCurPos, float stopSpeed, int iMoveMode, bool flySwimAddRun)
{
if (m_iDestType != DestTypes.DEST_2D)
return false;
float dx = m_vMoveDest.x - vCurPos.x;
float dz = m_vMoveDest.z - vCurPos.z;
if (dx * dx + dz * dz > Dest2D_ArrivalDistH * Dest2D_ArrivalDistH)
return false;
Finish();
int mode = flySwimAddRun ? (iMoveMode | (int)GPMoveMode.GP_MOVE_RUN) : iMoveMode;
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), stopSpeed, mode);
return true;
}
void OrientHostHorizontal(A3DVECTOR3 dirHorizontal)
{
if (dirHorizontal.IsZero())
return;
Vector3 d = EC_Utility.ToVector3(dirHorizontal);
d.y = 0f;
if (d.sqrMagnitude < 1e-8f)
return;
d.Normalize();
m_pHost.SetDirAndUp(d, Vector3.up);
}
#if ENABLE_CEC_INTELLIGENT_ROUTE
/// <summary>After Search(), face the first path node so swim/air/walk doesn't use previous heading.</summary>
void OrientHostToFirstAutoPFWaypoint()
{
var route = global::BrewMonster.Scripts.CECIntelligentRoute.Instance();
if (!route.IsMoveOn())
return;
A3DVECTOR3 d = route.GetCurDest() - m_pHost.GetPos();
d.y = 0f;
if (d.Normalize() < 1e-4f)
return;
m_vCurDir = d;
OrientHostHorizontal(d);
}
#endif
// On first tick
protected override void OnFirstTick()
{
@@ -578,6 +671,9 @@ namespace BrewMonster.Scripts
m_iDestType = DestTypes.DEST_PUSH;
if (m_iDestType == DestTypes.DEST_2D)
{
if (TryFinishDest2DIfArrived(vCurPos, fSpeed, iMoveMode, false))
return true;
float fDist;
if (m_pHost.m_GndInfo.bOnGround)
{
@@ -786,18 +882,15 @@ namespace BrewMonster.Scripts
global::BrewMonster.Scripts.CECIntelligentRoute.Instance().OnPlayerPosChange(vCurPos);
if (global::BrewMonster.Scripts.CECIntelligentRoute.Instance().IsPathFinished())
{
if (TryContinueAutoPFToWorldDestDirect())
return true;
Finish();
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), fSpeed, iMoveMode);
}
else
{
// NOTE: Use Vector3 overload to avoid signature mismatch across ports.
// 注意:使用 Vector3 重载以避免移植过程中签名不匹配。
m_pHost.m_MoveCtrl.SendMoveCmd(
EC_Utility.ToVector3(vCurPos),
EC_Utility.ToVector3(cdr.vAbsVelocity),
iMoveMode,
false);
// C++: SendMoveCmd(vCurPos, 1, vCurDest, cdr.vAbsVelocity, iMoveMode)
m_pHost.m_MoveCtrl.SendMoveCmd(vCurPos, 1, vCurDest, cdr.vAbsVelocity, iMoveMode);
}
}
}
@@ -938,6 +1031,9 @@ namespace BrewMonster.Scripts
}
else if (m_iDestType == DestTypes.DEST_2D)
{
if (TryFinishDest2DIfArrived(vCurPos, fMaxSpeed, iMoveMode, true))
return true;
Vector3 vPushDir = Vector3.zero;
m_pHost.GetPushDir(ref vPushDir, (uint)CECHostPlayer.MOVE_DIR.MD_ALL, 0f);
vPushDir.x = vPushDir.z = 0.0f;
@@ -1227,6 +1323,110 @@ namespace BrewMonster.Scripts
}
else if (IsAutoPF())
{
#if ENABLE_CEC_INTELLIGENT_ROUTE
var route = global::BrewMonster.Scripts.CECIntelligentRoute.Instance();
// Unity: ground AutoPF is in Tick_Walk; swim/fly use Tick_FlySwim. C++ resets here and forces DEST_2D,
// which breaks routes when the path or target is in water/air — follow path nodes like DEST_2D swim/fly.
bool envAirOrWater = m_pHost.m_iMoveEnv == (int)MoveEnvironment.MOVEENV_WATER
|| m_pHost.m_iMoveEnv == (int)MoveEnvironment.MOVEENV_AIR;
if (envAirOrWater && route.IsUsageMove())
{
if (route.IsPathFinished())
{
if (TryContinueAutoPFToWorldDestDirect())
return true;
Finish();
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), fMaxSpeed,
iMoveMode | (int)GPMoveMode.GP_MOVE_RUN);
return true;
}
if (!route.IsMoveOn())
{
route.ResetSearch();
m_bSwitchTo2D = true;
return true;
}
Vector3 vPushDir = Vector3.zero;
m_pHost.GetPushDir(ref vPushDir, (uint)CECHostPlayer.MOVE_DIR.MD_ALL, 0f);
vPushDir.x = vPushDir.z = 0.0f;
float fSpeed1H = m_pHost.m_vVelocity.MagnitudeH();
float fSpeed1V = m_pHost.m_vVelocity.y;
A3DVECTOR3 vCurDest = route.GetCurDest();
A3DVECTOR3 vMoveDirH = vCurDest - vCurPos;
vMoveDirH.y = 0.0f;
float fDistH = vMoveDirH.Normalize();
if (fDistH < 1e-4f)
{
route.OnPlayerPosChange(vCurPos);
if (route.IsPathFinished())
{
if (TryContinueAutoPFToWorldDestDirect())
return true;
Finish();
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), fMaxSpeed,
iMoveMode | (int)GPMoveMode.GP_MOVE_RUN);
}
return true;
}
float pa = 0.0f;
float s = -0.5f * fSpeed1H * fSpeed1H / na;
if (fDistH > s - 0.01f)
pa = CECHostMove.EC_PUSH_ACCE;
float fSpeed2H = fSpeed1H + (pa + na) * fDeltaTime;
if (Math.Abs(pa - 0f) < float.Epsilon && fSpeed2H < 0.0f)
fSpeed2H = 0.0f;
else if (fSpeed2H > fMaxSpeed)
fSpeed2H = fMaxSpeed;
Glide(fDistH / Mathf.Max(fMaxSpeed, 0.01f), vMoveDirH, fDeltaTime, bInAir);
vMoveDirH = m_pHost.GetModelMoveDir();
vMoveDirH.y = 0;
vMoveDirH.Normalize();
float fSpeed2V = CalcFlySwimVertSpeed(fSpeed1V, vPushDir.y, CECHostMove.EC_PUSH_ACCE, fDeltaTime);
A3DVECTOR3 vVel2 = vMoveDirH * fSpeed2H + GPDataTypeHelper.g_vAxisY * fSpeed2V;
vCurPos = m_pHost.m_MoveCtrl.AirWaterMove(vVel2, fDeltaTime, bInAir);
if (m_pHost.m_MoveCtrl.MoveBlocked() >= 3)
{
vVel2.Clear();
Finish();
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), fMaxSpeed,
iMoveMode | (int)GPMoveMode.GP_MOVE_RUN);
}
else
{
m_pHost.SetPos(EC_Utility.ToVector3(vCurPos));
m_pHost.m_vVelocity = vVel2;
route.OnPlayerPosChange(vCurPos);
if (route.IsPathFinished())
{
if (TryContinueAutoPFToWorldDestDirect())
return true;
Finish();
m_pHost.m_MoveCtrl.SendStopMoveCmd(EC_Utility.ToVector3(vCurPos), vVel2.Magnitude(),
iMoveMode | (int)GPMoveMode.GP_MOVE_RUN);
}
else
{
m_pHost.m_MoveCtrl.SendMoveCmd(vCurPos, 0, m_vMoveDest, vVel2,
iMoveMode | (int)GPMoveMode.GP_MOVE_RUN);
}
}
return true;
}
#endif
CECIntelligentRoute.Instance().ResetSearch();
m_bSwitchTo2D = true;
}
@@ -1308,22 +1508,8 @@ namespace BrewMonster.Scripts
while (true)
{
if (m_pHost.IsFlying())
{
if (CECUIManager.Instance != null)
{
// string message = $"Please deactive the fly mode to start the path finding.";
// CECUIManager.Instance.ShowMessageBox(
// "Fly Mode", // 飞行模式
// message, // 消息
// BrewMonster.MessageBoxType.YesButton
// );
// Finish();
//return;
}
break;
}
// C++ skips Search while flying (forces DEST_2D). Unity: allow Search so aerial targets get a 2D path.
// 原版飞行时不做 Search;此处允许寻路,使空中起点/空中目标也能得到 XZ 路径。
// Brush test is not fully ported yet; pass null for now (static movemap only).
// BrushTest 暂未完整移植;当前先传 null(仅静态 movemap)。
@@ -1334,34 +1520,13 @@ namespace BrewMonster.Scripts
if (ret == CECIntelligentRoute.SearchResult.enumSearchNoPath)
{
// Calculate map coordinates from world coordinates
// 从世界坐标计算地图坐标
// Map coord formula: (world / 10) + offset (X: +400, Z: +550)
// 地图坐标公式:(世界坐标 / 10) + 偏移量 (X: +400, Z: +550)
// C++ UpdateResetUseAutoPF: Search != success → break; bSwitchTo2D stays true (fallback straight line).
// Do not Finish() — same as EC_HPWorkMove.cpp (no path still switches to DEST_2D).
int mapX = Mathf.RoundToInt(m_vMoveDest.x / 10.0f) + 400;
int mapY = Mathf.RoundToInt(m_vMoveDest.y / 10.0f);
int mapZ = Mathf.RoundToInt(m_vMoveDest.z / 10.0f) + 550;
// Show popup notification to player that path cannot be found
// 显示弹窗通知玩家无法找到路径
if (CECUIManager.Instance != null)
{
// string message = $"Cannot find path to target position.\nPlease move manually to target location.\n\nMap Coordinates: ({mapX}, {mapZ}, ↑{mapY})";
// string messageCN = $"无法找到到目标位置的路径。\n请手动移动到目标位置。\n\n地图坐标: ({mapX}, {mapZ}, ↑{mapY})";
//
// // CECUIManager.Instance.ShowMessageBox(
// // "Path Not Found", // 路径未找到
// // message, // English message with map coordinates
// // BrewMonster.MessageBoxType.YesButton
// // );
// CECUIManager.Instance.ShowMessageBoxYes("Path Not Found", message, null, null);
}
else
{
Debug.LogWarning($"[CECIntelligentRoute] Cannot find path to target position. Map Coordinates: ({mapX}, {mapZ}, ↑{mapY}). Please move manually.");
}
Finish();
Debug.LogWarning(
$"[CECIntelligentRoute] No path; switching to DEST_2D. Map coords: ({mapX}, {mapZ}, ↑{mapY}).");
break;
}
else if (ret != CECIntelligentRoute.SearchResult.enumSearchSuccess)
@@ -1369,6 +1534,7 @@ namespace BrewMonster.Scripts
break;
}
bSwitchTo2D = false;
OrientHostToFirstAutoPFWaypoint();
break;
}