Index: ps/trunk/binaries/data/mods/public/simulation/components/Formation.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Formation.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Formation.js @@ -85,7 +85,6 @@ "formationMembersWithAura", "width", "depth", - "oldOrientation", "twinFormations", "formationSeparation", "offsets" @@ -146,7 +145,6 @@ this.formationMembersWithAura = []; this.width = 0; this.depth = 0; - this.oldOrientation = { "sin": 0, "cos": 0 }; this.twinFormations = []; // Distance from which two twin formations will merge into one. this.formationSeparation = 0; @@ -515,17 +513,12 @@ let offsetsChanged = false; let newOrientation = this.GetEstimatedOrientation(avgpos); - let dSin = Math.abs(newOrientation.sin - this.oldOrientation.sin); - let dCos = Math.abs(newOrientation.cos - this.oldOrientation.cos); - // If the formation existed, only recalculate positions if the turning agle is somewhat large. - if (!this.offsets || dSin > 1 || dCos > 1) + if (!this.offsets) { this.offsets = this.ComputeFormationOffsets(active, positions); offsetsChanged = true; } - this.oldOrientation = newOrientation; - let xMax = 0; let yMax = 0; let xMin = 0; Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -941,13 +941,14 @@ "IDLE": { "enter": function(msg) { - // Turn rearrange off. Otherwise, if the formation is idle, - // but individual units go off to fight, any death - // will rearrange the formation, looking odd. + // Turn rearrange off. Otherwise, if the formation is idle + // but individual units go off to fight, + // any death will rearrange the formation, which looks odd. // Instead, move idle units in formation on a timer. let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); cmpFormation.SetRearrange(false); - this.StartTimer(0, 2000); + // Start the timer on the next turn to catch up with potential stragglers. + this.StartTimer(100, 2000); this.isIdle = true; this.CallMemberFunction("ResetIdle"); return false; Index: ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp +++ ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp @@ -1007,10 +1007,13 @@ if (cmpControllerMotion && cmpControllerMotion->IsMoveRequested()) return false; + // In formation, return a match only if we are exactly at the target position. + // Otherwise, units can go in an infinite "walzting" loop when the Idle formation timer + // reforms them. CFixedVector2D targetPos; ComputeTargetPosition(targetPos); CmpPtr cmpPosition(GetEntityHandle()); - return cmpObstructionManager->IsInPointRange(GetEntityId(), targetPos.X, targetPos.Y, m_MoveRequest.m_MinRange, m_MoveRequest.m_MaxRange, false); + return (targetPos-cmpPosition->GetPosition2D()).CompareLength(fixed::Zero()) <= 0; } return false; } @@ -1072,11 +1075,7 @@ target = CFixedVector2D(shortPath.m_Waypoints.back().x, shortPath.m_Waypoints.back().z); CFixedVector2D offset = target - pos; - // Idle formations reorder their members to move to their offset, but numerical imprecisions can lead - // to small offsets every time. Units would then rotate in-place, looking odd. - // If the offset is small enough (epsilon is about 0.000015, so that's 0.0015 which is still irrelevant), - // simply don't turn. - if (turnRate > zero && offset.CompareLength(fixed::Epsilon() * 100) > 0) + if (turnRate > zero && !offset.IsZero()) { fixed maxRotation = turnRate.Multiply(timeLeft); fixed angleDiff = angle - atan2_approx(offset.X, offset.Y);