2020-05-07 08:03:18 -07:00
|
|
|
// Number of rounds of firing per 2 seconds.
|
2024-12-21 13:10:00 -08:00
|
|
|
const roundCount = 20;
|
2013-08-16 04:17:36 -07:00
|
|
|
const attackType = "Ranged";
|
2010-11-21 10:41:13 -08:00
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
function BuildingAI() {}
|
|
|
|
|
|
2015-08-29 19:49:49 -07:00
|
|
|
BuildingAI.prototype.Schema =
|
2010-11-16 23:30:25 -08:00
|
|
|
"<element name='DefaultArrowCount'>" +
|
|
|
|
|
"<data type='nonNegativeInteger'/>" +
|
|
|
|
|
"</element>" +
|
2016-03-13 10:02:18 -07:00
|
|
|
"<optional>" +
|
|
|
|
|
"<element name='MaxArrowCount' a:help='Limit the number of arrows to a certain amount'>" +
|
|
|
|
|
"<data type='nonNegativeInteger'/>" +
|
|
|
|
|
"</element>" +
|
|
|
|
|
"</optional>" +
|
2010-11-16 23:30:25 -08:00
|
|
|
"<element name='GarrisonArrowMultiplier'>" +
|
|
|
|
|
"<ref name='nonNegativeDecimal'/>" +
|
2013-08-16 04:17:36 -07:00
|
|
|
"</element>" +
|
2016-03-13 10:02:18 -07:00
|
|
|
"<element name='GarrisonArrowClasses' a:help='Add extra arrows for this class list'>" +
|
2013-08-16 04:17:36 -07:00
|
|
|
"<text/>" +
|
2010-11-16 23:30:25 -08:00
|
|
|
"</element>";
|
|
|
|
|
|
2013-10-16 12:08:01 -07:00
|
|
|
BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2;
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
BuildingAI.prototype.Init = function()
|
|
|
|
|
{
|
2015-03-30 02:25:20 -07:00
|
|
|
this.currentRound = 0;
|
2016-03-13 10:02:18 -07:00
|
|
|
this.archersGarrisoned = 0;
|
2015-03-30 02:25:20 -07:00
|
|
|
this.arrowsLeft = 0;
|
|
|
|
|
this.targetUnits = [];
|
2024-12-21 13:10:00 -08:00
|
|
|
this.focusTargets = [];
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2021-03-26 03:18:30 -07:00
|
|
|
BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
|
2016-03-13 10:02:18 -07:00
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const classes = this.template.GarrisonArrowClasses;
|
|
|
|
|
for (const ent of msg.added)
|
2016-03-13 10:02:18 -07:00
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
2021-03-26 03:18:30 -07:00
|
|
|
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
2020-07-20 03:51:14 -07:00
|
|
|
++this.archersGarrisoned;
|
2016-03-13 10:02:18 -07:00
|
|
|
}
|
2025-05-11 01:39:08 -07:00
|
|
|
for (const ent of msg.removed)
|
2021-03-26 03:18:30 -07:00
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
2021-03-26 03:18:30 -07:00
|
|
|
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
|
|
|
|
--this.archersGarrisoned;
|
|
|
|
|
}
|
2016-03-13 10:02:18 -07:00
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
BuildingAI.prototype.OnOwnershipChanged = function(msg)
|
|
|
|
|
{
|
2012-12-23 12:03:00 -08:00
|
|
|
this.targetUnits = [];
|
2024-12-21 13:10:00 -08:00
|
|
|
this.focusTargets = [];
|
2015-03-30 02:25:20 -07:00
|
|
|
this.SetupRangeQuery();
|
|
|
|
|
this.SetupGaiaRangeQuery();
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2012-11-02 10:20:32 -07:00
|
|
|
BuildingAI.prototype.OnDiplomacyChanged = function(msg)
|
|
|
|
|
{
|
2015-03-30 02:25:20 -07:00
|
|
|
if (!IsOwnedByPlayer(msg.player, this.entity))
|
|
|
|
|
return;
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Remove maybe now allied/neutral units.
|
2015-03-30 02:25:20 -07:00
|
|
|
this.targetUnits = [];
|
|
|
|
|
this.SetupRangeQuery();
|
|
|
|
|
this.SetupGaiaRangeQuery();
|
2012-11-02 10:20:32 -07:00
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
BuildingAI.prototype.OnDestroy = function()
|
|
|
|
|
{
|
|
|
|
|
if (this.timer)
|
|
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
2010-11-16 23:30:25 -08:00
|
|
|
cmpTimer.CancelTimer(this.timer);
|
|
|
|
|
this.timer = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Clean up range queries.
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
2010-11-16 23:30:25 -08:00
|
|
|
if (this.enemyUnitsQuery)
|
|
|
|
|
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
|
2012-08-07 22:32:53 -07:00
|
|
|
if (this.gaiaUnitsQuery)
|
|
|
|
|
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2015-03-30 02:25:20 -07:00
|
|
|
/**
|
2020-12-17 11:55:22 -08:00
|
|
|
* React on Attack value modifications, as it might influence the range.
|
2015-03-30 02:25:20 -07:00
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.OnValueModification = function(msg)
|
|
|
|
|
{
|
|
|
|
|
if (msg.component != "Attack")
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
this.targetUnits = [];
|
|
|
|
|
this.SetupRangeQuery();
|
|
|
|
|
this.SetupGaiaRangeQuery();
|
|
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
/**
|
2020-12-17 11:55:22 -08:00
|
|
|
* Setup the Range Query to detect units coming in & out of range.
|
2010-11-16 23:30:25 -08:00
|
|
|
*/
|
2015-03-30 02:25:20 -07:00
|
|
|
BuildingAI.prototype.SetupRangeQuery = function()
|
2010-11-16 23:30:25 -08:00
|
|
|
{
|
2013-08-16 04:17:36 -07:00
|
|
|
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
|
|
|
|
if (!cmpAttack)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-08-29 19:49:49 -07:00
|
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
2010-11-17 10:25:00 -08:00
|
|
|
if (this.enemyUnitsQuery)
|
2012-11-02 10:20:32 -07:00
|
|
|
{
|
2010-11-17 10:25:00 -08:00
|
|
|
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
|
2012-11-02 10:20:32 -07:00
|
|
|
this.enemyUnitsQuery = undefined;
|
|
|
|
|
}
|
2015-03-30 02:25:20 -07:00
|
|
|
|
2023-06-18 23:33:33 -07:00
|
|
|
const cmpDiplomacy = QueryOwnerInterface(this.entity, IID_Diplomacy);
|
|
|
|
|
if (!cmpDiplomacy)
|
2015-03-30 02:25:20 -07:00
|
|
|
return;
|
|
|
|
|
|
2023-06-18 23:33:33 -07:00
|
|
|
const enemies = cmpDiplomacy.GetEnemies();
|
2020-12-17 11:55:22 -08:00
|
|
|
// Remove gaia.
|
2016-08-27 08:28:02 -07:00
|
|
|
if (enemies.length && enemies[0] == 0)
|
2020-12-17 11:55:22 -08:00
|
|
|
enemies.shift();
|
2013-12-02 17:01:12 -08:00
|
|
|
|
2016-02-22 08:44:57 -08:00
|
|
|
if (!enemies.length)
|
2015-08-29 19:49:49 -07:00
|
|
|
return;
|
|
|
|
|
|
Rename "ElevationBonus" and "Delay" to "Origin" and "EffectDelay", respectively.
`ElevationBonus` is vague, as discussions proved. Therefore it is
renamed to `Origin`, which, describes better what the value stands for.
`Delay` is also quite vague, so renamed to `EffectDelay`.
Differential revision: https://code.wildfiregames.com/D2016
Comments by: @bb, @nani, @Nescio, @Silier, @Stan, @wraitii
This was SVN commit r26074.
2021-12-14 23:42:06 -08:00
|
|
|
const range = cmpAttack.GetRange(attackType);
|
|
|
|
|
const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
|
2026-05-28 07:27:45 -07:00
|
|
|
|
|
|
|
|
// Get building's vision range
|
|
|
|
|
const cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
|
|
|
|
const visionRange = cmpVision ? cmpVision.GetRange() : 0;
|
|
|
|
|
|
|
|
|
|
// Base range
|
|
|
|
|
const baseRange = Math.min(visionRange, range.max);
|
|
|
|
|
|
2021-01-23 10:57:46 -08:00
|
|
|
// This takes entity sizes into accounts, so no need to compensate for structure size.
|
2016-02-22 08:44:57 -08:00
|
|
|
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
|
2026-05-28 07:27:45 -07:00
|
|
|
this.entity, range.min, range.max, baseRange, yOrigin,
|
|
|
|
|
enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal"),
|
|
|
|
|
true // Allow mirages for attack queries
|
|
|
|
|
);
|
2016-02-22 08:44:57 -08:00
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2012-08-07 22:32:53 -07:00
|
|
|
// Set up a range query for Gaia units within LOS range which can be attacked.
|
|
|
|
|
// This should be called whenever our ownership changes.
|
|
|
|
|
BuildingAI.prototype.SetupGaiaRangeQuery = function()
|
|
|
|
|
{
|
2013-08-16 04:17:36 -07:00
|
|
|
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
|
|
|
|
if (!cmpAttack)
|
|
|
|
|
return;
|
2012-08-07 22:32:53 -07:00
|
|
|
|
2015-08-29 19:49:49 -07:00
|
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
2012-08-07 22:32:53 -07:00
|
|
|
if (this.gaiaUnitsQuery)
|
|
|
|
|
{
|
2013-08-16 04:17:36 -07:00
|
|
|
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
|
2012-08-07 22:32:53 -07:00
|
|
|
this.gaiaUnitsQuery = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-18 23:33:33 -07:00
|
|
|
if (!QueryOwnerInterface(this.entity, IID_Diplomacy)?.IsEnemy(0))
|
2012-08-07 22:32:53 -07:00
|
|
|
return;
|
|
|
|
|
|
Rename "ElevationBonus" and "Delay" to "Origin" and "EffectDelay", respectively.
`ElevationBonus` is vague, as discussions proved. Therefore it is
renamed to `Origin`, which, describes better what the value stands for.
`Delay` is also quite vague, so renamed to `EffectDelay`.
Differential revision: https://code.wildfiregames.com/D2016
Comments by: @bb, @nani, @Nescio, @Silier, @Stan, @wraitii
This was SVN commit r26074.
2021-12-14 23:42:06 -08:00
|
|
|
const range = cmpAttack.GetRange(attackType);
|
|
|
|
|
const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
|
2012-08-07 22:32:53 -07:00
|
|
|
|
2026-05-28 07:27:45 -07:00
|
|
|
// Get building's vision range
|
|
|
|
|
const cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
|
|
|
|
|
const visionRange = cmpVision ? cmpVision.GetRange() : 0;
|
|
|
|
|
|
|
|
|
|
// Base range
|
|
|
|
|
const baseRange = Math.min(visionRange, range.max);
|
|
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
// This query is only interested in Gaia entities that can attack.
|
2021-01-23 10:57:46 -08:00
|
|
|
// This takes entity sizes into accounts, so no need to compensate for structure size.
|
2016-02-22 08:44:57 -08:00
|
|
|
this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
|
2026-05-28 07:27:45 -07:00
|
|
|
this.entity, range.min, range.max, baseRange, yOrigin,
|
2020-12-17 11:55:22 -08:00
|
|
|
[0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal"));
|
2016-02-22 08:44:57 -08:00
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery);
|
2012-08-07 22:32:53 -07:00
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
/**
|
2020-12-17 11:55:22 -08:00
|
|
|
* Called when units enter or leave range.
|
2010-11-16 23:30:25 -08:00
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.OnRangeUpdate = function(msg)
|
|
|
|
|
{
|
2013-08-16 04:17:36 -07:00
|
|
|
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
|
|
|
|
if (!cmpAttack)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Target enemy units except non-dangerous animals.
|
2012-08-07 22:32:53 -07:00
|
|
|
if (msg.tag == this.gaiaUnitsQuery)
|
|
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
msg.added = msg.added.filter(e =>
|
|
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
|
2016-02-22 08:44:57 -08:00
|
|
|
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal());
|
|
|
|
|
});
|
2012-08-07 22:32:53 -07:00
|
|
|
}
|
|
|
|
|
else if (msg.tag != this.enemyUnitsQuery)
|
2011-07-07 16:07:36 -07:00
|
|
|
return;
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Add new targets.
|
2025-05-11 01:39:08 -07:00
|
|
|
for (const entity of msg.added)
|
2026-05-28 07:27:45 -07:00
|
|
|
if (!this.targetUnits.includes(entity))
|
2016-02-22 08:44:57 -08:00
|
|
|
this.targetUnits.push(entity);
|
|
|
|
|
|
2026-05-28 07:27:45 -07:00
|
|
|
// Remove targets out of range.
|
2025-05-11 01:39:08 -07:00
|
|
|
for (const entity of msg.removed)
|
2010-11-17 10:25:00 -08:00
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const index = this.targetUnits.indexOf(entity);
|
2016-02-22 08:44:57 -08:00
|
|
|
if (index > -1)
|
|
|
|
|
this.targetUnits.splice(index, 1);
|
2010-11-17 10:25:00 -08:00
|
|
|
}
|
2013-12-02 17:01:12 -08:00
|
|
|
|
2015-04-25 05:54:34 -07:00
|
|
|
if (this.targetUnits.length)
|
|
|
|
|
this.StartTimer();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BuildingAI.prototype.StartTimer = function()
|
|
|
|
|
{
|
|
|
|
|
if (this.timer)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
|
|
|
|
if (!cmpAttack)
|
2013-08-16 04:17:36 -07:00
|
|
|
return;
|
2013-12-02 17:01:12 -08:00
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
|
|
|
|
var attackTimers = cmpAttack.GetTimers(attackType);
|
2016-02-22 08:44:57 -08:00
|
|
|
|
|
|
|
|
this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows",
|
|
|
|
|
attackTimers.prepare, attackTimers.repeat / roundCount, null);
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2012-04-26 14:58:05 -07:00
|
|
|
BuildingAI.prototype.GetDefaultArrowCount = function()
|
|
|
|
|
{
|
2012-05-04 15:51:14 -07:00
|
|
|
var arrowCount = +this.template.DefaultArrowCount;
|
2017-05-10 06:04:35 -07:00
|
|
|
return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity));
|
2012-04-26 14:58:05 -07:00
|
|
|
};
|
|
|
|
|
|
2016-03-13 10:02:18 -07:00
|
|
|
BuildingAI.prototype.GetMaxArrowCount = function()
|
|
|
|
|
{
|
|
|
|
|
if (!this.template.MaxArrowCount)
|
2016-03-24 06:25:47 -07:00
|
|
|
return Infinity;
|
2016-03-13 10:02:18 -07:00
|
|
|
|
2025-05-11 01:39:08 -07:00
|
|
|
const maxArrowCount = +this.template.MaxArrowCount;
|
2016-03-13 10:02:18 -07:00
|
|
|
return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity));
|
|
|
|
|
};
|
|
|
|
|
|
2012-04-26 14:58:05 -07:00
|
|
|
BuildingAI.prototype.GetGarrisonArrowMultiplier = function()
|
|
|
|
|
{
|
2012-05-04 15:51:14 -07:00
|
|
|
var arrowMult = +this.template.GarrisonArrowMultiplier;
|
2013-10-15 03:05:08 -07:00
|
|
|
return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity);
|
2012-04-26 14:58:05 -07:00
|
|
|
};
|
|
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
BuildingAI.prototype.GetGarrisonArrowClasses = function()
|
|
|
|
|
{
|
2013-08-16 05:17:18 -07:00
|
|
|
var string = this.template.GarrisonArrowClasses;
|
|
|
|
|
if (string)
|
|
|
|
|
return string.split(/\s+/);
|
|
|
|
|
return [];
|
2013-08-16 04:17:36 -07:00
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
/**
|
|
|
|
|
* Returns the number of arrows which needs to be fired.
|
2020-12-17 11:55:22 -08:00
|
|
|
* DefaultArrowCount + Garrisoned Archers (i.e., any unit capable
|
|
|
|
|
* of shooting arrows from inside buildings).
|
2010-11-16 23:30:25 -08:00
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.GetArrowCount = function()
|
|
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
const count = this.GetDefaultArrowCount() +
|
2016-03-23 02:04:02 -07:00
|
|
|
Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier());
|
2016-02-22 08:44:57 -08:00
|
|
|
|
2016-03-24 06:25:47 -07:00
|
|
|
return Math.min(count, this.GetMaxArrowCount());
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2015-04-25 05:54:34 -07:00
|
|
|
BuildingAI.prototype.SetUnitAITarget = function(ent)
|
|
|
|
|
{
|
|
|
|
|
this.unitAITarget = ent;
|
|
|
|
|
if (ent)
|
|
|
|
|
this.StartTimer();
|
|
|
|
|
};
|
|
|
|
|
|
2024-12-21 13:10:00 -08:00
|
|
|
/**
|
|
|
|
|
* Adds index to keep track of the user-targeted units supporting a queue
|
|
|
|
|
* @param {ent} - Target of focus-fire from unit-actions if the selection is an enemy.
|
|
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.AddFocusTarget = function(ent, queued, push)
|
|
|
|
|
{
|
|
|
|
|
if (!ent || this.targetUnits.indexOf(ent) === -1)
|
|
|
|
|
return;
|
|
|
|
|
if (queued)
|
2025-05-03 08:16:41 -07:00
|
|
|
this.focusTargets.push({ "entityId": ent });
|
2024-12-21 13:10:00 -08:00
|
|
|
else if (push)
|
2025-05-03 08:16:41 -07:00
|
|
|
this.focusTargets.unshift({ "entityId": ent });
|
2024-12-21 13:10:00 -08:00
|
|
|
else
|
2025-05-03 08:16:41 -07:00
|
|
|
this.focusTargets = [{ "entityId": ent }];
|
2024-12-21 13:10:00 -08:00
|
|
|
};
|
|
|
|
|
|
2010-11-16 23:30:25 -08:00
|
|
|
/**
|
2016-02-22 08:44:57 -08:00
|
|
|
* Fire arrows with random temporal distribution on prefered targets.
|
|
|
|
|
* Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range.
|
2010-11-16 23:30:25 -08:00
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.FireArrows = function()
|
|
|
|
|
{
|
2015-04-25 05:54:34 -07:00
|
|
|
if (!this.targetUnits.length && !this.unitAITarget)
|
2013-08-16 04:17:36 -07:00
|
|
|
{
|
2015-03-30 02:25:20 -07:00
|
|
|
if (!this.timer)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
2015-03-30 02:25:20 -07:00
|
|
|
cmpTimer.CancelTimer(this.timer);
|
|
|
|
|
this.timer = undefined;
|
2013-08-16 04:17:36 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
2013-08-16 04:17:36 -07:00
|
|
|
if (!cmpAttack)
|
|
|
|
|
return;
|
2013-12-02 17:01:12 -08:00
|
|
|
|
2016-02-22 08:44:57 -08:00
|
|
|
if (this.currentRound > roundCount - 1)
|
2013-08-16 04:17:36 -07:00
|
|
|
this.currentRound = 0;
|
2015-08-29 19:49:49 -07:00
|
|
|
|
2013-08-16 04:17:36 -07:00
|
|
|
if (this.currentRound == 0)
|
|
|
|
|
this.arrowsLeft = this.GetArrowCount();
|
2015-08-29 19:49:49 -07:00
|
|
|
|
2025-07-27 05:40:15 -07:00
|
|
|
let arrowsToFire;
|
2016-02-22 08:44:57 -08:00
|
|
|
if (this.currentRound == roundCount - 1)
|
2013-08-16 04:17:36 -07:00
|
|
|
arrowsToFire = this.arrowsLeft;
|
|
|
|
|
else
|
2015-08-29 19:49:49 -07:00
|
|
|
arrowsToFire = Math.min(
|
2024-12-21 13:10:00 -08:00
|
|
|
// shooting arrows in the first quarter of rounds results in a burst.
|
|
|
|
|
this.GetArrowCount() / (roundCount / 4),
|
|
|
|
|
this.arrowsLeft
|
2013-08-16 04:17:36 -07:00
|
|
|
);
|
2016-02-22 08:44:57 -08:00
|
|
|
|
2013-09-11 10:50:49 -07:00
|
|
|
if (arrowsToFire <= 0)
|
|
|
|
|
{
|
2016-02-22 08:44:57 -08:00
|
|
|
++this.currentRound;
|
2013-09-11 10:50:49 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2015-04-25 05:54:34 -07:00
|
|
|
|
2024-12-21 13:10:00 -08:00
|
|
|
// Add targets to a list.
|
2025-05-03 00:37:00 -07:00
|
|
|
let targets = [];
|
2025-05-11 01:39:08 -07:00
|
|
|
const addTarget = function(target)
|
2013-09-11 10:50:49 -07:00
|
|
|
{
|
2025-05-30 12:33:49 -07:00
|
|
|
const pref = (cmpAttack.GetPreference(target) ?? 49);
|
|
|
|
|
targets.push({ "entityId": target, "preference": pref });
|
2015-04-25 05:54:34 -07:00
|
|
|
};
|
2016-02-22 08:44:57 -08:00
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ.
|
2015-04-25 05:54:34 -07:00
|
|
|
if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1)
|
|
|
|
|
addTarget(this.unitAITarget);
|
2024-12-21 13:10:00 -08:00
|
|
|
|
|
|
|
|
else if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) != -1)
|
2025-05-03 08:16:41 -07:00
|
|
|
this.focusTargets = [{ "entityId": this.unitAITarget }];
|
2024-12-21 13:10:00 -08:00
|
|
|
|
|
|
|
|
if (!this.focusTargets.length)
|
|
|
|
|
{
|
2025-05-11 01:39:08 -07:00
|
|
|
for (const target of this.targetUnits)
|
2025-05-30 12:33:49 -07:00
|
|
|
addTarget(target);
|
2024-12-21 13:10:00 -08:00
|
|
|
// Sort targets by preference and then by proximity.
|
2025-12-30 00:57:37 -08:00
|
|
|
targets.sort((a, b) =>
|
|
|
|
|
{
|
2024-12-21 13:10:00 -08:00
|
|
|
if (a.preference > b.preference)
|
|
|
|
|
return 1;
|
|
|
|
|
else if (a.preference < b.preference)
|
|
|
|
|
return -1;
|
|
|
|
|
else if (PositionHelper.DistanceBetweenEntities(this.entity, a.entityId) > PositionHelper.DistanceBetweenEntities(this.entity, b.entityId))
|
|
|
|
|
return 1;
|
2025-05-03 00:37:00 -07:00
|
|
|
return -1;
|
|
|
|
|
});
|
2024-12-21 13:10:00 -08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
targets = this.focusTargets;
|
2015-04-25 05:54:34 -07:00
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// The obstruction manager performs approximate range checks.
|
2020-11-19 06:07:24 -08:00
|
|
|
// so we need to verify them here.
|
|
|
|
|
// TODO: perhaps an optional 'precise' mode to range queries would be more performant.
|
2022-02-15 12:31:55 -08:00
|
|
|
const cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
|
Rename "ElevationBonus" and "Delay" to "Origin" and "EffectDelay", respectively.
`ElevationBonus` is vague, as discussions proved. Therefore it is
renamed to `Origin`, which, describes better what the value stands for.
`Delay` is also quite vague, so renamed to `EffectDelay`.
Differential revision: https://code.wildfiregames.com/D2016
Comments by: @bb, @nani, @Nescio, @Silier, @Stan, @wraitii
This was SVN commit r26074.
2021-12-14 23:42:06 -08:00
|
|
|
const range = cmpAttack.GetRange(attackType);
|
2022-02-15 12:31:55 -08:00
|
|
|
const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
|
2020-11-19 06:07:24 -08:00
|
|
|
|
2025-05-03 00:37:00 -07:00
|
|
|
let firedArrows = 0;
|
2025-05-03 08:30:55 -07:00
|
|
|
let targetIndex = 0;
|
2024-12-21 13:10:00 -08:00
|
|
|
while (firedArrows < arrowsToFire && targetIndex < targets.length)
|
2025-05-03 00:37:00 -07:00
|
|
|
{
|
2024-12-21 13:10:00 -08:00
|
|
|
|
2025-05-11 01:39:08 -07:00
|
|
|
const selectedTarget = targets[targetIndex].entityId;
|
2026-05-28 07:27:45 -07:00
|
|
|
if (cmpAttack.CanAttack(selectedTarget, [attackType]))
|
2010-11-21 10:41:13 -08:00
|
|
|
{
|
2022-02-15 12:31:55 -08:00
|
|
|
cmpAttack.PerformAttack(attackType, selectedTarget);
|
|
|
|
|
PlaySound("attack_" + attackType.toLowerCase(), this.entity);
|
|
|
|
|
++firedArrows;
|
2010-11-21 10:41:13 -08:00
|
|
|
}
|
2024-12-21 13:10:00 -08:00
|
|
|
else
|
|
|
|
|
++targetIndex;// Could not attack target, try a different target.
|
2010-11-16 23:30:25 -08:00
|
|
|
}
|
2024-12-21 13:10:00 -08:00
|
|
|
targets.splice(0, targetIndex);
|
2020-12-17 11:55:22 -08:00
|
|
|
this.arrowsLeft -= firedArrows;
|
|
|
|
|
++this.currentRound;
|
2010-11-16 23:30:25 -08:00
|
|
|
};
|
|
|
|
|
|
2013-08-03 12:20:20 -07:00
|
|
|
/**
|
|
|
|
|
* Returns true if the target entity is visible through the FoW/SoD.
|
|
|
|
|
*/
|
|
|
|
|
BuildingAI.prototype.CheckTargetVisible = function(target)
|
|
|
|
|
{
|
|
|
|
|
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
|
|
|
if (!cmpOwnership)
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Entities that are hidden and miraged are considered visible.
|
2014-08-04 15:49:19 -07:00
|
|
|
var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
|
|
|
|
|
if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
|
|
|
|
|
return true;
|
|
|
|
|
|
2020-12-17 11:55:22 -08:00
|
|
|
// Either visible directly, or visible in fog.
|
2025-05-11 01:39:08 -07:00
|
|
|
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
2015-11-28 00:27:38 -08:00
|
|
|
return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden";
|
2013-08-03 12:20:20 -07:00
|
|
|
};
|
|
|
|
|
|
2011-07-07 16:07:36 -07:00
|
|
|
Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI);
|