Optimise FindWalkAndFightTargets.
FindWalkAndFightTargets is used during attack-walk (and a few other situations) to find new entities to attack. This function can be a bit slow, taking large chunks of time during battles. This optimises it by assuming that one of the surrounding unit will match preferred criteria (which, for most soldiers, are 'Human'), thus returning the first attackable entity. In the worst case, it should still be slightly faster than the current code. Differential Revision: https://code.wildfiregames.com/D3446 This was SVN commit r25102.
This commit is contained in:
parent
72f0fdb41b
commit
c87229aa48
@ -334,7 +334,7 @@ Attack.prototype.CanAttack = function(target, wantedTypes)
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns null if we have no preference or the lowest index of a preferred class.
|
||||
* Returns undefined if we have no preference or the lowest index of a preferred class.
|
||||
*/
|
||||
Attack.prototype.GetPreference = function(target)
|
||||
{
|
||||
@ -344,7 +344,7 @@ Attack.prototype.GetPreference = function(target)
|
||||
|
||||
let targetClasses = cmpIdentity.GetClassesList();
|
||||
|
||||
let minPref = null;
|
||||
let minPref;
|
||||
for (let type of this.GetAttackTypes())
|
||||
{
|
||||
let preferredClasses = this.GetPreferredClasses(type);
|
||||
@ -354,7 +354,7 @@ Attack.prototype.GetPreference = function(target)
|
||||
{
|
||||
if (pref === 0)
|
||||
return pref;
|
||||
if ((minPref === null || minPref > pref))
|
||||
if ((minPref === undefined || minPref > pref))
|
||||
minPref = pref;
|
||||
}
|
||||
}
|
||||
|
@ -996,7 +996,9 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
Engine.ProfileStart("FindWalkAndFightTargets");
|
||||
this.FindWalkAndFightTargets();
|
||||
Engine.ProfileStop();
|
||||
},
|
||||
|
||||
"MovementUpdate": function(msg) {
|
||||
@ -2221,7 +2223,11 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
if (this.FinishOrder())
|
||||
{
|
||||
if (this.IsWalkingAndFighting())
|
||||
{
|
||||
Engine.ProfileStart("FindWalkAndFightTargets");
|
||||
this.FindWalkAndFightTargets();
|
||||
Engine.ProfileStop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -6056,58 +6062,77 @@ UnitAI.prototype.FindWalkAndFightTargets = function()
|
||||
{
|
||||
if (this.IsFormationController())
|
||||
{
|
||||
var cmpUnitAI;
|
||||
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
||||
for (var ent of cmpFormation.members)
|
||||
let cmpUnitAI;
|
||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
||||
for (let ent of cmpFormation.members)
|
||||
{
|
||||
if (!(cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI)))
|
||||
continue;
|
||||
var targets = cmpUnitAI.GetTargetsFromUnit();
|
||||
for (var targ of targets)
|
||||
{
|
||||
if (!cmpUnitAI.CanAttack(targ))
|
||||
continue;
|
||||
if (this.order.data.targetClasses)
|
||||
{
|
||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity);
|
||||
var targetClasses = this.order.data.targetClasses;
|
||||
if (targetClasses.attack && cmpIdentity &&
|
||||
!MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack))
|
||||
continue;
|
||||
if (targetClasses.avoid && cmpIdentity &&
|
||||
MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid))
|
||||
continue;
|
||||
// Only used by the AIs to prevent some choices of targets
|
||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
|
||||
continue;
|
||||
}
|
||||
this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this.order.data.allowCapture });
|
||||
if (cmpUnitAI.FindWalkAndFightTargets())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var targets = this.GetTargetsFromUnit();
|
||||
for (var targ of targets)
|
||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
|
||||
let entities;
|
||||
if (!this.losAttackRangeQuery || !this.GetStance().targetVisibleEnemies || !cmpAttack)
|
||||
entities = [];
|
||||
else
|
||||
{
|
||||
if (!this.CanAttack(targ))
|
||||
continue;
|
||||
if (this.order.data.targetClasses)
|
||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery);
|
||||
}
|
||||
|
||||
let attackfilter = e => {
|
||||
if (this?.order?.data?.targetClasses)
|
||||
{
|
||||
var cmpIdentity = Engine.QueryInterface(targ, IID_Identity);
|
||||
var targetClasses = this.order.data.targetClasses;
|
||||
let cmpIdentity = Engine.QueryInterface(e, IID_Identity);
|
||||
let targetClasses = this.order.data.targetClasses;
|
||||
if (cmpIdentity && targetClasses.attack &&
|
||||
!MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.attack))
|
||||
continue;
|
||||
return false;
|
||||
if (cmpIdentity && targetClasses.avoid &&
|
||||
MatchesClassList(cmpIdentity.GetClassesList(), targetClasses.avoid))
|
||||
continue;
|
||||
return false;
|
||||
// Only used by the AIs to prevent some choices of targets
|
||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[targ])
|
||||
continue;
|
||||
if (targetClasses.vetoEntities && targetClasses.vetoEntities[e])
|
||||
return false;
|
||||
}
|
||||
this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this.order.data.allowCapture });
|
||||
let cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
|
||||
if (cmpOwnership && cmpOwnership.GetOwner() > 0)
|
||||
return true;
|
||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
|
||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal());
|
||||
};
|
||||
|
||||
let prefs = {};
|
||||
let bestPref;
|
||||
let targets = [];
|
||||
let pref;
|
||||
for (let v of entities)
|
||||
{
|
||||
if (this.CanAttack(v) && attackfilter(v))
|
||||
{
|
||||
pref = cmpAttack.GetPreference(v);
|
||||
if (pref === 0)
|
||||
{
|
||||
this.PushOrderFront("Attack", { "target": v, "force": false, "allowCapture": this?.order?.data?.allowCapture });
|
||||
return true;
|
||||
}
|
||||
targets.push(v);
|
||||
}
|
||||
prefs[v] = pref;
|
||||
if (pref !== undefined && (bestPref === undefined || pref < bestPref))
|
||||
bestPref = pref;
|
||||
}
|
||||
|
||||
for (let targ of targets)
|
||||
{
|
||||
if (prefs[targ] !== bestPref)
|
||||
continue;
|
||||
this.PushOrderFront("Attack", { "target": targ, "force": false, "allowCapture": this?.order?.data?.allowCapture });
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -6118,39 +6143,6 @@ UnitAI.prototype.FindWalkAndFightTargets = function()
|
||||
return false;
|
||||
};
|
||||
|
||||
UnitAI.prototype.GetTargetsFromUnit = function()
|
||||
{
|
||||
if (!this.losAttackRangeQuery)
|
||||
return [];
|
||||
|
||||
if (!this.GetStance().targetVisibleEnemies)
|
||||
return [];
|
||||
|
||||
let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
if (!cmpAttack)
|
||||
return [];
|
||||
|
||||
let attackfilter = function(e) {
|
||||
if (!cmpAttack.CanAttack(e))
|
||||
return false;
|
||||
|
||||
let cmpOwnership = Engine.QueryInterface(e, IID_Ownership);
|
||||
if (cmpOwnership && cmpOwnership.GetOwner() > 0)
|
||||
return true;
|
||||
|
||||
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
|
||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal());
|
||||
};
|
||||
|
||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
let entities = cmpRangeManager.ResetActiveQuery(this.losAttackRangeQuery);
|
||||
let targets = entities.filter(attackfilter).sort(function(a, b) {
|
||||
return cmpAttack.CompareEntitiesByPreference(a, b);
|
||||
});
|
||||
|
||||
return targets;
|
||||
};
|
||||
|
||||
UnitAI.prototype.GetQueryRange = function(iid)
|
||||
{
|
||||
let ret = { "min": 0, "max": 0 };
|
||||
|
@ -397,7 +397,7 @@ function testAttackPreference()
|
||||
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+1), 0);
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+2), 1);
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+3), null);
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+4), null);
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+3), undefined);
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+4), undefined);
|
||||
}
|
||||
testAttackPreference();
|
||||
|
@ -449,3 +449,77 @@ TestFormationExiting(1);
|
||||
TestFormationExiting(2);
|
||||
|
||||
TestMoveIntoFormationWhileAttacking();
|
||||
|
||||
|
||||
function TestWalkAndFightTargets()
|
||||
{
|
||||
const ent = 10;
|
||||
let unitAI = ConstructComponent(ent, "UnitAI", {
|
||||
"FormationController": "false",
|
||||
"DefaultStance": "aggressive",
|
||||
"FleeDistance": 10
|
||||
});
|
||||
unitAI.OnCreate();
|
||||
unitAI.losAttackRangeQuery = true;
|
||||
|
||||
// The result is stored here
|
||||
let result;
|
||||
unitAI.PushOrderFront = function(type, order)
|
||||
{
|
||||
if (type === "Attack" && order?.target)
|
||||
result = order.target;
|
||||
};
|
||||
|
||||
// Create some targets.
|
||||
AddMock(ent+1, IID_UnitAI, { "IsAnimal": () => true, "IsDangerousAnimal": () => false });
|
||||
AddMock(ent+2, IID_Ownership, { "GetOwner": () => 2 });
|
||||
AddMock(ent+3, IID_Ownership, { "GetOwner": () => 2 });
|
||||
AddMock(ent+4, IID_Ownership, { "GetOwner": () => 2 });
|
||||
AddMock(ent+5, IID_Ownership, { "GetOwner": () => 2 });
|
||||
AddMock(ent+6, IID_Ownership, { "GetOwner": () => 2 });
|
||||
AddMock(ent+7, IID_Ownership, { "GetOwner": () => 2 });
|
||||
|
||||
unitAI.CanAttack = function(target)
|
||||
{
|
||||
return target !== ent+2 && target !== ent+7;
|
||||
};
|
||||
|
||||
AddMock(ent, IID_Attack, {
|
||||
"GetPreference": (target) => ({
|
||||
[ent+4]: 0,
|
||||
[ent+5]: 1,
|
||||
[ent+6]: 2,
|
||||
[ent+7]: 0
|
||||
}?.[target])
|
||||
});
|
||||
|
||||
let runTest = function(ents, res)
|
||||
{
|
||||
result = undefined;
|
||||
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||
"ResetActiveQuery": () => ents
|
||||
});
|
||||
TS_ASSERT_EQUALS(unitAI.FindWalkAndFightTargets(), !!res);
|
||||
TS_ASSERT_EQUALS(result, res);
|
||||
};
|
||||
|
||||
// No entities.
|
||||
runTest([]);
|
||||
|
||||
// Entities that cannot be attacked.
|
||||
runTest([ent+1, ent+2, ent+7]);
|
||||
|
||||
// No preference, one attackable entity.
|
||||
runTest([ent+1, ent+2, ent+3], ent+3);
|
||||
|
||||
// Check preferences.
|
||||
runTest([ent+1, ent+2, ent+3, ent+4], ent+4);
|
||||
runTest([ent+1, ent+2, ent+3, ent+4, ent+5], ent+4);
|
||||
runTest([ent+1, ent+2, ent+6, ent+3, ent+4, ent+5], ent+4);
|
||||
runTest([ent+1, ent+2, ent+7, ent+6, ent+3, ent+4, ent+5], ent+4);
|
||||
runTest([ent+1, ent+2, ent+7, ent+6, ent+3, ent+5], ent+5);
|
||||
runTest([ent+1, ent+2, ent+7, ent+6, ent+3], ent+6);
|
||||
runTest([ent+1, ent+2, ent+7, ent+3], ent+3);
|
||||
}
|
||||
|
||||
TestWalkAndFightTargets();
|
||||
|
Loading…
Reference in New Issue
Block a user