Merge pull request 'fixbug/npc-dialog' (#360) from fixbug/npc-dialog into develop
Reviewed-on: https://git.pthub.vn/Unity/perfect-world-unity/pulls/360
This commit is contained in:
@@ -178,15 +178,20 @@ namespace BrewMonster.Scripts
|
||||
|
||||
#if ENABLE_CEC_INTELLIGENT_ROUTE
|
||||
if (IsAutoPF())
|
||||
{
|
||||
{
|
||||
if (global::BrewMonster.Scripts.CECIntelligentRoute.Instance().IsIdle())
|
||||
{
|
||||
{
|
||||
// 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 沿路径移动。
|
||||
return true;
|
||||
}
|
||||
// EC_HPWorkMove.cpp: mid-air AutoPF → reset route and switch to DEST_2D next tick.
|
||||
if (m_pHost.IsFlying())
|
||||
{
|
||||
global::BrewMonster.Scripts.CECIntelligentRoute.Instance().ResetSearch();
|
||||
m_bSwitchTo2D = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1323,111 +1328,10 @@ namespace BrewMonster.Scripts
|
||||
}
|
||||
else if (IsAutoPF())
|
||||
{
|
||||
// EC_HPWorkMove.cpp Tick_FlySwim: AutoPF in air/water does not walk waypoints — drop path and use DEST_2D.
|
||||
#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;
|
||||
}
|
||||
global::BrewMonster.Scripts.CECIntelligentRoute.Instance().ResetSearch();
|
||||
#endif
|
||||
CECIntelligentRoute.Instance().ResetSearch();
|
||||
m_bSwitchTo2D = true;
|
||||
}
|
||||
return true;
|
||||
@@ -1508,8 +1412,9 @@ namespace BrewMonster.Scripts
|
||||
|
||||
while (true)
|
||||
{
|
||||
// C++ skips Search while flying (forces DEST_2D). Unity: allow Search so aerial targets get a 2D path.
|
||||
// 原版飞行时不做 Search;此处允许寻路,使空中起点/空中目标也能得到 XZ 路径。
|
||||
// EC_HPWorkMove.cpp UpdateResetUseAutoPF: skip Search while flying (bSwitchTo2D stays true).
|
||||
if (m_pHost.IsFlying())
|
||||
break;
|
||||
|
||||
// Brush test is not fully ported yet; pass null for now (static movemap only).
|
||||
// BrushTest 暂未完整移植;当前先传 null(仅静态 movemap)。
|
||||
|
||||
@@ -118,6 +118,12 @@ namespace AutoMove
|
||||
|
||||
public int GetPathCount() => m_path3D.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Matches C++ CMoveAgent::GetOptimizeCatchCount — lookahead window for path following.
|
||||
/// 对应 C++:无 PathOptimizer 时为 0,FindNearest/Farthest 仅在单步索引上工作。
|
||||
/// </summary>
|
||||
public int GetOptimizeCatchCount() => 0;
|
||||
|
||||
public Vector3 Get3DPathNode(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_path3D.Count) return Vector3.zero;
|
||||
@@ -351,6 +357,7 @@ namespace AutoMove
|
||||
//m_pBrushTest = null;
|
||||
|
||||
//m_iStat = PF_STATE_UNKNOWN;
|
||||
m_path3D.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace BrewMonster.Scripts
|
||||
public sealed class CECIntelligentRoute
|
||||
{
|
||||
private const bool DEBUG_AUTOPF = true;
|
||||
/// <summary>cos(5°) for FindFarthestNode — same as C++ EC_IntelligentRoute.cpp.</summary>
|
||||
private static readonly float s_cos5Deg = Mathf.Cos(5f * Mathf.PI / 180f);
|
||||
public enum SearchResult
|
||||
{
|
||||
enumSearchSuccess, // 寻路成功 // Search success
|
||||
@@ -395,26 +397,32 @@ namespace BrewMonster.Scripts
|
||||
return SearchResult.enumSearchNoPath;
|
||||
}
|
||||
|
||||
// EC_IntelligentRoute.cpp: single-node 2D path → start/goal coincide.
|
||||
if (agent.GetPathCount() == 1)
|
||||
{
|
||||
agent.ResetSearch();
|
||||
if (DEBUG_AUTOPF)
|
||||
BMLogger.Log("[CECIntelligentRoute] Search: path size 1 (start/end coincide).");
|
||||
return SearchResult.enumSearchStartEndCoincide;
|
||||
}
|
||||
|
||||
m_state = RouteState.enumRouteMoving;
|
||||
m_iCurDest = 0;
|
||||
m_lastPos = m_start;
|
||||
m_lastMove = 0.0f;
|
||||
m_iCurDest = FindNextNode(start, 0);
|
||||
m_dist2CurDest = (GetCurDest() - m_start).MagnitudeH();
|
||||
if (DEBUG_AUTOPF)
|
||||
{
|
||||
BMLogger.Log($"[CECIntelligentRoute] Search: success pathCount={agent.GetPathCount()} start=({start.x:F2},{start.z:F2}) end=({end.x:F2},{end.z:F2})");
|
||||
BMLogger.Log($"[CECIntelligentRoute] Search: success pathCount={agent.GetPathCount()} start=({start.x:F2},{start.z:F2}) end=({end.x:F2},{end.z:F2}) curDestIdx={m_iCurDest}");
|
||||
}
|
||||
return SearchResult.enumSearchSuccess;
|
||||
}
|
||||
|
||||
public A3DVECTOR3 GetCurDest()
|
||||
{
|
||||
if (m_state == RouteState.enumRouteIdle || m_iCurMoveAgent < 0) return m_end;
|
||||
var agent = m_moveAgents[m_iCurMoveAgent].agent;
|
||||
if (agent == null) return m_end;
|
||||
|
||||
int cnt = agent.GetPathCount();
|
||||
if (cnt <= 0) return m_end;
|
||||
int idx = Mathf.Clamp(m_iCurDest, 0, cnt - 1);
|
||||
Vector3 v = agent.Get3DPathNode(idx);
|
||||
return new A3DVECTOR3(v.x, v.y, v.z);
|
||||
if (IsIdle() || m_iCurMoveAgent < 0)
|
||||
return m_end;
|
||||
return GetNodePos(m_iCurDest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -430,40 +438,175 @@ namespace BrewMonster.Scripts
|
||||
return agent.GetFullPath();
|
||||
}
|
||||
|
||||
/// <summary>Ported from C++ CECIntelligentRoute::OnPlayerPosChange (EC_IntelligentRoute.cpp).</summary>
|
||||
public void OnPlayerPosChange(A3DVECTOR3 pos)
|
||||
{
|
||||
if (m_state != RouteState.enumRouteMoving) return;
|
||||
if (m_iCurMoveAgent < 0) return;
|
||||
var agent = m_moveAgents[m_iCurMoveAgent].agent;
|
||||
if (agent == null) return;
|
||||
if (!IsMoveOn())
|
||||
return;
|
||||
|
||||
int cnt = agent.GetPathCount();
|
||||
if (cnt <= 0)
|
||||
if (CanFinishPath(pos))
|
||||
{
|
||||
m_state = RouteState.enumRoutePathFinished;
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance nodes when close to current dest.
|
||||
// 接近当前节点时推进到下一节点。
|
||||
float reach = 1.0f;
|
||||
while (m_iCurDest < cnt)
|
||||
{
|
||||
Vector3 d = agent.Get3DPathNode(m_iCurDest);
|
||||
Vector3 delta = d - new Vector3(pos.x, pos.y, pos.z);
|
||||
delta.y = 0.0f;
|
||||
if (delta.sqrMagnitude <= reach * reach)
|
||||
{
|
||||
m_iCurDest++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!CanMoveToNext(pos))
|
||||
return;
|
||||
|
||||
if (m_iCurDest >= cnt)
|
||||
RangedMoveAgent? pCur = GetCurAgent();
|
||||
if (pCur == null || pCur.Value.agent == null)
|
||||
return;
|
||||
CMoveAgent agent = pCur.Value.agent;
|
||||
int pathCount = agent.GetPathCount();
|
||||
if (m_iCurDest == pathCount - 1)
|
||||
{
|
||||
m_state = RouteState.enumRoutePathFinished;
|
||||
return;
|
||||
}
|
||||
|
||||
m_iCurDest = FindNextNode(pos, m_iCurDest + 1);
|
||||
m_dist2CurDest = (GetCurDest() - pos).MagnitudeH();
|
||||
// C++ calls agent->Optimize(m_iCurDest) here; Unity CMoveAgent has no path optimizer yet.
|
||||
}
|
||||
|
||||
A3DVECTOR3 GetNodePos(int iNode)
|
||||
{
|
||||
RangedMoveAgent? pCurAgent = GetCurAgent();
|
||||
if (pCurAgent == null || pCurAgent.Value.agent == null)
|
||||
return new A3DVECTOR3(0f, 0f, 0f);
|
||||
CMoveAgent ag = pCurAgent.Value.agent;
|
||||
int nPathCount = ag.GetPathCount();
|
||||
if (iNode >= 0 && iNode < nPathCount)
|
||||
return GetNodePosNoCheck(iNode);
|
||||
return new A3DVECTOR3(0f, 0f, 0f);
|
||||
}
|
||||
|
||||
A3DVECTOR3 GetNodePosNoCheck(int iNode)
|
||||
{
|
||||
if (m_iCurMoveAgent < 0 || m_iCurMoveAgent >= m_moveAgents.Count)
|
||||
return new A3DVECTOR3(0f, 0f, 0f);
|
||||
CMoveAgent agent = m_moveAgents[m_iCurMoveAgent].agent;
|
||||
if (agent == null)
|
||||
return new A3DVECTOR3(0f, 0f, 0f);
|
||||
Vector3 v = agent.Get3DPathNode(iNode);
|
||||
// C++ adds GetTerrainHeight to Y; Unity path nodes use movemap/world height from loader.
|
||||
return new A3DVECTOR3(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
A3DVECTOR3 GetNodePosXZ(int iNode)
|
||||
{
|
||||
if (IsIdle())
|
||||
return new A3DVECTOR3(0f, 0f, 0f);
|
||||
A3DVECTOR3 p = GetNodePosNoCheck(iNode);
|
||||
p.y = 0f;
|
||||
return p;
|
||||
}
|
||||
|
||||
int FindNearestNode(A3DVECTOR3 curPos, int iNodeFrom)
|
||||
{
|
||||
if (!IsMoveOn())
|
||||
return -1;
|
||||
RangedMoveAgent? pCur = GetCurAgent();
|
||||
if (pCur == null || pCur.Value.agent == null)
|
||||
return -1;
|
||||
CMoveAgent agent = pCur.Value.agent;
|
||||
int pathCount = agent.GetPathCount();
|
||||
if (iNodeFrom < 0 || iNodeFrom >= pathCount)
|
||||
return -1;
|
||||
if (iNodeFrom == pathCount - 1)
|
||||
return iNodeFrom;
|
||||
|
||||
int maxCheckIndex = iNodeFrom + agent.GetOptimizeCatchCount();
|
||||
maxCheckIndex = Mathf.Min(maxCheckIndex, pathCount - 1);
|
||||
int bestIndex = -1;
|
||||
double bestDist2 = double.MaxValue;
|
||||
for (int i = iNodeFrom; i <= maxCheckIndex; ++i)
|
||||
{
|
||||
A3DVECTOR3 testPos = GetNodePosXZ(i);
|
||||
testPos -= curPos;
|
||||
testPos.y = 0f;
|
||||
double dist2 = testPos.x * testPos.x + testPos.z * testPos.z;
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestIndex = i;
|
||||
bestDist2 = dist2;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex >= 0 ? bestIndex : -1;
|
||||
}
|
||||
|
||||
int FindFarthestNode(A3DVECTOR3 curPos, int iNodeFrom)
|
||||
{
|
||||
if (iNodeFrom < 0)
|
||||
return iNodeFrom;
|
||||
RangedMoveAgent? pCur = GetCurAgent();
|
||||
if (pCur == null || pCur.Value.agent == null)
|
||||
return iNodeFrom;
|
||||
CMoveAgent agent = pCur.Value.agent;
|
||||
int pathCount = agent.GetPathCount();
|
||||
if (iNodeFrom == pathCount - 1)
|
||||
return iNodeFrom;
|
||||
|
||||
A3DVECTOR3 moveDir = GetNodePosXZ(iNodeFrom);
|
||||
moveDir -= curPos;
|
||||
moveDir.y = 0f;
|
||||
if (moveDir.Normalize() < 1e-4f)
|
||||
return iNodeFrom;
|
||||
|
||||
int maxCheckIndex = iNodeFrom + agent.GetOptimizeCatchCount();
|
||||
maxCheckIndex = Mathf.Min(maxCheckIndex, pathCount - 1);
|
||||
for (int i = iNodeFrom + 1; i <= maxCheckIndex; ++i)
|
||||
{
|
||||
A3DVECTOR3 testDir = GetNodePosXZ(i);
|
||||
testDir -= curPos;
|
||||
testDir.y = 0f;
|
||||
if (testDir.Normalize() < 1e-4f)
|
||||
break;
|
||||
float dtp = A3DVECTOR3.DotProduct(moveDir, testDir);
|
||||
if (dtp < s_cos5Deg)
|
||||
break;
|
||||
iNodeFrom = i;
|
||||
}
|
||||
|
||||
return iNodeFrom;
|
||||
}
|
||||
|
||||
int FindNextNode(A3DVECTOR3 curPos, int iNodeFrom)
|
||||
{
|
||||
int iCandidate = FindNearestNode(curPos, iNodeFrom);
|
||||
if (iCandidate < 0)
|
||||
return Mathf.Max(0, iNodeFrom);
|
||||
return FindFarthestNode(curPos, iCandidate);
|
||||
}
|
||||
|
||||
bool CanFinishPath(A3DVECTOR3 pos)
|
||||
{
|
||||
if (!IsMoveOn())
|
||||
return false;
|
||||
A3DVECTOR3 delta = m_end - pos;
|
||||
return delta.Magnitude() <= 0.5f;
|
||||
}
|
||||
|
||||
bool CanMoveToNext(A3DVECTOR3 pos)
|
||||
{
|
||||
float fMove = (pos - m_lastPos).MagnitudeH();
|
||||
m_lastPos = pos;
|
||||
|
||||
float lastMove = m_lastMove;
|
||||
m_lastMove = fMove;
|
||||
|
||||
float dist2CurDest = m_dist2CurDest;
|
||||
A3DVECTOR3 vCurDest = GetCurDest();
|
||||
m_dist2CurDest = (vCurDest - pos).MagnitudeH();
|
||||
|
||||
if (fMove >= dist2CurDest)
|
||||
return true;
|
||||
if (fMove + 0.1f >= dist2CurDest)
|
||||
return true;
|
||||
if (lastMove * 0.5f >= m_dist2CurDest)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user