Add projectile count to template viewer & tooltips

This commit is contained in:
Atrik 2026-05-22 15:07:37 +02:00
parent 885bca7ebf
commit a20c1ad20d
4 changed files with 48 additions and 15 deletions

View file

@ -241,6 +241,9 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, modif
(2 * ret.attack[type].yOrigin + ret.attack[type].maxRange));
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
if (template.Attack[type].Projectile)
ret.attack[type].projectileCount = template.Attack[type].Projectile.Count ?
+template.Attack[type].Projectile.Count : 1;
if (template.Attack[type].Projectile)
ret.attack[type].friendlyFire = template.Attack[type].Projectile.FriendlyFire == "true";

View file

@ -305,7 +305,7 @@ function getStatusEffectsResistanceTooltip(resistanceTypeTemplate)
});
}
function attackRateDetails(interval, projectiles)
function attackRateDetails(interval, projectiles, isVolley = false)
{
if (!interval)
return "";
@ -313,26 +313,41 @@ function attackRateDetails(interval, projectiles)
if (projectiles === 0)
return translate("Garrison to fire arrows");
let attackRateString = getSecondsString(interval / 1000);
let header = headerFont(translate("Interval:"));
if (projectiles && +projectiles > 1)
// Volley attack (multiple projectiles fired simultaneously)
if (isVolley && projectiles > 1)
{
return sprintf(translate("%(label)s %(time)s, %(projectileLabel)s %(count)s %(unit)s"), {
"label": headerFont(translate("Interval:")),
"time": getSecondsString(interval / 1000),
"projectileLabel": headerFont(translate("Projectiles:")),
"count": projectiles,
"unit": unitFont(translate("per attack"))
});
}
// Garrison arrows (multiple projectiles from garrisoned units)
if (!isVolley && projectiles > 1)
{
header = headerFont(translate("Rate:"));
const projectileString = sprintf(translatePlural("%(projectileCount)s %(projectileName)s", "%(projectileCount)s %(projectileName)s", projectiles), {
"projectileCount": projectiles,
"projectileName": unitFont(translatePlural("arrow", "arrows", projectiles))
});
attackRateString = sprintf(translate("%(projectileString)s / %(attackRateString)s"), {
const rateString = sprintf(translate("%(projectileString)s / %(time)s"), {
"projectileString": projectileString,
"attackRateString": attackRateString
"time": getSecondsString(interval / 1000)
});
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Rate:")),
"details": rateString
});
}
return sprintf(translate("%(label)s %(details)s"), {
"label": header,
"details": attackRateString
// Single projectile
return sprintf(translate("%(label)s %(time)s"), {
"label": headerFont(translate("Interval:")),
"time": getSecondsString(interval / 1000)
});
}
@ -457,10 +472,13 @@ function getAttackTooltip(template)
"attackType": translateWithContext(attackTypeTemplate.attackName.context || "Name of an attack, usually the weapon.", attackTypeTemplate.attackName.name)
});
const isVolley = !!(attackTypeTemplate.projectileCount && attackTypeTemplate.projectileCount > 1);
// Get projectile count - either volley size or garrison arrows
let projectiles;
// Use either current rate from simulation or default count if the sim is not running.
// TODO: This ought to be extended to include units which fire multiple projectiles.
if (template.buildingAI)
if (isVolley)
projectiles = attackTypeTemplate.projectileCount;
else if (template.buildingAI)
projectiles = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount;
const splashTemplate = attackTypeTemplate.splash;
@ -476,7 +494,7 @@ function getAttackTooltip(template)
"attackLabel": attackLabel,
"effects": attackEffectsDetails(attackTypeTemplate),
"range": rangeDetails(attackTypeTemplate),
"rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles),
"rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles, isVolley),
"splash": splashTemplate ? "\n" + g_Indent + g_Indent + splashDetails(splashTemplate) : "",
"statusEffects": statusEffectsDetails
}));

View file

@ -228,6 +228,16 @@ Attack.prototype.GetAttackTypes = function(wantedTypes)
(!wantedTypesReal || !wantedTypesReal.length || wantedTypesReal.indexOf(type) != -1));
};
Attack.prototype.GetProjectileCount = function(type)
{
if (!this.template[type] || !this.template[type].Projectile)
return 1;
let count = this.template[type].Projectile.Count ? +this.template[type].Projectile.Count : 1;
count = ApplyValueModificationsToEntity("Attack/" + type + "/Projectile/Count", count, this.entity);
return Math.max(1, Math.floor(count));
};
Attack.prototype.GetPreferredClasses = function(type)
{
if (this.template[type] && this.template[type].PreferredClasses &&

View file

@ -491,6 +491,8 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
if (ret.attack[type].splash)
Object.assign(ret.attack[type].splash, cmpAttack.GetAttackEffectsData(type, true));
ret.attack[type].projectileCount = cmpAttack.GetProjectileCount(type);
const range = cmpAttack.GetRange(type);
ret.attack[type].minRange = range.min;
ret.attack[type].maxRange = range.max;