diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs index 20f29cd05c..a090a4b0e1 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_HPWorkMove.cs @@ -70,6 +70,15 @@ namespace BrewMonster.Scripts protected bool m_bResetAutoPF; + /// + /// 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. + /// + const float AutoPF_WorldDestContinueDistH = 4.0f; + + /// Horizontal radius to treat DEST_2D as arrived (swim/fly overshoot logic often never fires). + 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; } + + /// Matches C++ CECHPWorkMove::GetAutoMove — must not always return true or input stays "auto" forever. 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 + /// True if switched to direct 2D leg — caller must not Finish() this frame. + 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 + + /// + /// 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. + /// + 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 + /// After Search(), face the first path node so swim/air/walk doesn't use previous heading. + 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; }