1
0
forked from mirrors/0ad

Fix formations not queueing orders properly.

Formation controllers were not always waiting on their members correctly
because the "finishedOrder" check was being reset in the wrong place.
This happened particularly with queued orders.

This merges the "finishedOrder" and the "InPosition" logic.

Reported by: elexis
Based on a patch by: Freagarach
Tested by: Freagarach
Reviewed By: Angen
Fixes #3274

Differential Revision: https://code.wildfiregames.com/D2702
This was SVN commit r23744.
This commit is contained in:
wraitii
2020-06-06 16:07:01 +00:00
parent 759bc754c3
commit feda708d5d
3 changed files with 58 additions and 90 deletions
@@ -78,7 +78,7 @@ Formation.prototype.variablesToSerialize = [
"memberPositions",
"maxRowsUsed",
"maxColumnsUsed",
"inPosition",
"waitingOnController",
"columnar",
"rearrange",
"formationMembersWithAura",
@@ -135,7 +135,7 @@ Formation.prototype.Init = function(deserialized = false)
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 @@ Formation.prototype.GetFormationAnimationVariant = function(entity)
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 @@ Formation.prototype.SetInPosition = function(ent)
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)
{
var ind = this.inPosition.indexOf(ent);
if (ind != -1)
this.inPosition.splice(ind, 1);
let ind = this.waitingOnController.indexOf(ent);
if (ind !== -1)
this.waitingOnController.splice(ind, 1);
};
Formation.prototype.ResetWaitingEntities = function()
{
this.waitingOnController = [];
};
Formation.prototype.AreAllMembersWaiting = function()
{
return this.waitingOnController.length === this.members.length;
};
/**
@@ -355,8 +359,8 @@ Formation.prototype.SetMembers = function(ents)
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.RemoveMembers = function(ents, renamed = false)
Formation.prototype.AddMembers = function(ents)
{
this.offsets = undefined;
this.inPosition = [];
for (let ent of this.formationMembersWithAura)
{
@@ -430,25 +433,6 @@ Formation.prototype.AddMembers = function(ents)
this.MoveMembersIntoFormation(true, true, this.lastOrderVariant);
};
/**
* 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.
*/
@@ -468,7 +452,7 @@ Formation.prototype.Disband = function()
this.members = [];
this.inPosition = [];
this.waitingOnController = [];
this.formationMembersWithAura = [];
this.offsets = undefined;
@@ -549,15 +533,22 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter, force, varia
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.OnGlobalOwnershipChanged = function(msg)
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;
@@ -962,10 +962,7 @@ UnitAI.prototype.UnitFsmSpec = {
"MovementUpdate": function(msg) {
if (msg.likelyFailure || this.CheckRange(this.order.data))
{
this.FinishOrder();
this.CallMemberFunction("ResetFinishOrder", []);
}
},
},
@@ -995,10 +992,7 @@ UnitAI.prototype.UnitFsmSpec = {
"MovementUpdate": function(msg) {
if (msg.likelyFailure || this.CheckRange(this.order.data))
{
this.FinishOrder();
this.CallMemberFunction("ResetFinishOrder", []);
}
},
},
@@ -1132,14 +1126,7 @@ UnitAI.prototype.UnitFsmSpec = {
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 @@ UnitAI.prototype.UnitFsmSpec = {
"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 @@ UnitAI.prototype.UnitFsmSpec = {
// 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 @@ UnitAI.prototype.UnitFsmSpec = {
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 @@ UnitAI.prototype.UnitFsmSpec = {
// 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 @@ UnitAI.prototype.Init = function()
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 @@ UnitAI.prototype.IsFormationMember = function()
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 @@ UnitAI.prototype.FinishOrder = function()
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 @@ UnitAI.prototype.AttackEntitiesByPreference = function(ents)
};
/**
* 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);
});
};
@@ -394,16 +394,16 @@ function TestMoveIntoFormationWhileAttacking()
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();