From 65cd29696ded6795abd468fed388cd2de7fbbb36 Mon Sep 17 00:00:00 2001 From: Freagarach Date: Sun, 3 Oct 2021 05:59:54 +0000 Subject: [PATCH] Don't stop gathering after starting by autocontinue when in a formation. FinishOrder called SetWaitingOnController, although the order (constructing) had pushed another order (gather). This is done now by telling we finished the order only when really idle. This seems to boil down to the question: If we issued an order to a formation, and its members have wandered off (imagine attacking an entity and our members have finished the initial target but attack nearby ones) do we want the whole formation to continue attacking or execute the next order. Also fixes reforming whilst attacking when an attack order was issued by the player. Reported by: @Langbart at https://code.wildfiregames.com/D2175#182343 Differential revision: https://code.wildfiregames.com/D4282 Tested by: @Langbart This was SVN commit r25947. --- .../public/simulation/components/Formation.js | 67 ++++++++++++------- .../public/simulation/components/UnitAI.js | 19 +++--- .../components/tests/test_UnitAI.js | 2 +- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js index f3c90ddd78..72edd89fc7 100644 --- a/binaries/data/mods/public/simulation/components/Formation.js +++ b/binaries/data/mods/public/simulation/components/Formation.js @@ -79,7 +79,8 @@ Formation.prototype.variablesToSerialize = [ "memberPositions", "maxRowsUsed", "maxColumnsUsed", - "waitingOnController", + "finishedEntities", + "idleEntities", "columnar", "rearrange", "formationMembersWithAura", @@ -135,8 +136,9 @@ Formation.prototype.Init = function(deserialized = false) this.memberPositions = {}; this.maxRowsUsed = 0; this.maxColumnsUsed = []; - // Entities that are waiting on the controller. - this.waitingOnController = []; + // Entities that have finished the original task. + this.finishedEntities = new Set(); + this.idleEntities = new Set(); // Whether we're travelling in column (vs box) formation. this.columnar = false; // Whether we should rearrange all formation members. @@ -284,34 +286,50 @@ Formation.prototype.GetFormationAnimationVariant = function(entity) return undefined; }; -Formation.prototype.SetWaitingOnController = function(ent) +Formation.prototype.SetFinishedEntity = function(ent) { - // Rotate the entity to the right angle. - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - let cmpEntPosition = Engine.QueryInterface(ent, IID_Position); + // Rotate the entity to the correct angle. + const cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + const cmpEntPosition = Engine.QueryInterface(ent, IID_Position); if (cmpEntPosition && cmpEntPosition.IsInWorld() && cmpPosition && cmpPosition.IsInWorld()) cmpEntPosition.TurnTo(cmpPosition.GetRotation().y); - if (this.waitingOnController.indexOf(ent) !== -1) - return; - this.waitingOnController.push(ent); + this.finishedEntities.add(ent); }; -Formation.prototype.UnsetWaitingOnController = function(ent) +Formation.prototype.UnsetFinishedEntity = function(ent) { - let ind = this.waitingOnController.indexOf(ent); - if (ind !== -1) - this.waitingOnController.splice(ind, 1); + this.finishedEntities.delete(ent); }; -Formation.prototype.ResetWaitingEntities = function() +Formation.prototype.ResetFinishedEntities = function() { - this.waitingOnController = []; + this.finishedEntities.clear(); }; -Formation.prototype.AreAllMembersWaiting = function() +Formation.prototype.AreAllMembersFinished = function() { - return this.waitingOnController.length === this.members.length; + return this.finishedEntities.size === this.members.length; +}; + +Formation.prototype.SetIdleEntity = function(ent) +{ + this.idleEntities.add(ent); +}; + +Formation.prototype.UnsetIdleEntity = function(ent) +{ + this.idleEntities.delete(ent); +}; + +Formation.prototype.ResetIdleEntities = function() +{ + this.idleEntities.clear(); +}; + +Formation.prototype.AreAllMembersIdle = function() +{ + return this.idleEntities.size === this.members.length; }; /** @@ -362,10 +380,10 @@ Formation.prototype.RemoveMembers = function(ents, renamed = false) { this.offsets = undefined; 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) { + this.finishedEntities.delete(ent); let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); cmpUnitAI.UpdateWorkOrders(); cmpUnitAI.SetFormationController(INVALID_ENTITY); @@ -453,7 +471,7 @@ Formation.prototype.Disband = function() } this.members = []; - this.waitingOnController = []; + this.finishedEntities.clear(); this.formationMembersWithAura = []; this.offsets = undefined; @@ -525,8 +543,8 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force, varia let yMin = 0; if (force) - // Reset waitingOnController as FormationWalk is called. - this.ResetWaitingEntities(); + // Reset finishedEntities as FormationWalk is called. + this.ResetFinishedEntities(); for (let i = 0; i < this.offsets.length; ++i) { @@ -965,9 +983,8 @@ Formation.prototype.OnGlobalEntityRenamed = function(msg) 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); + if (this.finishedEntities.delete(msg.entity)) + this.finishedEntities.add(msg.newentity); // Save rearranging to temporarily set it to false. let temp = this.rearrange; diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index f5d5004bd5..534d99d89c 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1358,7 +1358,7 @@ UnitAI.prototype.UnitFsmSpec = { "Timer": function(msg) { let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - if (cmpFormation && !cmpFormation.AreAllMembersWaiting()) + if (cmpFormation && !cmpFormation.AreAllMembersFinished()) return; if (this.FinishOrder()) @@ -1374,7 +1374,7 @@ UnitAI.prototype.UnitFsmSpec = { this.StopTimer(); // Reform entirely as members might be all over the place now. let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - if (cmpFormation) + if (cmpFormation && (cmpFormation.AreAllMembersIdle() || this.orderQueue.length)) cmpFormation.MoveMembersIntoFormation(true); // Update the held position so entities respond to orders. @@ -1609,6 +1609,8 @@ UnitAI.prototype.UnitFsmSpec = { if (this.isIdle) { + if (this.IsFormationMember()) + Engine.QueryInterface(this.formationController, IID_Formation).UnsetIdleEntity(this.entity); this.isIdle = false; Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); } @@ -1677,6 +1679,7 @@ UnitAI.prototype.UnitFsmSpec = { let cmpFormationAI = Engine.QueryInterface(this.formationController, IID_UnitAI); if (!cmpFormationAI || !cmpFormationAI.IsIdle()) return; + Engine.QueryInterface(this.formationController, IID_Formation).SetIdleEntity(this.entity); } this.isIdle = true; @@ -3895,8 +3898,8 @@ UnitAI.prototype.FinishOrder = function() if (cmpUnitAI) { // Inform the formation controller that we finished this task - let cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); - cmpFormation.SetWaitingOnController(this.entity); + Engine.QueryInterface(this.formationController, IID_Formation). + SetFinishedEntity(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) @@ -6450,20 +6453,20 @@ UnitAI.prototype.AttackEntitiesByPreference = function(ents) /** * Call UnitAI.funcname(args) on all formation members. - * @param resetWaitingEntities - If true, call ResetWaitingEntities first. + * @param resetFinishedEntities - If true, call ResetFinishedEntities 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, resetWaitingEntities = true) +UnitAI.prototype.CallMemberFunction = function(funcname, args, resetFinishedEntities = true) { var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); if (!cmpFormation) return; - if (resetWaitingEntities) - cmpFormation.ResetWaitingEntities(); + if (resetFinishedEntities) + cmpFormation.ResetFinishedEntities(); cmpFormation.GetMembers().forEach(ent => { let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); diff --git a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js index 38480a92ea..ddfcc81b72 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ b/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -439,7 +439,7 @@ function TestMoveIntoFormationWhileAttacking() // let all units be in position for (let ent of unitAIs) - controllerFormation.SetWaitingOnController(ent); + controllerFormation.SetFinishedEntity(ent); for (let ent of unitAIs) TS_ASSERT_EQUALS(unitAI.fsmStateName, "INDIVIDUAL.COMBAT.ATTACKING");