Unit Motion - Improve behaviour around obstructions and unreachable goals
Use the "pretend correct path" behaviour for short goals too. This is a
fix for #5545, since the position check now works correctly without
needing to add a tricky check for path-vs-path distance.
Simplify HandleObstructedMove to use ComputePathToGoal more. This means
we occasionally compute a long path when stuck, which fixes two cases of
stuck units reported in #5547.
Move some common calls into functions for convenience.
Make sure the short-path-waypoint-range-relaxing introduced in
32e8ed51aa
doesn't happen for the last waypoint, which caused units to
occasionally never get in range of the last waypoint.
Clear short path waypoints when computing a long path.
This won't fix all instances of unit dancing, but it should improve most
of them.
Fixes #5545, Fixes #5547
Differential Revision: https://code.wildfiregames.com/D2135
This was SVN commit r22609.
This commit is contained in:
parent
9ece3e7088
commit
2ff8614ce2
@ -137,9 +137,11 @@ public:
|
|||||||
u8 m_FailedPathComputations = 0;
|
u8 m_FailedPathComputations = 0;
|
||||||
|
|
||||||
// If true, PathingUpdateNeeded returns false always.
|
// If true, PathingUpdateNeeded returns false always.
|
||||||
// This is an optimisation against unreachable goals, where otherwise we would always
|
// This exists because the goal may be unreachable to the short/long pathfinder.
|
||||||
// be recomputing a path.
|
// In such cases, we would compute inacceptable paths and PathingUpdateNeeded would trigger every turn.
|
||||||
bool m_PretendLongPathIsCorrect = false;
|
// To avoid that, when we know the new path is imperfect, treat it as OK and follow it until the end.
|
||||||
|
// When reaching the end, we'll run through HandleObstructedMove and this will be reset.
|
||||||
|
bool m_FollowKnownImperfectPath = false;
|
||||||
|
|
||||||
struct Ticket {
|
struct Ticket {
|
||||||
u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none
|
u32 m_Ticket = 0; // asynchronous request ID we're waiting for, or 0 if none
|
||||||
@ -250,7 +252,7 @@ public:
|
|||||||
SerializeU8_Enum<Ticket::Type, Ticket::Type::LONG_PATH>()(serialize, "ticket type", m_ExpectedPathTicket.m_Type);
|
SerializeU8_Enum<Ticket::Type, Ticket::Type::LONG_PATH>()(serialize, "ticket type", m_ExpectedPathTicket.m_Type);
|
||||||
|
|
||||||
serialize.NumberU8("failed path computations", m_FailedPathComputations, 0, 255);
|
serialize.NumberU8("failed path computations", m_FailedPathComputations, 0, 255);
|
||||||
serialize.Bool("pretendLongPathIsCorrect", m_PretendLongPathIsCorrect);
|
serialize.Bool("followknownimperfectpath", m_FollowKnownImperfectPath);
|
||||||
|
|
||||||
SerializeU8_Enum<MoveRequest::Type, MoveRequest::Type::OFFSET>()(serialize, "target type", m_MoveRequest.m_Type);
|
SerializeU8_Enum<MoveRequest::Type, MoveRequest::Type::OFFSET>()(serialize, "target type", m_MoveRequest.m_Type);
|
||||||
serialize.NumberU32_Unbounded("target entity", m_MoveRequest.m_Entity);
|
serialize.NumberU32_Unbounded("target entity", m_MoveRequest.m_Entity);
|
||||||
@ -521,6 +523,20 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const;
|
bool RejectFartherPaths(const PathGoal& goal, const WaypointPath& path, const CFixedVector2D& pos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there are 2 waypoints of more remaining in longPath, return SHORT_PATH_LONG_WAYPOINT_RANGE.
|
||||||
|
* Otherwise the pathing should be exact.
|
||||||
|
*/
|
||||||
|
entity_pos_t ShortPathWaypointRange(const WaypointPath& longPath) const
|
||||||
|
{
|
||||||
|
return longPath.m_Waypoints.size() >= 2 ? SHORT_PATH_LONG_WAYPOINT_RANGE : entity_pos_t::Zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InShortPathRange(const PathGoal& goal, const CFixedVector2D& pos) const
|
||||||
|
{
|
||||||
|
return goal.DistanceToPoint(pos) < LONG_PATH_MIN_DIST;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the result of an asynchronous path query.
|
* Handle the result of an asynchronous path query.
|
||||||
*/
|
*/
|
||||||
@ -689,7 +705,7 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
|||||||
|
|
||||||
m_LongPath = path;
|
m_LongPath = path;
|
||||||
|
|
||||||
m_PretendLongPathIsCorrect = false;
|
m_FollowKnownImperfectPath = false;
|
||||||
|
|
||||||
// If there's no waypoints then we couldn't get near the target.
|
// If there's no waypoints then we couldn't get near the target.
|
||||||
// Sort of hack: Just try going directly to the goal point instead
|
// Sort of hack: Just try going directly to the goal point instead
|
||||||
@ -712,7 +728,7 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
|||||||
// TODO: For now, we won't warn components straight away as that could lead to units idling earlier than expected,
|
// TODO: For now, we won't warn components straight away as that could lead to units idling earlier than expected,
|
||||||
// but it should be done someday when the message can differentiate between different failure causes.
|
// but it should be done someday when the message can differentiate between different failure causes.
|
||||||
else if (PathingUpdateNeeded(pos))
|
else if (PathingUpdateNeeded(pos))
|
||||||
m_PretendLongPathIsCorrect = true;
|
m_FollowKnownImperfectPath = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,8 +741,13 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
|||||||
|
|
||||||
m_ShortPath = path;
|
m_ShortPath = path;
|
||||||
|
|
||||||
|
m_FollowKnownImperfectPath = false;
|
||||||
if (!m_ShortPath.m_Waypoints.empty())
|
if (!m_ShortPath.m_Waypoints.empty())
|
||||||
|
{
|
||||||
|
if (PathingUpdateNeeded(pos))
|
||||||
|
m_FollowKnownImperfectPath = true;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_FailedPathComputations >= 1)
|
if (m_FailedPathComputations >= 1)
|
||||||
{
|
{
|
||||||
@ -750,7 +771,7 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
|||||||
{
|
{
|
||||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
||||||
// we'll easily be able to revert it using a long path.
|
// we'll easily be able to revert it using a long path.
|
||||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE };
|
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, ShortPathWaypointRange(m_LongPath) };
|
||||||
RequestShortPath(pos, goal, true);
|
RequestShortPath(pos, goal, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -994,27 +1015,24 @@ bool CCmpUnitMotion::HandleObstructedMove()
|
|||||||
if (!ComputeGoal(goal, m_MoveRequest))
|
if (!ComputeGoal(goal, m_MoveRequest))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If close enough, just compute a short path to the goal
|
if (!InShortPathRange(goal, pos))
|
||||||
if (goal.DistanceToPoint(pos) < LONG_PATH_MIN_DIST)
|
|
||||||
{
|
{
|
||||||
m_LongPath.m_Waypoints.clear();
|
// If we still have long waypoints, try and compute a short path.
|
||||||
RequestShortPath(pos, goal, true);
|
// Assume the next waypoint is impassable
|
||||||
return true;
|
if (m_LongPath.m_Waypoints.size() > 1)
|
||||||
|
m_LongPath.m_Waypoints.pop_back();
|
||||||
|
if (!m_LongPath.m_Waypoints.empty())
|
||||||
|
{
|
||||||
|
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
||||||
|
// we'll easily be able to revert it using a long path.
|
||||||
|
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, ShortPathWaypointRange(m_LongPath) };
|
||||||
|
RequestShortPath(pos, goal, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we still have long waypoints, try and compute a short path.
|
// Else, just entirely recompute. This will ensure we occasionally run a long path so avoid getting stuck
|
||||||
// Assume the next waypoint is impassable
|
// in the short pathfinder, which can happen when an entity is right ober an obstruction's edge.
|
||||||
if (m_LongPath.m_Waypoints.size() > 1)
|
|
||||||
m_LongPath.m_Waypoints.pop_back();
|
|
||||||
if (!m_LongPath.m_Waypoints.empty())
|
|
||||||
{
|
|
||||||
// Get close enough - this will likely help the short path efficiency, and if we end up taking a wrong way
|
|
||||||
// we'll easily be able to revert it using a long path.
|
|
||||||
PathGoal goal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, SHORT_PATH_LONG_WAYPOINT_RANGE };
|
|
||||||
RequestShortPath(pos, goal, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Else, just entirely recompute
|
|
||||||
ComputePathToGoal(pos, goal);
|
ComputePathToGoal(pos, goal);
|
||||||
|
|
||||||
// potential TODO: We could switch the short-range pathfinder for something else entirely.
|
// potential TODO: We could switch the short-range pathfinder for something else entirely.
|
||||||
@ -1125,7 +1143,7 @@ bool CCmpUnitMotion::PathingUpdateNeeded(const CFixedVector2D& from) const
|
|||||||
if (!ComputeTargetPosition(targetPos))
|
if (!ComputeTargetPosition(targetPos))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_PretendLongPathIsCorrect)
|
if (m_FollowKnownImperfectPath)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (PossiblyAtDestination())
|
if (PossiblyAtDestination())
|
||||||
@ -1360,13 +1378,16 @@ void CCmpUnitMotion::ComputePathToGoal(const CFixedVector2D& from, const PathGoa
|
|||||||
// need a long path, so we shouldn't simply check linear distance
|
// need a long path, so we shouldn't simply check linear distance
|
||||||
// the check is arbitrary but should be a reasonably small distance.
|
// the check is arbitrary but should be a reasonably small distance.
|
||||||
// To avoid getting stuck because the short-range pathfinder is bounded, occasionally compute a long path instead.
|
// To avoid getting stuck because the short-range pathfinder is bounded, occasionally compute a long path instead.
|
||||||
if (m_FailedPathComputations != MAX_FAILED_PATH_COMPUTATIONS_BEFORE_LONG_PATH && goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST)
|
if (m_FailedPathComputations != MAX_FAILED_PATH_COMPUTATIONS_BEFORE_LONG_PATH && InShortPathRange(goal, from))
|
||||||
{
|
{
|
||||||
m_LongPath.m_Waypoints.clear();
|
m_LongPath.m_Waypoints.clear();
|
||||||
RequestShortPath(from, goal, true);
|
RequestShortPath(from, goal, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
m_ShortPath.m_Waypoints.clear();
|
||||||
RequestLongPath(from, goal);
|
RequestLongPath(from, goal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal)
|
void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal)
|
||||||
@ -1414,7 +1435,7 @@ bool CCmpUnitMotion::MoveTo(MoveRequest request)
|
|||||||
|
|
||||||
m_MoveRequest = request;
|
m_MoveRequest = request;
|
||||||
m_FailedPathComputations = 0;
|
m_FailedPathComputations = 0;
|
||||||
m_PretendLongPathIsCorrect = false;
|
m_FollowKnownImperfectPath = false;
|
||||||
|
|
||||||
ComputePathToGoal(cmpPosition->GetPosition2D(), goal);
|
ComputePathToGoal(cmpPosition->GetPosition2D(), goal);
|
||||||
return true;
|
return true;
|
||||||
|
Loading…
Reference in New Issue
Block a user