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 @@ -78,7 +78,7 @@ "memberPositions", "maxRowsUsed", "maxColumnsUsed", - "inPosition", + "waitingOnController", "columnar", "rearrange", "formationMembersWithAura", @@ -135,7 +135,7 @@ this.memberPositions = {}; this.maxRowsUsed = 0; this.maxColumnsUsed = []; - this.inPosition = []; // entities that have reached their final position + this.waitingOnController = []; // entities that are waiting on the controller. this.columnar = false; // whether we're travelling in column (vs box) formation this.rearrange = true; // whether we should rearrange all formation members this.formationMembersWithAura = []; // Members with a formation aura @@ -281,10 +281,7 @@ return undefined; }; -/** - * Permits formation members to register that they've reached their destination. - */ -Formation.prototype.SetInPosition = function(ent) +Formation.prototype.SetWaitingOnController = function(ent) { // Rotate the entity to the right angle. let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); @@ -292,19 +289,26 @@ if (cmpEntPosition && cmpEntPosition.IsInWorld() && cmpPosition && cmpPosition.IsInWorld()) cmpEntPosition.TurnTo(cmpPosition.GetRotation().y); - if (this.inPosition.indexOf(ent) != -1) + if (this.waitingOnController.indexOf(ent) !== -1) return; - this.inPosition.push(ent); + this.waitingOnController.push(ent); }; -/** - * Called by formation members upon entering non-walking states. - */ -Formation.prototype.UnsetInPosition = function(ent) +Formation.prototype.UnsetWaitingOnController = function(ent) +{ + let ind = this.waitingOnController.indexOf(ent); + if (ind !== -1) + this.waitingOnController.splice(ind, 1); +}; + +Formation.prototype.ResetWaitingEntities = function() +{ + this.waitingOnController = []; +}; + +Formation.prototype.AreAllMembersWaiting = function() { - var ind = this.inPosition.indexOf(ent); - if (ind != -1) - this.inPosition.splice(ind, 1); + return this.waitingOnController.length === this.members.length; }; /** @@ -355,8 +359,8 @@ Formation.prototype.RemoveMembers = function(ents, renamed = false) { this.offsets = undefined; - this.members = this.members.filter(function(e) { return ents.indexOf(e) == -1; }); - this.inPosition = this.inPosition.filter(function(e) { return ents.indexOf(e) == -1; }); + this.members = this.members.filter(ent => ents.indexOf(ent) === -1); + this.waitingOnController = this.waitingOnController.filter(ent => ents.indexOf(ent) === -1); for (let ent of ents) { @@ -397,7 +401,6 @@ Formation.prototype.AddMembers = function(ents) { this.offsets = undefined; - this.inPosition = []; for (let ent of this.formationMembersWithAura) { @@ -431,25 +434,6 @@ }; /** - * Called when the formation stops moving in order to detect - * units that have already reached their final positions. - */ -Formation.prototype.FindInPosition = function() -{ - for (var i = 0; i < this.members.length; ++i) - { - var cmpUnitMotion = Engine.QueryInterface(this.members[i], IID_UnitMotion); - if (!cmpUnitMotion.IsMoveRequested()) - { - // Verify that members are stopped in FORMATIONMEMBER.WALKING - var cmpUnitAI = Engine.QueryInterface(this.members[i], IID_UnitAI); - if (cmpUnitAI.IsWalking()) - this.SetInPosition(this.members[i]); - } - } -}; - -/** * Remove all members and destroy the formation. */ Formation.prototype.Disband = function() @@ -468,7 +452,7 @@ this.members = []; - this.inPosition = []; + this.waitingOnController = []; this.formationMembersWithAura = []; this.offsets = undefined; @@ -549,15 +533,22 @@ var xMin = 0; var yMin = 0; - for (var i = 0; i < this.offsets.length; ++i) + if (force) + // Reset waitingOnController as FormationWalk is called. + this.ResetWaitingEntities(); + + for (let i = 0; i < this.offsets.length; ++i) { - var offset = this.offsets[i]; + let offset = this.offsets[i]; - var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI); + let cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI); if (!cmpUnitAI) + { + warn("Entities without UnitAI in formation are not supported."); continue; + } - var data = + let data = { "target": this.entity, "x": offset.x, @@ -978,9 +969,13 @@ Formation.prototype.OnGlobalEntityRenamed = function(msg) { - if (this.members.indexOf(msg.entity) == -1) + if (this.members.indexOf(msg.entity) === -1) return; + let waitingIndex = this.waitingOnController.indexOf(msg.entity); + if (waitingIndex !== -1) + this.waitingOnController.splice(waitingIndex, 1, msg.newentity); + // Save rearranging to temporarily set it to false. let temp = this.rearrange; this.rearrange = false; 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 @@ -962,10 +962,7 @@ "MovementUpdate": function(msg) { if (msg.likelyFailure || this.CheckRange(this.order.data)) - { this.FinishOrder(); - this.CallMemberFunction("ResetFinishOrder", []); - } }, }, @@ -995,10 +992,7 @@ "MovementUpdate": function(msg) { if (msg.likelyFailure || this.CheckRange(this.order.data)) - { this.FinishOrder(); - this.CallMemberFunction("ResetFinishOrder", []); - } }, }, @@ -1132,14 +1126,7 @@ if (!msg.likelyFailure && !this.CheckRange(this.order.data)) return; - if (this.FinishOrder()) - { - this.CallMemberFunction("ResetFinishOrder", []); - return; - } - var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - - cmpFormation.FindInPosition(); + this.FinishOrder(); } }, @@ -1239,11 +1226,10 @@ "Timer": function(msg) { // Have all members finished the task? - if (!this.TestAllMemberFunction("HasFinishedOrder", [])) + let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + if (cmpFormation && !cmpFormation.AreAllMembersWaiting()) return; - this.CallMemberFunction("ResetFinishOrder", []); - // Execute the next order if (this.FinishOrder()) { @@ -1267,9 +1253,6 @@ // States for entities moving as part of a formation: "FORMATIONMEMBER": { "FormationLeave": function(msg) { - // We're not in a formation anymore, so no need to track this. - this.finishedOrder = false; - // Stop moving as soon as the formation disbands this.StopMoving(); @@ -1377,10 +1360,6 @@ if (this.FinishOrder()) return; - let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); - if (cmpFormation) - cmpFormation.SetInPosition(this.entity); - delete this.formationOffset; }, }, @@ -1388,10 +1367,6 @@ // Special case used by Order.LeaveFoundation "WALKINGTOPOINT": { "enter": function() { - var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); - if (cmpFormation) - cmpFormation.UnsetInPosition(this.entity); - if (!this.MoveTo(this.order.data)) { this.FinishOrder(); @@ -3377,7 +3352,6 @@ this.isGarrisoned = false; this.isIdle = false; this.isImmobile = false; // True if the unit is currently unable to move (garrisoned,...) - this.finishedOrder = false; // used to find if all formation members finished the order this.heldPosition = undefined; @@ -3413,16 +3387,6 @@ return (this.formationController != INVALID_ENTITY); }; -UnitAI.prototype.HasFinishedOrder = function() -{ - return this.finishedOrder; -}; - -UnitAI.prototype.ResetFinishOrder = function() -{ - this.finishedOrder = false; -}; - UnitAI.prototype.IsAnimal = function() { return (this.template.NaturalBehaviour ? true : false); @@ -3785,7 +3749,8 @@ if (cmpUnitAI) { // Inform the formation controller that we finished this task - this.finishedOrder = true; + let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); + cmpFormation.SetWaitingOnController(this.entity); // We don't want to carry out the default order // if there are still queued formation orders left if (cmpUnitAI.GetOrders().length > 1) @@ -6389,16 +6354,24 @@ }; /** - * Call obj.funcname(args) on UnitAI components of all formation members. + * Call UnitAI.funcname(args) on all formation members. + * @param resetWaitingEntities - If true, call ResetWaitingEntities first. + * If the controller wants to wait on its members to finish their order, + * this needs to be reset before sending new orders (in case they instafail) + * so it makes sense to do it here. + * Only set this to false if you're sure it's safe. */ -UnitAI.prototype.CallMemberFunction = function(funcname, args) +UnitAI.prototype.CallMemberFunction = function(funcname, args, resetWaitingEntities = true) { var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); if (!cmpFormation) return; + if (resetWaitingEntities) + cmpFormation.ResetWaitingEntities(); + cmpFormation.GetMembers().forEach(ent => { - var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); cmpUnitAI[funcname].apply(cmpUnitAI, args); }); }; Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -394,16 +394,16 @@ controllerAI.Attack(enemy, []); - for (var ent of unitAIs) + for (let ent of unitAIs) TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); controllerAI.MoveIntoFormation({"name": "Circle"}); // let all units be in position - for (var ent of unitAIs) - controllerFormation.SetInPosition(ent); + for (let ent of unitAIs) + controllerFormation.SetWaitingOnController(ent); - for (var ent of unitAIs) + for (let ent of unitAIs) TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING"); controllerFormation.Disband();