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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user