Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/BuildingAI.js
// Number of rounds of firing per 2 seconds. | // Number of rounds of firing per 2 seconds. | ||||
const roundCount = 10; | const roundCount = 10; | ||||
const attackType = "Ranged"; | |||||
function BuildingAI() {} | function BuildingAI() {} | ||||
BuildingAI.prototype.Schema = | BuildingAI.prototype.Schema = | ||||
"<element name='DefaultArrowCount'>" + | "<element name='DefaultArrowCount'>" + | ||||
"<data type='nonNegativeInteger'/>" + | "<data type='nonNegativeInteger'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<optional>" + | "<optional>" + | ||||
▲ Show 20 Lines • Show All 106 Lines • ▼ Show 20 Lines | BuildingAI.prototype.SetupRangeQuery = function() | ||||
var enemies = cmpPlayer.GetEnemies(); | var enemies = cmpPlayer.GetEnemies(); | ||||
// Remove gaia. | // Remove gaia. | ||||
if (enemies.length && enemies[0] == 0) | if (enemies.length && enemies[0] == 0) | ||||
enemies.shift(); | enemies.shift(); | ||||
if (!enemies.length) | if (!enemies.length) | ||||
return; | return; | ||||
var range = cmpAttack.GetRange(attackType); | const range = cmpAttack.GetFullAttackRange(); | ||||
// This takes entity sizes into accounts, so no need to compensate for structure size. | // This takes entity sizes into accounts, so no need to compensate for structure size. | ||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( | this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( | ||||
this.entity, range.min, range.max, range.elevationBonus, | this.entity, range.min, range.max, range.elevationBonus, | ||||
enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal")); | enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal")); | ||||
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); | cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); | ||||
}; | }; | ||||
Show All 11 Lines | if (this.gaiaUnitsQuery) | ||||
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); | cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); | ||||
this.gaiaUnitsQuery = undefined; | this.gaiaUnitsQuery = undefined; | ||||
} | } | ||||
var cmpPlayer = QueryOwnerInterface(this.entity); | var cmpPlayer = QueryOwnerInterface(this.entity); | ||||
if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) | if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) | ||||
return; | return; | ||||
var range = cmpAttack.GetRange(attackType); | const range = cmpAttack.GetFullAttackRange(); | ||||
// This query is only interested in Gaia entities that can attack. | // This query is only interested in Gaia entities that can attack. | ||||
// This takes entity sizes into accounts, so no need to compensate for structure size. | // This takes entity sizes into accounts, so no need to compensate for structure size. | ||||
this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( | this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( | ||||
this.entity, range.min, range.max, range.elevationBonus, | this.entity, range.min, range.max, range.elevationBonus, | ||||
[0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); | [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); | ||||
cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); | cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); | ||||
Show All 17 Lines | msg.added = msg.added.filter(e => { | ||||
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); | ||||
}); | }); | ||||
} | } | ||||
else if (msg.tag != this.enemyUnitsQuery) | else if (msg.tag != this.enemyUnitsQuery) | ||||
return; | return; | ||||
// Add new targets. | // Add new targets. | ||||
for (let entity of msg.added) | for (let entity of msg.added) | ||||
if (cmpAttack.CanAttack(entity)) | if (cmpAttack.CanAttack(entity, false)) | ||||
this.targetUnits.push(entity); | this.targetUnits.push(entity); | ||||
// Remove targets outside of vision-range. | // Remove targets outside of vision-range. | ||||
for (let entity of msg.removed) | for (let entity of msg.removed) | ||||
{ | { | ||||
let index = this.targetUnits.indexOf(entity); | let index = this.targetUnits.indexOf(entity); | ||||
if (index > -1) | if (index > -1) | ||||
this.targetUnits.splice(index, 1); | this.targetUnits.splice(index, 1); | ||||
} | } | ||||
if (this.targetUnits.length) | if (this.targetUnits.length) | ||||
this.StartTimer(); | this.StartTimer(); | ||||
}; | }; | ||||
BuildingAI.prototype.StartTimer = function() | BuildingAI.prototype.StartTimer = function() | ||||
{ | { | ||||
if (this.timer) | if (this.timer) | ||||
return; | return; | ||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); | ||||
if (!cmpAttack) | if (!cmpAttack) | ||||
return; | return; | ||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
var attackTimers = cmpAttack.GetTimers(attackType); | // Take the timer of the first attackType. This is a hack | ||||
// TODO: We should properly implement the timer and design what we | |||||
// actually want. See #4000. | |||||
var attackTimers = cmpAttack.GetTimers(cmpAttack.GetAttackTypes()[0]); | |||||
this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", | this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", | ||||
attackTimers.prepare, attackTimers.repeat / roundCount, null); | attackTimers.prepare, attackTimers.repeat / roundCount, null); | ||||
}; | }; | ||||
BuildingAI.prototype.GetDefaultArrowCount = function() | BuildingAI.prototype.GetDefaultArrowCount = function() | ||||
{ | { | ||||
var arrowCount = +this.template.DefaultArrowCount; | var arrowCount = +this.template.DefaultArrowCount; | ||||
▲ Show 20 Lines • Show All 100 Lines • ▼ Show 20 Lines | BuildingAI.prototype.FireArrows = function() | ||||
}; | }; | ||||
// Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ. | // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ. | ||||
if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) | if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) | ||||
addTarget(this.unitAITarget); | addTarget(this.unitAITarget); | ||||
for (let target of this.targetUnits) | for (let target of this.targetUnits) | ||||
addTarget(target); | addTarget(target); | ||||
// The obstruction manager performs approximate range checks. | |||||
// so we need to verify them here. | |||||
// TODO: perhaps an optional 'precise' mode to range queries would be more performant. | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
let range = cmpAttack.GetRange(attackType); | |||||
let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!thisCmpPosition.IsInWorld()) | |||||
return; | |||||
let s = thisCmpPosition.GetPosition(); | |||||
let firedArrows = 0; | let firedArrows = 0; | ||||
while (firedArrows < arrowsToFire && targets.length()) | while (firedArrows < arrowsToFire && targets.length()) | ||||
{ | { | ||||
let selectedTarget = targets.randomItem(); | let selectedTarget = targets.randomItem(); | ||||
if (this.CheckTargetVisible(selectedTarget)) | |||||
let targetCmpPosition = Engine.QueryInterface(selectedTarget, IID_Position); | |||||
if (targetCmpPosition && targetCmpPosition.IsInWorld() && this.CheckTargetVisible(selectedTarget)) | |||||
{ | { | ||||
// Parabolic range compuation is the same as in UnitAI's MoveToTargetAttackRange. | // The obstruction manager performs approximate range checks. | ||||
// h is positive when I'm higher than the target. | // so we need to verify them here. Hence the second argument. | ||||
let h = s.y - targetCmpPosition.GetPosition().y + range.elevationBonus; | // TODO: perhaps an optional 'precise' mode to range queries would be more performant. | ||||
if (h > -range.max / 2 && cmpObstructionManager.IsInTargetRange( | const attackType = cmpAttack.GetBestAttackAgainst(selectedTarget, true); | ||||
this.entity, | if (attackType) | ||||
selectedTarget, | |||||
range.min, | |||||
Math.sqrt(Math.square(range.max) + 2 * range.max * h), false)) | |||||
{ | { | ||||
cmpAttack.PerformAttack(attackType, selectedTarget); | cmpAttack.PerformAttack(attackType, selectedTarget); | ||||
PlaySound("attack_" + attackType.toLowerCase(), this.entity); | PlaySound("attack_" + attackType.toLowerCase(), this.entity); | ||||
++firedArrows; | ++firedArrows; | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
Show All 28 Lines |
Wildfire Games · Phabricator