restore random arrows.

This commit is contained in:
rca 2024-06-04 22:10:51 -07:00 committed by rts
parent ce2febaeba
commit e8ba5dffcf
11 changed files with 1093 additions and 3319 deletions

File diff suppressed because it is too large Load Diff

View File

@ -22,42 +22,34 @@ BuildingAI.prototype.Schema =
BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2;
BuildingAI.prototype.Init = function() BuildingAI.prototype.Init = function () {
{
this.currentRound = 0; this.currentRound = 0;
this.archersGarrisoned = 0; this.archersGarrisoned = 0;
this.arrowsLeft = 0; this.arrowsLeft = 0;
this.targetUnits = []; this.targetUnits = [];
this.focusTargets = [];
}; };
BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) BuildingAI.prototype.OnGarrisonedUnitsChanged = function (msg) {
{
let classes = this.template.GarrisonArrowClasses; let classes = this.template.GarrisonArrowClasses;
for (let ent of msg.added) for (let ent of msg.added) {
{
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes)) if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
++this.archersGarrisoned; ++this.archersGarrisoned;
} }
for (let ent of msg.removed) for (let ent of msg.removed) {
{
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes)) if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
--this.archersGarrisoned; --this.archersGarrisoned;
} }
}; };
BuildingAI.prototype.OnOwnershipChanged = function(msg) BuildingAI.prototype.OnOwnershipChanged = function (msg) {
{
this.targetUnits = []; this.targetUnits = [];
this.focusTargets = [];
this.SetupRangeQuery(); this.SetupRangeQuery();
this.SetupGaiaRangeQuery(); this.SetupGaiaRangeQuery();
}; };
BuildingAI.prototype.OnDiplomacyChanged = function(msg) BuildingAI.prototype.OnDiplomacyChanged = function (msg) {
{
if (!IsOwnedByPlayer(msg.player, this.entity)) if (!IsOwnedByPlayer(msg.player, this.entity))
return; return;
@ -67,10 +59,8 @@ BuildingAI.prototype.OnDiplomacyChanged = function(msg)
this.SetupGaiaRangeQuery(); this.SetupGaiaRangeQuery();
}; };
BuildingAI.prototype.OnDestroy = function() BuildingAI.prototype.OnDestroy = function () {
{ if (this.timer) {
if (this.timer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer); cmpTimer.CancelTimer(this.timer);
this.timer = undefined; this.timer = undefined;
@ -87,8 +77,7 @@ BuildingAI.prototype.OnDestroy = function()
/** /**
* React on Attack value modifications, as it might influence the range. * React on Attack value modifications, as it might influence the range.
*/ */
BuildingAI.prototype.OnValueModification = function(msg) BuildingAI.prototype.OnValueModification = function (msg) {
{
if (msg.component != "Attack") if (msg.component != "Attack")
return; return;
@ -100,15 +89,13 @@ BuildingAI.prototype.OnValueModification = function(msg)
/** /**
* Setup the Range Query to detect units coming in & out of range. * Setup the Range Query to detect units coming in & out of range.
*/ */
BuildingAI.prototype.SetupRangeQuery = function() BuildingAI.prototype.SetupRangeQuery = function () {
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack) if (!cmpAttack)
return; return;
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (this.enemyUnitsQuery) if (this.enemyUnitsQuery) {
{
cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery);
this.enemyUnitsQuery = undefined; this.enemyUnitsQuery = undefined;
} }
@ -137,15 +124,13 @@ BuildingAI.prototype.SetupRangeQuery = function()
// Set up a range query for Gaia units within LOS range which can be attacked. // Set up a range query for Gaia units within LOS range which can be attacked.
// This should be called whenever our ownership changes. // This should be called whenever our ownership changes.
BuildingAI.prototype.SetupGaiaRangeQuery = function() BuildingAI.prototype.SetupGaiaRangeQuery = function () {
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack) if (!cmpAttack)
return; return;
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (this.gaiaUnitsQuery) if (this.gaiaUnitsQuery) {
{
cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery);
this.gaiaUnitsQuery = undefined; this.gaiaUnitsQuery = undefined;
} }
@ -169,16 +154,14 @@ BuildingAI.prototype.SetupGaiaRangeQuery = function()
/** /**
* Called when units enter or leave range. * Called when units enter or leave range.
*/ */
BuildingAI.prototype.OnRangeUpdate = function(msg) BuildingAI.prototype.OnRangeUpdate = function (msg) {
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack) if (!cmpAttack)
return; return;
// Target enemy units except non-dangerous animals. // Target enemy units except non-dangerous animals.
if (msg.tag == this.gaiaUnitsQuery) if (msg.tag == this.gaiaUnitsQuery) {
{
msg.added = msg.added.filter(e => { msg.added = msg.added.filter(e => {
let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal());
@ -193,8 +176,7 @@ BuildingAI.prototype.OnRangeUpdate = function(msg)
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);
@ -204,8 +186,7 @@ BuildingAI.prototype.OnRangeUpdate = function(msg)
this.StartTimer(); this.StartTimer();
}; };
BuildingAI.prototype.StartTimer = function() BuildingAI.prototype.StartTimer = function () {
{
if (this.timer) if (this.timer)
return; return;
@ -220,14 +201,12 @@ BuildingAI.prototype.StartTimer = function()
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;
return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity)); return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity));
}; };
BuildingAI.prototype.GetMaxArrowCount = function() BuildingAI.prototype.GetMaxArrowCount = function () {
{
if (!this.template.MaxArrowCount) if (!this.template.MaxArrowCount)
return Infinity; return Infinity;
@ -235,14 +214,12 @@ BuildingAI.prototype.GetMaxArrowCount = function()
return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity)); return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity));
}; };
BuildingAI.prototype.GetGarrisonArrowMultiplier = function() BuildingAI.prototype.GetGarrisonArrowMultiplier = function () {
{
var arrowMult = +this.template.GarrisonArrowMultiplier; var arrowMult = +this.template.GarrisonArrowMultiplier;
return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity);
}; };
BuildingAI.prototype.GetGarrisonArrowClasses = function() BuildingAI.prototype.GetGarrisonArrowClasses = function () {
{
var string = this.template.GarrisonArrowClasses; var string = this.template.GarrisonArrowClasses;
if (string) if (string)
return string.split(/\s+/); return string.split(/\s+/);
@ -254,45 +231,25 @@ BuildingAI.prototype.GetGarrisonArrowClasses = function()
* DefaultArrowCount + Garrisoned Archers (i.e., any unit capable * DefaultArrowCount + Garrisoned Archers (i.e., any unit capable
* of shooting arrows from inside buildings). * of shooting arrows from inside buildings).
*/ */
BuildingAI.prototype.GetArrowCount = function() BuildingAI.prototype.GetArrowCount = function () {
{
let count = this.GetDefaultArrowCount() + let count = this.GetDefaultArrowCount() +
Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier());
return Math.min(count, this.GetMaxArrowCount()); return Math.min(count, this.GetMaxArrowCount());
}; };
BuildingAI.prototype.SetUnitAITarget = function(ent) BuildingAI.prototype.SetUnitAITarget = function (ent) {
{
this.unitAITarget = ent; this.unitAITarget = ent;
if (ent) if (ent)
this.StartTimer(); this.StartTimer();
}; };
/**
* Adds index to keep track of the user-targeted units supporting a queue
* @param {ent} - Target of rallypoint selection when selection is an enemy unit from unit_actions.js
*/
BuildingAI.prototype.AddFocusTarget = function(ent, queued, push)
{
if (!ent || this.targetUnits.indexOf(ent) === -1)
return;
if (queued)
this.focusTargets.push({"entityId": ent});
else if (push)
this.focusTargets.unshift({"entityId": ent});
else
this.focusTargets = [{"entityId": ent}];
};
/** /**
* Fire arrows with random temporal distribution on prefered targets. * Fire arrows with random temporal distribution on prefered targets.
* Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range.
*/ */
BuildingAI.prototype.FireArrows = function() BuildingAI.prototype.FireArrows = function () {
{ if (!this.targetUnits.length && !this.unitAITarget) {
if (!this.targetUnits.length && !this.unitAITarget)
{
if (!this.timer) if (!this.timer)
return; return;
@ -321,38 +278,29 @@ BuildingAI.prototype.FireArrows = function()
this.arrowsLeft this.arrowsLeft
); );
if (arrowsToFire <= 0) if (arrowsToFire <= 0) {
{
++this.currentRound; ++this.currentRound;
return; return;
} }
// Add targets to a list. // Add targets to a weighted list, to allow preferences.
let targets = []; let targets = new WeightedList();
let addTarget = function(target) let maxPreference = this.MAX_PREFERENCE_BONUS;
{ let addTarget = function (target) {
const pref = (cmpAttack.GetPreference(target) ?? 49); let preference = cmpAttack.GetPreference(target);
targets.push({"entityId": target, "preference": pref}); let weight = 1;
if (preference !== null && preference !== undefined)
weight += maxPreference / (1 + preference);
targets.push(target, weight);
}; };
// 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);
else if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) != -1)
this.focusTargets = [{"entityId": this.unitAITarget}];
if (!this.focusTargets.length)
{
for (let target of this.targetUnits) for (let target of this.targetUnits)
addTarget(target); addTarget(target);
// Sort targets by preference and then by proximity.
targets.sort( (a,b) => {
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;
return -1;
});
}
else
targets = this.focusTargets;
// The obstruction manager performs approximate range checks. // The obstruction manager performs approximate range checks.
// so we need to verify them here. // so we need to verify them here.
@ -362,32 +310,25 @@ BuildingAI.prototype.FireArrows = function()
const yOrigin = cmpAttack.GetAttackYOrigin(attackType); const yOrigin = cmpAttack.GetAttackYOrigin(attackType);
let firedArrows = 0; let firedArrows = 0;
let killedTargets = 0; while (firedArrows < arrowsToFire && targets.length()) {
while (firedArrows < arrowsToFire && killedTargets < targets.length) const selectedTarget = targets.randomItem();
{
let selectedTarget = targets[killedTargets].entityId;
if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange(
this.entity, this.entity,
selectedTarget, selectedTarget,
range.min, range.min,
range.max, range.max,
yOrigin, yOrigin,
false)) 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;
} }
else
{
// Could not attack target, try a different target. // Could not attack target, try a different target.
++killedTargets; targets.remove(selectedTarget);
} }
}
targets.splice(0,killedTargets);
killedTargets = 0;//not sure if this is necessary
this.arrowsLeft -= firedArrows; this.arrowsLeft -= firedArrows;
++this.currentRound; ++this.currentRound;
}; };
@ -395,8 +336,7 @@ BuildingAI.prototype.FireArrows = function()
/** /**
* Returns true if the target entity is visible through the FoW/SoD. * Returns true if the target entity is visible through the FoW/SoD.
*/ */
BuildingAI.prototype.CheckTargetVisible = function(target) BuildingAI.prototype.CheckTargetVisible = function (target) {
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership) if (!cmpOwnership)
return false; return false;

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,6 @@
<GarrisonHolder> <GarrisonHolder>
<Max op="mul">1.5</Max> <Max op="mul">1.5</Max>
</GarrisonHolder> </GarrisonHolder>
<BuildingAI>
<DefaultArrowCount>10</DefaultArrowCount>
<MaxArrowCount>24</MaxArrowCount>
</BuildingAI>
<Health> <Health>
<Max op="mul">1.5</Max> <Max op="mul">1.5</Max>
</Health> </Health>
@ -34,7 +30,7 @@
</Technologies> </Technologies>
</Researcher> </Researcher>
<Trainer> <Trainer>
<BatchTimeModifier op="mul">0.75</BatchTimeModifier> <BatchTimeModifier op="mul">0.5</BatchTimeModifier>
<Entities datatype="tokens"> <Entities datatype="tokens">
units/{civ}/hero_han_xin_horse units/{civ}/hero_han_xin_horse
units/{civ}/hero_liu_bang_horse units/{civ}/hero_liu_bang_horse

View File

@ -6,7 +6,7 @@
</Ranged> </Ranged>
</Attack> </Attack>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>4</DefaultArrowCount> <DefaultArrowCount>2</DefaultArrowCount>
</BuildingAI> </BuildingAI>
<Cost> <Cost>
<BuildTime>200</BuildTime> <BuildTime>200</BuildTime>

View File

@ -14,10 +14,10 @@
</Damage> </Damage>
<MaxRange>60</MaxRange> <MaxRange>60</MaxRange>
<PrepareTime>1200</PrepareTime> <PrepareTime>1200</PrepareTime>
<RepeatTime>4000</RepeatTime> <RepeatTime>2000</RepeatTime>
<Projectile> <Projectile>
<Speed>100</Speed> <Speed>100</Speed>
<Spread>2</Spread> <Spread>1.5</Spread>
<Gravity>50</Gravity> <Gravity>50</Gravity>
<FriendlyFire>false</FriendlyFire> <FriendlyFire>false</FriendlyFire>
<LaunchPoint y="3"/> <LaunchPoint y="3"/>
@ -39,10 +39,9 @@
</Distance> </Distance>
</BuildRestrictions> </BuildRestrictions>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>8</DefaultArrowCount> <DefaultArrowCount>3</DefaultArrowCount>
<GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
<GarrisonArrowClasses>Soldier</GarrisonArrowClasses> <GarrisonArrowClasses>Soldier</GarrisonArrowClasses>
<MaxArrowCount>18</MaxArrowCount>
</BuildingAI> </BuildingAI>
<Capturable> <Capturable>
<CapturePoints>2500</CapturePoints> <CapturePoints>2500</CapturePoints>

View File

@ -9,8 +9,7 @@
</Distance> </Distance>
</BuildRestrictions> </BuildRestrictions>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>6</DefaultArrowCount> <DefaultArrowCount>1</DefaultArrowCount>
<MaxArrowCount>16</MaxArrowCount>
</BuildingAI> </BuildingAI>
<Cost> <Cost>
<BuildTime>300</BuildTime> <BuildTime>300</BuildTime>

View File

@ -9,7 +9,7 @@
<MaxRange>60</MaxRange> <MaxRange>60</MaxRange>
<MinRange>10</MinRange> <MinRange>10</MinRange>
<PrepareTime>1200</PrepareTime> <PrepareTime>1200</PrepareTime>
<RepeatTime>4000</RepeatTime> <RepeatTime>2000</RepeatTime>
<Projectile> <Projectile>
<Speed>100</Speed> <Speed>100</Speed>
<Spread>1.5</Spread> <Spread>1.5</Spread>
@ -26,7 +26,7 @@
</Ranged> </Ranged>
</Attack> </Attack>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>2</DefaultArrowCount> <DefaultArrowCount>1</DefaultArrowCount>
<GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
<GarrisonArrowClasses>Infantry</GarrisonArrowClasses> <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
</BuildingAI> </BuildingAI>

View File

@ -10,12 +10,12 @@
</Ranged> </Ranged>
</Attack> </Attack>
<BuildingAI> <BuildingAI>
<MaxArrowCount>5</MaxArrowCount> <MaxArrowCount>4</MaxArrowCount>
</BuildingAI> </BuildingAI>
<Cost> <Cost>
<BuildTime>40</BuildTime> <BuildTime>40</BuildTime>
<Resources> <Resources>
<wood>125</wood> <wood>100</wood>
</Resources> </Resources>
</Cost> </Cost>
<Footprint> <Footprint>
@ -37,7 +37,7 @@
<RequiredTechnology>phase_village</RequiredTechnology> <RequiredTechnology>phase_village</RequiredTechnology>
</Identity> </Identity>
<Loot> <Loot>
<wood>25</wood> <wood>20</wood>
</Loot> </Loot>
<Obstruction> <Obstruction>
<Static width="9.0" depth="7.5"/> <Static width="9.0" depth="7.5"/>
@ -59,7 +59,7 @@
<Tooltip>Reinforce with stone and upgrade to a defense tower.</Tooltip> <Tooltip>Reinforce with stone and upgrade to a defense tower.</Tooltip>
<RequiredTechnology>phase_town</RequiredTechnology> <RequiredTechnology>phase_town</RequiredTechnology>
<Cost> <Cost>
<wood>25</wood> <wood>50</wood>
<stone>100</stone> <stone>100</stone>
</Cost> </Cost>
<Variant>upgrading</Variant> <Variant>upgrading</Variant>

View File

@ -20,9 +20,6 @@
<Square width="10.0" depth="10.0"/> <Square width="10.0" depth="10.0"/>
<Height>15.0</Height> <Height>15.0</Height>
</Footprint> </Footprint>
<BuildingAI>
<DefaultArrowCount>3</DefaultArrowCount>
</BuildingAI>
<GarrisonHolder> <GarrisonHolder>
<Max>5</Max> <Max>5</Max>
</GarrisonHolder> </GarrisonHolder>

View File

@ -8,10 +8,10 @@
</Damage> </Damage>
<MaxRange>60</MaxRange> <MaxRange>60</MaxRange>
<PrepareTime>1200</PrepareTime> <PrepareTime>1200</PrepareTime>
<RepeatTime>4000</RepeatTime> <RepeatTime>2000</RepeatTime>
<Projectile> <Projectile>
<Speed>100</Speed> <Speed>100</Speed>
<Spread>2.5</Spread> <Spread>1.5</Spread>
<Gravity>50</Gravity> <Gravity>50</Gravity>
<FriendlyFire>false</FriendlyFire> <FriendlyFire>false</FriendlyFire>
<LaunchPoint y="3"/> <LaunchPoint y="3"/>
@ -32,7 +32,7 @@
</Distance> </Distance>
</BuildRestrictions> </BuildRestrictions>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>12</DefaultArrowCount> <DefaultArrowCount>4</DefaultArrowCount>
<GarrisonArrowMultiplier>1</GarrisonArrowMultiplier> <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
<GarrisonArrowClasses>Soldier</GarrisonArrowClasses> <GarrisonArrowClasses>Soldier</GarrisonArrowClasses>
</BuildingAI> </BuildingAI>