1
0
forked from 0ad/0ad

Generalise attack tooltips after D2092/16b452cf91.

This allows attacks with both capture and damage effects to show both,
and centralises display between regular attack and Splash.
It also reverts a change from e05c8263c5 which incorrectly switched
'Interval' and 'Rate'.

Patch By: Freagarach
Reviewed By: wraitii
Differential Revision: https://code.wildfiregames.com/D2138
This was SVN commit r22866.
This commit is contained in:
wraitii 2019-09-07 14:47:39 +00:00
parent 43bbd7d671
commit f29159b935
2 changed files with 136 additions and 113 deletions

View File

@ -32,7 +32,7 @@
buffer_zone="4"
delay="500"
font="sans-14"
maxwidth="300"
maxwidth="400"
offset="16 32"
sprite="BackgroundWhiteBorderBlack"
textcolor="0 0 0"
@ -43,7 +43,7 @@
buffer_zone="4"
delay="500"
font="sans-14"
maxwidth="300"
maxwidth="400"
offset="-4 -4"
sprite="BackgroundWhiteBorderBlack"
textcolor="0 0 0"
@ -54,7 +54,7 @@
buffer_zone="4"
delay="100"
font="sans-14"
maxwidth="300"
maxwidth="400"
offset="16 32"
sprite="BackgroundSessionTooltip"
textcolor="255 255 255"
@ -65,7 +65,7 @@
buffer_zone="4"
delay="0"
font="sans-14"
maxwidth="300"
maxwidth="400"
offset="16 32"
sprite="BackgroundSessionTooltip"
textcolor="255 255 255"
@ -87,7 +87,7 @@
buffer_zone="4"
delay="100"
font="sans-bold-14"
maxwidth="300"
maxwidth="400"
offset="16 32"
sprite="BackgroundSessionTooltip"
textcolor="255 255 255"

View File

@ -8,32 +8,6 @@ var g_TooltipTextFormats = {
"nameGeneric": { "font": "sans-bold-16" }
};
var g_AttackTypes = {
"Melee": translate("Melee Attack:"),
"Ranged": translate("Ranged Attack:"),
"Capture": translate("Capture Attack:")
};
var g_SplashDamageTypes = {
"Circular": translate("Circular Splash Damage"),
"Linear": translate("Linear Splash Damage")
};
var g_RangeTooltipString = {
"relative": {
// Translation: For example: Ranged Attack: 12.0 Pierce, Range: 2 to 10 (+2) meters, Interval: 3 arrows / 2 seconds
"minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s"),
// Translation: For example: Ranged Attack: 12.0 Pierce, Range: 10 (+2) meters, Interval: 3 arrows / 2 seconds
"no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s"),
},
"non-relative": {
// Translation: For example: Ranged Attack: 12.0 Pierce, Range: 2 to 10 meters, Interval: 3 arrows / 2 seconds
"minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s, %(rate)s"),
// Translation: For example: Ranged Attack: 12.0 Pierce, Range: 10 meters, Interval: 3 arrows / 2 seconds
"no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s %(rangeUnit)s, %(rate)s"),
}
};
function getCostTypes()
{
return g_ResourceData.GetCodes().concat(["population", "populationBonus", "time"]);
@ -177,28 +151,6 @@ function getCurrentHealthTooltip(entState, label)
});
}
function attackRateDetails(template, type)
{
// Either one arrow shot by UnitAI,
let timeString = getSecondsString(template.attack[type].repeatTime / 1000);
// or multiple arrows shot by BuildingAI
if (!template.buildingAI || type != "Ranged")
return timeString;
// Show either current rate from simulation or default count if the sim is not running
let arrows = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount;
let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrows)s", "%(arrowcount)s %(arrows)s", arrows), {
"arrowcount": arrows,
"arrows": unitFont(translatePlural("arrow", "arrows", arrows))
});
return sprintf(translate("%(arrowString)s / %(timeString)s"), {
"arrowString": arrowString,
"timeString": timeString
});
}
/**
* Converts an armor level into the actual reduction percentage
*/
@ -231,69 +183,137 @@ function getArmorTooltip(template)
});
}
function damageTypesToText(dmg)
function attackRateDetails(interval, projectiles)
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
// ToDo: Get the name of a projectile from the template.
return Object.keys(dmg).filter(dmgType => dmg[dmgType]).map(
if (!interval || projectiles == 0)
return "";
let attackRateString = getSecondsString(interval / 1000);
let header = headerFont(translate("Interval:"));
if (+projectiles > 1)
{
header = headerFont(translate("Rate:"));
let 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"), {
"projectileString": projectileString,
"attackRateString": attackRateString
});
}
return sprintf(translate("%(label)s %(details)s"), {
"label": header,
"details": attackRateString
});
}
function rangeDetails(attackTypeTemplate)
{
if (!attackTypeTemplate.maxRange)
return "";
let rangeTooltipString = {
"relative": {
// Translation: For example: Range: 2 to 10 (+2) meters
"minRange": translate("%(rangeLabel)s %(minRange)s to %(maxRange)s (%(relativeRange)s) %(rangeUnit)s"),
// Translation: For example: Range: 10 (+2) meters
"no-minRange": translate("%(rangeLabel)s %(maxRange)s (%(relativeRange)s) %(rangeUnit)s"),
},
"non-relative": {
// Translation: For example: Range: 2 to 10 meters
"minRange": translate("%(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s"),
// Translation: For example: Range: 10 meters
"no-minRange": translate("%(rangeLabel)s %(maxRange)s %(rangeUnit)s"),
}
};
let minRange = Math.round(attackTypeTemplate.minRange);
let maxRange = Math.round(attackTypeTemplate.maxRange);
let realRange = attackTypeTemplate.elevationAdaptedRange;
let relativeRange = realRange ? Math.round(realRange - maxRange) : 0;
return sprintf(rangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], {
"rangeLabel": headerFont(translate("Range:")),
"minRange": minRange,
"maxRange": maxRange,
"relativeRange": relativeRange > 0 ? sprintf(translate("+%(number)s"), { "number": relativeRange }) : relativeRange,
"rangeUnit":
unitFont(minRange || relativeRange ?
// Translation: For example "0.5 to 1 meters", "1 (+1) meters" or "1 to 2 (+3) meters"
translate("meters") :
translatePlural("meter", "meters", maxRange))
});
}
function damageDetails(damageTemplate)
{
if (!damageTemplate)
return "";
return Object.keys(damageTemplate).filter(dmgType => damageTemplate[dmgType]).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg[dmgType].toFixed(1),
"damage": damageTemplate[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", dmgType))
})).join(commaFont(translate(", ")));
}
function captureDetails(captureTemplate)
{
if (!captureTemplate)
return "";
return sprintf(translate("%(amount)s %(name)s"), {
"amount": captureTemplate.toFixed(1),
"name": unitFont(translateWithContext("damage type", "Capture"))
});
}
function attackEffectsDetails(attackTypeTemplate)
{
if (!attackTypeTemplate)
return "";
let effects = [
captureDetails(attackTypeTemplate.Capture || undefined),
damageDetails(attackTypeTemplate.Damage || undefined)
];
return effects.filter(effect => effect).join(commaFont(translate(", ")));
}
function getAttackTooltip(template)
{
if (!template.attack)
return "";
let tooltips = [];
for (let type in template.attack)
for (let attackType in template.attack)
{
if (type == "Slaughter")
continue; // Slaughter is used to kill animals, so do not show it.
let rate = sprintf(translate("%(label)s %(details)s"), {
"label":
headerFont(
template.buildingAI && type == "Ranged" ?
translate("Interval:") :
translate("Rate:")),
"details": attackRateDetails(template, type)
});
let attackLabel = headerFont(g_AttackTypes[type]);
if (type == "Capture" || type != "Ranged")
{
tooltips.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
"attackLabel": attackLabel,
"details":
type == "Capture" ?
template.attack.Capture.Capture :
damageTypesToText(template.attack[type].Damage),
"rate": rate
}));
// Slaughter is used to kill animals, so do not show it.
if (attackType == "Slaughter")
continue;
}
let minRange = Math.round(template.attack[type].minRange);
let maxRange = Math.round(template.attack[type].maxRange);
let realRange = template.attack[type].elevationAdaptedRange;
let relativeRange = realRange ? Math.round(realRange - maxRange) : 0;
tooltips.push(sprintf(g_RangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], {
let attackLabel = sprintf(headerFont(translate("%(attackType)s Attack")), {
"attackType": attackType
});
let attackTypeTemplate = template.attack[attackType];
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)
projectiles = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount;
tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s"), {
"attackLabel": attackLabel,
"damageTypes": damageTypesToText(template.attack[type].Damage),
"rangeLabel": headerFont(translate("Range:")),
"minRange": minRange,
"maxRange": maxRange,
"relativeRange": relativeRange > 0 ? sprintf(translate("+%(number)s"), { "number": relativeRange }) : relativeRange,
"rangeUnit":
unitFont(minRange || relativeRange ?
// Translation: For example "0.5 to 1 meters", "1 (+1) meters" or "1 to 2 (+3) meters"
translate("meters") :
translatePlural("meter", "meters", maxRange)),
"rate": rate,
"effects": attackEffectsDetails(attackTypeTemplate),
"range": rangeDetails(attackTypeTemplate),
"rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles)
}));
}
return tooltips.join("\n");
@ -305,20 +325,23 @@ function getSplashDamageTooltip(template)
return "";
let tooltips = [];
for (let type in template.attack)
for (let attackType in template.attack)
{
let splash = template.attack[type].splash;
if (!splash)
let splashTemplate = template.attack[attackType].splash;
if (!splashTemplate)
continue;
let splashDamageTooltip = sprintf(translate("%(label)s: %(value)s"), {
"label": headerFont(g_SplashDamageTypes[splash.shape]),
"value": damageTypesToText(splash.Damage)
let splashLabel = sprintf(headerFont(translate("%(splashShape)s Splash Damage")), {
"splashShape": splashTemplate.shape
});
let splashDamageTooltip = sprintf(translate("%(label)s: %(effects)s"), {
"label": splashLabel,
"effects": attackEffectsDetails(splashTemplate)
});
if (g_AlwaysDisplayFriendlyFire || splash.friendlyFire)
if (g_AlwaysDisplayFriendlyFire || splashTemplate.friendlyFire)
splashDamageTooltip += commaFont(translate(", ")) + sprintf(translate("Friendly Fire: %(enabled)s"), {
"enabled": splash.friendlyFire ? translate("Yes") : translate("No")
"enabled": splashTemplate.friendlyFire ? translate("Yes") : translate("No")
});
tooltips.push(splashDamageTooltip);
@ -389,9 +412,9 @@ function getProjectilesTooltip(template)
function getRepairTimeTooltip(entState)
{
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Number of repairers:")),
"details": entState.repairable.numBuilders
}) + "\n" + (entState.repairable.numBuilders ?
"label": headerFont(translate("Number of repairers:")),
"details": entState.repairable.numBuilders
}) + "\n" + (entState.repairable.numBuilders ?
sprintf(translatePlural(
"Add another worker to speed up the repairs by %(second)s second.",
"Add another worker to speed up the repairs by %(second)s seconds.",
@ -411,9 +434,9 @@ function getRepairTimeTooltip(entState)
function getBuildTimeTooltip(entState)
{
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Number of builders:")),
"details": entState.foundation.numBuilders
}) + "\n" + (entState.foundation.numBuilders ?
"label": headerFont(translate("Number of builders:")),
"details": entState.foundation.numBuilders
}) + "\n" + (entState.foundation.numBuilders ?
sprintf(translatePlural(
"Add another worker to speed up the construction by %(second)s second.",
"Add another worker to speed up the construction by %(second)s seconds.",