diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 8e21df617b..ae87671e04 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1384,17 +1384,12 @@ UnitAI.prototype.UnitFsmSpec = { // Occurs when the unit has reached its destination and the controller // is done moving. The controller is notified. "MovementUpdate": function(msg) { - // We're supposed to be walking in formation, - // but the controller has no position -> abort. - let cmpControllerPosition = Engine.QueryInterface(this.formationController, IID_Position); - if (!cmpControllerPosition || !cmpControllerPosition.IsInWorld()) - { - this.FinishOrder(); - return; - } - if (!msg.likelyFailure && !msg.likelySuccess) - return; - + // When walking in formation, we'll only get notified in case of failure + // if the formation controller has stopped walking. + // Formations can start lagging a lot if many entities request short path + // so prefer to finish order early than retry pathing. + // (see https://code.wildfiregames.com/rP23806) + // (if the message is likelyFailure of likelySuccess, we also want to stop). this.FinishOrder(); }, }, diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index a6b8a5f27a..9ff8393d7f 100644 --- a/source/simulation2/components/CCmpUnitMotion.cpp +++ b/source/simulation2/components/CCmpUnitMotion.cpp @@ -483,10 +483,10 @@ private: return m_MoveRequest.m_Type == MoveRequest::OFFSET; } - bool IsFormationControllerNotMoving() const + bool IsFormationControllerMoving() const { CmpPtr cmpControllerMotion(GetSimContext(), m_MoveRequest.m_Entity); - return cmpControllerMotion && !cmpControllerMotion->IsMoveRequested(); + return cmpControllerMotion && cmpControllerMotion->IsMoveRequested(); } entity_id_t GetGroup() const @@ -501,6 +501,12 @@ private: */ void MoveFailed() { + // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time + // if our current offset is unreachable, but we don't want to end up stuck. + // (If the formation controller has stopped moving however, we can safely message). + if (IsFormationMember() && IsFormationControllerMoving()) + return; + CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_FAILURE); GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); } @@ -512,10 +518,33 @@ private: */ void MoveSucceeded() { + // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time + // if our current offset is unreachable, but we don't want to end up stuck. + // (If the formation controller has stopped moving however, we can safely message). + if (IsFormationMember() && IsFormationControllerMoving()) + return; + CMessageMotionUpdate msg(CMessageMotionUpdate::LIKELY_SUCCESS); GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); } + /** + * Warns other components that our current movement was obstructed (i.e. we failed to move this turn). + * This should only be called before the actual movement in a given turn, or units might both move and try to do things + * on the same turn, leading to gliding units. + */ + void MoveObstructed() + { + // Don't notify if we are a formation member in a moving formation - we can occasionally be stuck for a long time + // if our current offset is unreachable, but we don't want to end up stuck. + // (If the formation controller has stopped moving however, we can safely message). + if (IsFormationMember() && IsFormationControllerMoving()) + return; + + CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); + GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); + } + /** * Increment the number of failed path computations and notify other components if required. */ @@ -761,17 +790,10 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path) } if (m_FailedPathComputations >= 1) - { // Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. - CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); - GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); - } + MoveObstructed(); - // Don't notify if we are a formation member - we can occasionally be stuck for a long time - // if our current offset is unreachable. - // Unless the formationcontroller has reached final destination and we are stuck - if (!IsFormationMember() || IsFormationControllerNotMoving()) - IncrementFailedPathComputationAndMaybeNotify(); + IncrementFailedPathComputationAndMaybeNotify(); // If there's no waypoints then we couldn't get near the target // If we're globally following a long path, try to remove the next waypoint, @@ -1013,11 +1035,8 @@ bool CCmpUnitMotion::HandleObstructedMove() return false; if (m_FailedPathComputations >= 1) - { // Inform other components - we might be ordered to stop, and computeGoal will then fail and return early. - CMessageMotionUpdate msg(CMessageMotionUpdate::OBSTRUCTED); - GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); - } + MoveObstructed(); CFixedVector2D pos = cmpPosition->GetPosition2D();