diff --git a/binaries/data/mods/public/globalscripts/Templates.js b/binaries/data/mods/public/globalscripts/Templates.js index 1a781fe547..539dfa4801 100644 --- a/binaries/data/mods/public/globalscripts/Templates.js +++ b/binaries/data/mods/public/globalscripts/Templates.js @@ -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"; diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js index 64deed2bbd..af24da67c5 100644 --- a/binaries/data/mods/public/gui/common/tooltips.js +++ b/binaries/data/mods/public/gui/common/tooltips.js @@ -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 })); diff --git a/binaries/data/mods/public/simulation/components/Attack.js b/binaries/data/mods/public/simulation/components/Attack.js index 4708318e45..ec6162b2b5 100644 --- a/binaries/data/mods/public/simulation/components/Attack.js +++ b/binaries/data/mods/public/simulation/components/Attack.js @@ -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 && diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 37d61f1c95..0ee226bb37 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -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;