Index: source/simulation2/components/CCmpUnitMotion.h =================================================================== --- source/simulation2/components/CCmpUnitMotion.h +++ source/simulation2/components/CCmpUnitMotion.h @@ -175,6 +175,10 @@ // Cached from ICmpObstruction. bool m_BlockMovement = false; + // Flag to try to guess if we're chasing a moving unit. + // If yes, we must recompute every turn in TryGoingStraightToTarget + bool m_IsLikelyChasing = false; + // Internal counter used when recovering from obstructed movement. // Most notably, increases the search range of the vertex pathfinder. // See HandleObstructedMove() for more details. @@ -325,6 +329,7 @@ serialize.NumberU32_Unbounded("ticket", m_ExpectedPathTicket.m_Ticket); Serializer(serialize, "ticket type", m_ExpectedPathTicket.m_Type, Ticket::Type::LONG_PATH); + serialize.Bool("likely chasing", m_IsLikelyChasing); serialize.NumberU8_Unbounded("failed movements", m_FailedMovements); serialize.NumberU8_Unbounded("followknownimperfectpath", m_FollowKnownImperfectPathCountdown); @@ -1062,9 +1067,10 @@ { PROFILE("Move"); - // If we're chasing a potentially-moving unit and are currently close - // enough to its current position, and we can head in a straight line - // to it, then throw away our current path and go straight to it. + // Try replacing our paths with a direct path if it seems possible. + // In the case of chasing a moving unit this is generally a required step, + // as the target might move further away from our current waypoint + // by the end of the turn, leading to infinite never-quite-in-range problems. state.wentStraight = TryGoingStraightToTarget(state.initialPos, true); state.wasObstructed = PerformMove(dt, state.cmpPosition->GetTurnRate(), m_ShortPath, m_LongPath, state.pos, state.speed, state.angle, state.pushingPressure); @@ -1094,14 +1100,15 @@ else if (!state.wasObstructed && state.pos != state.initialPos) m_FailedMovements = 0; - // If we moved straight, and didn't quite finish the path, reset - we'll update it next turn if still OK. - if (state.wentStraight && !state.wasObstructed) - m_ShortPath.m_Waypoints.clear(); - // We may need to recompute our path sometimes (e.g. if our target moves). // Since we request paths asynchronously anyways, this does not need to be done before moving. - if (!state.wentStraight && PathingUpdateNeeded(state.pos)) + if (PathingUpdateNeeded(state.pos)) { + // Flag that we might be following a moving units + m_IsLikelyChasing = true; + // If we took the straight shortcut, assume we'll do that next turn as well. + if (state.wentStraight) + return; PathGoal goal; if (ComputeGoal(goal, m_MoveRequest)) ComputePathToGoal(state.pos, goal); @@ -1461,16 +1468,27 @@ bool CCmpUnitMotion::TryGoingStraightToTarget(const CFixedVector2D& from, bool updatePaths) { - // Assume if we have short paths we want to follow them. - // Exception: offset movement (formations) generally have very short deltas + // If we have a short path with multiple waypoints left, assume we want to follow that. + if (m_ShortPath.m_Waypoints.size() > 1) + return false; + // Offset movement (formations) generally have very short deltas // and to look good we need them to walk-straight most of the time. - if (!IsFormationMember() && !m_ShortPath.m_Waypoints.empty()) + // Further, if it looks likely we're following a moving unit, recompute anyways, + // as that is necessary for good chasing behaviour. + else if (!m_ShortPath.m_Waypoints.empty() && !m_IsLikelyChasing && !IsFormationMember()) return false; CFixedVector2D targetPos; if (!ComputeTargetPosition(targetPos)) return false; + // Do a preliminary distance check, as the following operations can be slightly more expensive. + // Most goals should be less than DIRECT_PATH_RANGE in max range regarldess, + // so this won't make us miss too many cases. + if (!m_IsLikelyChasing) + if ((targetPos - from).CompareLength(DIRECT_PATH_RANGE * 2) > 0) + return false; + CmpPtr cmpPathfinder(GetSystemEntity()); if (!cmpPathfinder) return false; @@ -1487,7 +1505,7 @@ // Find the point on the goal shape that we should head towards CFixedVector2D goalPos = goal.NearestPointOnGoal(from); - // Fail if the target is too far away + // Fail if the target is indeed too far away. if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0) return false; @@ -1513,6 +1531,11 @@ if (!updatePaths) return true; + // If we end up here with no waypoints, it is quite likely that we tried going straight last turn, + // and that failed to reach the target because it moved. Turn on chasing mode. + if (!m_IsLikelyChasing && m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty()) + m_IsLikelyChasing = true; + // That route is okay, so update our path m_LongPath.m_Waypoints.clear(); m_ShortPath.m_Waypoints.clear(); @@ -1840,6 +1863,7 @@ m_MoveRequest = request; m_FailedMovements = 0; m_FollowKnownImperfectPathCountdown = 0; + m_IsLikelyChasing = false; ComputePathToGoal(cmpPosition->GetPosition2D(), goal); return true;