Index: binaries/data/mods/public/simulation/components/Formation.js =================================================================== --- binaries/data/mods/public/simulation/components/Formation.js +++ binaries/data/mods/public/simulation/components/Formation.js @@ -308,8 +308,7 @@ }; /** - * Set whether we should rearrange formation members if - * units are removed from the formation. + * Set whether we are allowed to rearrange formation members. */ Formation.prototype.SetRearrange = function(rearrange) { @@ -911,29 +910,33 @@ Formation.prototype.ShapeUpdate = function() { + if (!this.rearrange) + return; + // Check the distance to twin formations, and merge if when // the formations could collide - for (var i = this.twinFormations.length - 1; i >= 0; --i) + for (let i = this.twinFormations.length - 1; i >= 0; --i) { // only do the check on one side if (this.twinFormations[i] <= this.entity) continue; - - var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - var cmpOtherPosition = Engine.QueryInterface(this.twinFormations[i], IID_Position); - var cmpOtherFormation = Engine.QueryInterface(this.twinFormations[i], IID_Formation); - if (!cmpPosition || !cmpOtherPosition || !cmpOtherFormation) + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + let cmpOtherPosition = Engine.QueryInterface(this.twinFormations[i], IID_Position); + let cmpOtherFormation = Engine.QueryInterface(this.twinFormations[i], IID_Formation); + if (!cmpPosition || !cmpOtherPosition || !cmpOtherFormation || + !cmpPosition.IsInWorld() || !cmpOtherPosition.IsInWorld()) continue; - var thisPosition = cmpPosition.GetPosition2D(); - var otherPosition = cmpOtherPosition.GetPosition2D(); - var dx = thisPosition.x - otherPosition.x; - var dy = thisPosition.y - otherPosition.y; - var dist = Math.sqrt(dx * dx + dy * dy); + let thisPosition = cmpPosition.GetPosition2D(); + let otherPosition = cmpOtherPosition.GetPosition2D(); - var thisSize = this.GetSize(); - var otherSize = cmpOtherFormation.GetSize(); - var minDist = Math.max(thisSize.width / 2, thisSize.depth / 2) + + let dx = thisPosition.x - otherPosition.x; + let dy = thisPosition.y - otherPosition.y; + let dist = Math.sqrt(dx * dx + dy * dy); + + let thisSize = this.GetSize(); + let otherSize = cmpOtherFormation.GetSize(); + let minDist = Math.max(thisSize.width / 2, thisSize.depth / 2) + Math.max(otherSize.width / 2, otherSize.depth / 2) + this.formationSeparation; @@ -946,12 +949,12 @@ cmpOtherFormation.RemoveMembers(otherMembers); this.AddMembers(otherMembers); Engine.DestroyEntity(this.twinFormations[i]); - this.twinFormations.splice(i,1); + this.twinFormations.splice(i, 1); } // Switch between column and box if necessary - var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); - var walkingDistance = cmpUnitAI.ComputeWalkingDistance(); - var columnar = walkingDistance > g_ColumnDistanceThreshold; + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + let walkingDistance = cmpUnitAI.ComputeWalkingDistance(); + let columnar = walkingDistance > g_ColumnDistanceThreshold; if (columnar != this.columnar) { this.offsets = undefined; Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -742,6 +742,9 @@ this.CallMemberFunction("Stop", [false]); this.StopMoving(); this.FinishOrder(); + // Don't move the members back into formation, + // as the formation then resets and it looks odd when walk-stopping. + // TODO: this should be improved in the formation reshaping code. }, "Order.Attack": function(msg) { @@ -930,8 +933,6 @@ "IDLE": { "enter": function(msg) { - var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - cmpFormation.SetRearrange(false); return false; }, }, @@ -1223,9 +1224,16 @@ "MEMBER": { // Wait for individual members to finish "enter": function(msg) { - var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + // Don't rearrange the formation, as that forces all units to stop + // what they're doing. + let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); cmpFormation.SetRearrange(false); - this.StopMoving(); + // While waiting on members, the formation is more like + // a group of unit and does not have a well-defined position, + // so move the controller out of the world to enforce that. + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + cmpPosition.MoveOutOfWorld(); + this.StartTimer(1000, 1000); return false; }, @@ -1245,13 +1253,15 @@ this.FindWalkAndFightTargets(); return; } - return false; + return; }, "leave": function(msg) { this.StopTimer(); + // Reform entirely as members might be all over the place now. let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - cmpFormation.MoveToMembersCenter(); + if (cmpFormation) + cmpFormation.MoveMembersIntoFormation(true); }, }, }, @@ -1328,10 +1338,7 @@ "WALKING": { "enter": function() { - this.formationOffset = { "x": this.order.data.x, "z": this.order.data.z }; let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - // Prevent unit to turn when stopmoving is called. - cmpUnitMotion.SetFacePointAfterMove(false); cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z); if (this.order.data.offsetsChanged) { @@ -1349,32 +1356,31 @@ }, "leave": function() { + // Don't use the logic from unitMotion, as SetInPosition + // has already given us a custom rotation + // (or we failed to move and thus don't care.) + this.SetFacePointAfterMove(false); this.StopMoving(); + // Reset default behaviour (TODO: actually get the previuos behaviour). this.SetFacePointAfterMove(true); }, - // Occurs when the unit has reached its destination and the controller - // is done moving. The controller is notified. "MovementUpdate": function(msg) { - // We can only finish this order if the move was really completed. - let cmpPosition = Engine.QueryInterface(this.formationController, IID_Position); - let atDestination = cmpPosition && cmpPosition.IsInWorld(); - if (!atDestination && cmpPosition) + // 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()) { - let pos = cmpPosition.GetPosition2D(); - atDestination = this.CheckPointRangeExplicit(pos.X + this.order.data.x, pos.Y + this.order.data.z, 0, 1); - } - if (!atDestination && !msg.likelyFailure) + this.FinishOrder(); return; - - if (this.FinishOrder()) + } + if (!msg.likelyFailure && !msg.likelySuccess) return; let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); if (cmpFormation) cmpFormation.SetInPosition(this.entity); - - delete this.formationOffset; + this.FinishOrder(); }, }, @@ -1544,16 +1550,6 @@ if (this.FindNewTargets()) return; - if (this.formationOffset && this.formationController) - { - this.PushOrder("FormationWalk", { - "target": this.formationController, - "x": this.formationOffset.x, - "z": this.formationOffset.z - }); - return; - } - if (!this.isIdle) { this.isIdle = true; @@ -3730,7 +3726,8 @@ this.order = this.orderQueue[0]; let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (this.orderQueue.length && (this.IsGarrisoned() || cmpPosition && cmpPosition.IsInWorld())) + if (this.orderQueue.length && (this.IsGarrisoned() || this.IsFormationController() || + cmpPosition && cmpPosition.IsInWorld())) { let ret = this.UnitFsm.ProcessMessage(this, { "type": "Order."+this.order.type, "data": this.order.data } Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -289,6 +289,7 @@ GetPosition2D: function() { return new Vector2D(this.x, this.z); }, GetRotation: function() { return { "y": 0 }; }, IsInWorld: function() { return true; }, + MoveOutOfWorld: () => {} }); AddMock(controller, IID_UnitMotion, {