diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js index 6422947bab..960cf21f7e 100644 --- a/binaries/data/mods/public/gui/common/tooltips.js +++ b/binaries/data/mods/public/gui/common/tooltips.js @@ -10,62 +10,80 @@ const g_CostDisplayIcons = { const g_TooltipTextFormats = { "unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'], "header": ['[font="sans-bold-13"]', '[/font]'], - "body": ['[font="sans-13"]', '[/font]'] + "body": ['[font="sans-13"]', '[/font]'], + "comma": ['[font="sans-12"]', '[/font]'] }; -function damageValues(dmg) -{ - if (!dmg) - return [0, 0, 0]; +const g_AttackTypes = { + "Charge": translate("Charge Attack:"), + "Melee": translate("Melee Attack:"), + "Ranged": translate("Ranged Attack:"), + "Capture": translate("Capture Attack:") +}; - return [dmg.hack || 0, dmg.pierce || 0, dmg.crush || 0]; +const g_DamageTypes = { + "hack": translate("Hack"), + "pierce": translate("Pierce"), + "crush": translate("Crush"), +}; + +function bodyFont(text) +{ + return g_TooltipTextFormats.body[0] + text + g_TooltipTextFormats.body[1]; } -function damageTypeDetails(dmg) +function headerFont(text) { - if (!dmg) - return '[font="sans-12"]' + translate("(None)") + '[/font]'; - - let dmgArray = []; - - if (dmg.hack) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.hack.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1] - })); - - if (dmg.pierce) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.pierce.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1] - })); - - if (dmg.crush) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.crush.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1] - })); - - return dmgArray.join(translate(", ")); + return g_TooltipTextFormats.header[0] + text + g_TooltipTextFormats.header[1]; } -function attackRateDetails(entState, type) +function unitFont(text) +{ + return g_TooltipTextFormats.unit[0] + text + g_TooltipTextFormats.unit[1]; +} + +function commaFont(text) +{ + return g_TooltipTextFormats.comma[0] + text + g_TooltipTextFormats.comma[1]; +} + +function getEntityTooltip(template) +{ + if (!template.tooltip) + return ""; + + return bodyFont(template.tooltip); +} + +function getHealthTooltip(template) +{ + if (!template.health) + return ""; + + return sprintf(translate("%(label)s %(details)s"), { + "label": headerFont(translate("Health:")), + "details": template.health + }); +} + +function attackRateDetails(template, type) { // Either one arrow shot by UnitAI, - let time = entState.attack[type].repeatTime / 1000; + let time = template.attack[type].repeatTime / 1000; let timeString = sprintf(translatePlural("%(time)s %(second)s", "%(time)s %(second)s", time), { "time": time, - "second": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", time) + g_TooltipTextFormats.unit[1] + "second": unitFont(translatePlural("second", "seconds", time)) }); // or multiple arrows shot by BuildingAI - if (!entState.buildingAI) + if (!template.buildingAI || type != "Ranged") return timeString; - let arrows = entState.buildingAI.arrowCount; - let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrow)s", "%(arrowcount)s %(arrow)s", arrows), { + // 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, - "arrow": g_TooltipTextFormats.unit[0] + translatePlural("arrow", "arrows", arrows) + g_TooltipTextFormats.unit[1] + "arrows": unitFont(translatePlural("arrow", "arrows", arrows)) }); return sprintf(translate("%(arrowString)s / %(timeString)s"), { @@ -74,47 +92,35 @@ function attackRateDetails(entState, type) }); } -// Converts an armor level into the actual reduction percentage +/** + * Converts an armor level into the actual reduction percentage + */ function armorLevelToPercentageString(level) { - return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%"; - // return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation. + return sprintf(translate("%(percentage)s%%"), { + "percentage": (100 - Math.round(Math.pow(0.9, level) * 100)) + }); } -function getArmorTooltip(dmg) +function getArmorTooltip(template) { - let label = g_TooltipTextFormats.header[0] + translate("Armor:") + g_TooltipTextFormats.header[1]; - if (!dmg) - return sprintf(translate("%(label)s %(details)s"), { - "label": label, - "details": '[font="sans-12"]' + translate("(None)") + '[/font]' - }); - - let dmgArray = []; - if (dmg.hack) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { - "damage": dmg.hack.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1], - "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.hack) }) + '[/font]' - })); - - if (dmg.pierce) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { - "damage": dmg.pierce.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1], - "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.pierce) }) + '[/font]' - })); - - if (dmg.crush) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { - "damage": dmg.crush.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1], - "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.crush) }) + '[/font]' - })); + if (!template.armour) + return ""; return sprintf(translate("%(label)s %(details)s"), { - "label": label, - "details": dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]') + "label": headerFont(translate("Armor:")), + "details": + Object.keys(template.armour).map( + dmgType => sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { + "damage": template.armour[dmgType].toFixed(1), + "damageType": unitFont(g_DamageTypes[dmgType]), + "armorPercentage": + '[font="sans-10"]' + + sprintf(translate("(%(armorPercentage)s)"), { + "armorPercentage": armorLevelToPercentageString(template.armour[dmgType]) + }) + '[/font]' + }) + ).join(commaFont(translate(", "))) }); } @@ -123,74 +129,46 @@ function damageTypesToText(dmg) if (!dmg) return '[font="sans-12"]' + translate("(None)") + '[/font]'; - let dmgArray = []; - if (dmg.hack) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.hack.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1] - })); - - if (dmg.pierce) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.pierce.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1] - })); - - if (dmg.crush) - dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), { - "damage": dmg.crush.toFixed(1), - "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1] - })); - - return dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]'); -} - -function getAttackTypeLabel(type) -{ - if (type === "Charge") return translate("Charge Attack:"); - if (type === "Melee") return translate("Melee Attack:"); - if (type === "Ranged") return translate("Ranged Attack:"); - if (type === "Capture") return translate("Capture Attack:"); - - warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { "attackType": type })); - return translate("Attack:"); + return Object.keys(g_DamageTypes).filter( + dmgType => dmg[dmgType]).map( + dmgType => sprintf(translate("%(damage)s %(damageType)s"), { + "damage": dmg[dmgType].toFixed(1), + "damageType": unitFont(g_DamageTypes[dmgType]) + })).join(commaFont(translate(", "))); } +// TODO: should also show minRange and splash damage function getAttackTooltip(template) { - let attacks = []; if (!template.attack) return ""; - let rateLabel = g_TooltipTextFormats.header[0] + (template.buildingAI ? translate("Interval:") : translate("Rate:")) + g_TooltipTextFormats.header[1]; - + let attacks = []; for (let type in template.attack) { if (type == "Slaughter") - continue; // Slaughter is not a real attack, so do not show it. + continue; // Slaughter is used to kill animals, so do not show it. if (type == "Charge") continue; // Charging isn't implemented yet and shouldn't be displayed. let rate = sprintf(translate("%(label)s %(details)s"), { - "label": rateLabel, + "label": + headerFont( + template.buildingAI && type == "Ranged" ? + translate("Interval:") : + translate("Rate:")), "details": attackRateDetails(template, type) }); - let attackLabel = g_TooltipTextFormats.header[0] + getAttackTypeLabel(type) + g_TooltipTextFormats.header[1]; - if (type == "Capture") + let attackLabel = headerFont(g_AttackTypes[type]); + if (type == "Capture" || type != "Ranged") { attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), { "attackLabel": attackLabel, - "details": template.attack[type].value, - "rate": rate - })); - continue; - } - if (type != "Ranged") - { - attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), { - "attackLabel": attackLabel, - "details": damageTypesToText(template.attack[type]), + "details": + type == "Capture" ? + template.attack.Capture.value : + damageTypesToText(template.attack[type]), "rate": rate })); continue; @@ -198,75 +176,56 @@ function getAttackTooltip(template) let realRange = template.attack[type].elevationAdaptedRange; let range = Math.round(template.attack[type].maxRange); - let rangeLabel = g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1]; - let relativeRange = Math.round((realRange - range)); - let meters = g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", range) + g_TooltipTextFormats.unit[1]; + let relativeRange = realRange ? Math.round(realRange - range) : 0; - if (relativeRange) // show if it is non-zero - attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(rangeString)s (%(relative)s), %(rate)s"), { - "attackLabel": attackLabel, - "details": damageTypesToText(template.attack[type]), - "rangeLabel": rangeLabel, - "rangeString": sprintf( - translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), { - "range": range, - "meters": meters - }), - "relative": relativeRange > 0 ? "+" + relativeRange : relativeRange, - "rate": rate - })); - else - attacks.push(sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s, %(rate)s"), { - "attackLabel": attackLabel, - "damageTypes": damageTypesToText(template.attack[type]), - "rangeLabel": rangeLabel, - "rangeString": sprintf( - translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), { - "range": range, - "meters": meters - }), - rate: rate - })); + let rangeString = relativeRange ? + translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s (%(relative)s), %(rate)s") : + translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s, %(rate)s"); + + attacks.push(sprintf(rangeString, { + "attackLabel": attackLabel, + "damageTypes": damageTypesToText(template.attack[type]), + "rangeLabel": translate("Range:"), + "rangeString": sprintf( + translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), { + "range": range, + "meters": unitFont(translatePlural("meter", "meters", range)) + }), + "rate": rate, + "relative": relativeRange > 0 ? "+" + relativeRange : relativeRange, + })); } - return attacks.join("\n"); } -function getRepairRateTooltip(rate) +function getRepairRateTooltip(template) { - return "\n" + sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), { - "repairRateLabel": g_TooltipTextFormats.header[0] + translate("Repair Rate:") + g_TooltipTextFormats.header[1], - "value": Math.round(rate * 10) / 10, - "health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1], - "second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1], - "worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1] + if (!template.repairRate) + return ""; + + return sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), { + "repairRateLabel": headerFont(translate("Repair Rate:")), + "value": Math.round(template.repairRate * 10) / 10, + "health": unitFont(translate("health")), + "second": unitFont(translate("second")), + "worker": unitFont(translate("worker")) }); } -function getBuildRateTooltip(rate) +function getBuildRateTooltip(template) { - return "\n" + sprintf(translate("%(buildRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), { - "buildRateLabel": g_TooltipTextFormats.header[0] + translate("Build Rate:") + g_TooltipTextFormats.header[1], - "value": Math.round(rate * 10) / 10, - "health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1], - "second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1], - "worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1] + if (!template.buildRate) + return ""; + + return sprintf(translate("%(buildRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), { + "buildRateLabel": headerFont(translate("Build Rate:")), + "value": Math.round(template.buildRate * 10) / 10, + "health": unitFont(translate("health")), + "second": unitFont(translate("second")), + "worker": unitFont(translate("worker")) }); } -/** - * Translates a cost component identifier as they are used internally - * (e.g. "population", "food", etc.) to proper display names. - */ -function getCostComponentDisplayIcon(costComponentName) -{ - if (costComponentName in g_CostDisplayIcons) - return g_CostDisplayIcons[costComponentName]; - - warn(sprintf("The specified cost component, ‘%(component)s’, is not currently supported.", { "component": costComponentName })); - return ""; -} - /** * Multiplies the costs for a template by a given batch size. */ @@ -295,12 +254,28 @@ function getEntityCostComponentsTooltipString(template, trainNum, entity) for (let type in g_CostDisplayIcons) if (totalCosts[type]) costs.push(sprintf(translate("%(component)s %(cost)s"), { - "component": getCostComponentDisplayIcon(type), + "component": g_CostDisplayIcons[type], "cost": totalCosts[type] })); return costs; } +function getGatherTooltip(template) +{ + if (!template.gather) + return ""; + + return sprintf(translate("%(label)s %(details)s"), { + "label": headerFont(translate("Gather Rates:")), + "details": + Object.keys(template.gather).map( + type => sprintf(translate("%(resourceIcon)s %(rate)s"), { + "resourceIcon": g_CostDisplayIcons[type], + "rate": template.gather[type] + }) + ).join(" ") + }); +} /** * Returns an array of strings for a set of wall pieces. If the pieces share @@ -321,7 +296,6 @@ function getWallPieceTooltip(wallTypes) for (let i = 1; i < wallTypes.length; ++i) { for (let resource in wallTypes[i].cost) - { // Break out of the same-type mode if this wall requires // resource types that the first didn't. if (wallTypes[i].cost[resource] && !resourceCount[resource]) @@ -329,10 +303,8 @@ function getWallPieceTooltip(wallTypes) sameTypes = false; break; } - } for (let resource in resourceCount) - { if (wallTypes[i].cost[resource]) resourceCount[resource].push(wallTypes[i].cost[resource]); else @@ -340,25 +312,17 @@ function getWallPieceTooltip(wallTypes) sameTypes = false; break; } - } } if (sameTypes) - { for (let resource in resourceCount) - { - let resourceMin = Math.min.apply(Math, resourceCount[resource]); - let resourceMax = Math.max.apply(Math, resourceCount[resource]); - // Translation: This string is part of the resources cost string on // the tooltip for wall structures. out.push(sprintf(translate("%(resourceIcon)s %(minimum)s to %(resourceIcon)s %(maximum)s"), { - "resourceIcon": getCostComponentDisplayIcon(resource), - "minimum": resourceMin, - "maximum": resourceMax + "resourceIcon": g_CostDisplayIcons[resource], + "minimum": Math.min.apply(Math, resourceCount[resource]), + "maximum": Math.max.apply(Math, resourceCount[resource]) })); - } - } else for (let i = 0; i < wallTypes.length; ++i) out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", ")); @@ -400,8 +364,8 @@ function getPopulationBonusTooltip(template) { let popBonus = ""; if (template.cost && template.cost.populationBonus) - popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), { - "label": g_TooltipTextFormats.header[0] + translate("Population Bonus:") + g_TooltipTextFormats.header[1], + popBonus = sprintf(translate("%(label)s %(populationBonus)s"), { + "label": headerFont(translate("Population Bonus:")), "populationBonus": template.cost.populationBonus }); return popBonus; @@ -412,14 +376,17 @@ function getPopulationBonusTooltip(template) */ function getNeededResourcesTooltip(resources) { + if (!resources) + return ""; + let formatted = []; for (let resource in resources) formatted.push(sprintf(translate("%(component)s %(cost)s"), { - "component": '[font="sans-12"]' + getCostComponentDisplayIcon(resource) + '[/font]', + "component": '[font="sans-12"]' + g_CostDisplayIcons[resource] + '[/font]', "cost": resources[resource] })); - return '\n\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join(" "); + return '\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join(" "); } function getSpeedTooltip(template) @@ -427,23 +394,22 @@ function getSpeedTooltip(template) if (!template.speed) return ""; - let label = g_TooltipTextFormats.header[0] + translate("Speed:") + g_TooltipTextFormats.header[1]; let speeds = []; if (template.speed.walk) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { "speed": Math.round(template.speed.walk), - "movementType": g_TooltipTextFormats.unit[0] + translate("Walk") + g_TooltipTextFormats.unit[1] + "movementType": unitFont(translate("Walk")) })); if (template.speed.run) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { "speed": Math.round(template.speed.run), - "movementType": g_TooltipTextFormats.unit[0] + translate("Run") + g_TooltipTextFormats.unit[1] + "movementType": unitFont(translate("Run")) })); return sprintf(translate("%(label)s %(speeds)s"), { - "label": label, + "label": headerFont(translate("Speed:")), "speeds": speeds.join(translate(", ")) }); } @@ -453,25 +419,24 @@ function getHealerTooltip(template) if (!template.healer) return ""; - let healer = [ + return [ sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.HP), { - "label": g_TooltipTextFormats.header[0] + translate("Heal:") + g_TooltipTextFormats.header[1], + "label": headerFont(translate("Heal:")), "val": template.healer.HP, // Translation: Short for hit points (or health points) that are healed in one healing action - "unit": g_TooltipTextFormats.unit[0] + translatePlural("HP", "HP", template.healer.HP) + g_TooltipTextFormats.unit[1] + "unit": unitFont(translatePlural("HP", "HP", template.healer.HP)) }), sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Range), { - "label": g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1], + "label": headerFont(translate("Range:")), "val": template.healer.Range, - "unit": g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", template.healer.Range) + g_TooltipTextFormats.unit[1] + "unit": unitFont(translatePlural("meter", "meters", template.healer.Range)) }), sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Rate/1000), { - "label": g_TooltipTextFormats.header[0] + translate("Rate:") + g_TooltipTextFormats.header[1], + "label": headerFont(translate("Rate:")), "val": template.healer.Rate/1000, - "unit": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", template.healer.Rate/1000) + g_TooltipTextFormats.unit[1] + "unit": unitFont(translatePlural("second", "seconds", template.healer.Rate / 1000)) }) - ]; - return healer.join(translate(", ")); + ].join(translate(", ")); } function getAurasTooltip(template) @@ -479,67 +444,50 @@ function getAurasTooltip(template) if (!template.auras) return ""; - let txt = ""; - for (let aura in template.auras) - txt += '\n' + sprintf(translate("%(auralabel)s %(aurainfo)s"), { - "auralabel": g_TooltipTextFormats.header[0] + sprintf(translate("%(auraname)s:"), { + let tooltips = Object.keys(template.auras).map( + aura => sprintf(translate("%(auralabel)s %(aurainfo)s"), { + "auralabel": headerFont(sprintf(translate("%(auraname)s:"), { "auraname": translate(template.auras[aura].name) - }) + g_TooltipTextFormats.header[1], - "aurainfo": g_TooltipTextFormats.body[0] + translate(template.auras[aura].description) + g_TooltipTextFormats.body[1] - }); - return txt; + })), + "aurainfo": bodyFont(translate(template.auras[aura].description)) + })); + return tooltips.join("\n"); } function getEntityNames(template) { - if (template.name.specific) - { - if (template.name.generic && template.name.specific != template.name.generic) - return sprintf(translate("%(specificName)s (%(genericName)s)"), { - "specificName": template.name.specific, - "genericName": template.name.generic - }); - return template.name.specific; - } - if (template.name.generic) + if (!template.name.specific) return template.name.generic; - warn("Entity name requested on an entity without a name, specific or generic."); - return translate("???"); -} + if (template.name.specific == template.name.generic) + return template.name.specific; + return sprintf(translate("%(specificName)s (%(genericName)s)"), { + "specificName": template.name.specific, + "genericName": template.name.generic + }); + +} function getEntityNamesFormatted(template) { - let names = ""; - let generic = template.name.generic; - let specific = template.name.specific; - if (specific) - { - // drop caps for specific name - names += '[font="sans-bold-16"]' + specific[0] + '[/font]' + - '[font="sans-bold-12"]' + specific.slice(1).toUpperCase() + '[/font]'; + if (!template.name.specific) + return '[font="sans-bold-16"]' + template.name.generic + "[/font]"; - if (generic) - names += '[font="sans-bold-16"] (' + generic + ')[/font]'; - } - else if (generic) - names = '[font="sans-bold-16"]' + generic + "[/font]"; - else - names = "???"; - - return names; + return sprintf(translate("%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s"), { + "specificName": + '[font="sans-bold-16"]' + template.name.specific[0] + '[/font]' + + '[font="sans-bold-12"]' + template.name.specific.slice(1).toUpperCase() + '[/font]', + "genericName": template.name.generic, + "fontStart": '[font="sans-bold-16"]', + "fontEnd": '[/font]' + }); } function getVisibleEntityClassesFormatted(template) { - let r = ""; - if (template.visibleIdentityClasses && template.visibleIdentityClasses.length) - { - r += '\n' + g_TooltipTextFormats.header[0] + translate("Classes:") + g_TooltipTextFormats.header[1]; - let classes = []; - for (let c of template.visibleIdentityClasses) - classes.push(translate(c)); - r += ' ' + g_TooltipTextFormats.body[0] + classes.join(translate(", ")) + g_TooltipTextFormats.body[1]; - } - return r; + if (!template.visibleIdentityClasses || !template.visibleIdentityClasses.length) + return ""; + + return headerFont(translate("Classes:")) + ' ' + + bodyFont(template.visibleIdentityClasses.map(c => translate(c)).join(translate(", "))); } diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js index a9b9af76b7..3107261ea9 100644 --- a/binaries/data/mods/public/gui/session/selection_details.js +++ b/binaries/data/mods/public/gui/session/selection_details.js @@ -177,10 +177,10 @@ function displaySingle(entState) if (entState.resourceSupply) { let resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol - sprintf(translate("%(amount)s / %(max)s"), { - "amount": Math.ceil(+entState.resourceSupply.amount), - "max": entState.resourceSupply.max - }); + sprintf(translate("%(amount)s / %(max)s"), { + "amount": Math.ceil(+entState.resourceSupply.amount), + "max": entState.resourceSupply.max + }); let resourceType = getResourceTypeDisplayName(entState.resourceSupply.type); @@ -267,68 +267,41 @@ function displaySingle(entState) Engine.GetGUIObjectByName("resourceCarryingText").hidden = true; } - // Set Player details Engine.GetGUIObjectByName("specific").caption = specificName; Engine.GetGUIObjectByName("player").caption = playerName; Engine.GetGUIObjectByName("playerColorBackground").sprite = "color: " + playerColor; - - if (genericName !== specificName) - Engine.GetGUIObjectByName("generic").caption = sprintf(translate("(%(genericName)s)"), { "genericName": genericName }); - else - Engine.GetGUIObjectByName("generic").caption = ""; + Engine.GetGUIObjectByName("generic").caption = genericName == specificName ? "" : + sprintf(translate("(%(genericName)s)"), { + "genericName": genericName + }); - if ("gaia" != playerState.civ) - { - Engine.GetGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem; - Engine.GetGUIObjectByName("player").tooltip = civName; - } - else - { - Engine.GetGUIObjectByName("playerCivIcon").sprite = ""; - Engine.GetGUIObjectByName("player").tooltip = ""; - } + let isGaia = "gaia" == playerState.civ; + Engine.GetGUIObjectByName("playerCivIcon").sprite = isGaia ? "" : "stretched:grayscale:" + civEmblem; + Engine.GetGUIObjectByName("player").tooltip = isGaia ? "" : civName; - // Icon image // TODO: we should require all entities to have icons Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "bkFillBlack"; - let armorString = getArmorTooltip(entState.armour); + Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = [ + getAttackTooltip, + getArmorTooltip, + getRepairRateTooltip, + getBuildRateTooltip + ].map(func => func(entState)).filter(tip => tip).join("\n"); - // Attack and Armor - Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = entState.attack ? (getAttackTooltip(entState) + "\n" + armorString) : armorString; - - // Repair Rate - if (entState.repairRate) - Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getRepairRateTooltip(entState.repairRate); - - // Build Rate - if (entState.buildRate) - Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getBuildRateTooltip(entState.buildRate); - - // Icon Tooltip - let iconTooltip = ""; + let iconTooltips = []; if (genericName) - iconTooltip = "[font=\"sans-bold-16\"]" + genericName + "[/font]"; + iconTooltips.push("[font=\"sans-bold-16\"]" + genericName + "[/font]"); - if (template.visibleIdentityClasses && template.visibleIdentityClasses.length) - { - iconTooltip += "\n[font=\"sans-bold-13\"]" + translate("Classes:") + "[/font] "; + iconTooltips = iconTooltips.concat([ + getVisibleEntityClassesFormatted, + getAurasTooltip, + getEntityTooltip + ].map(func => func(template))); - iconTooltip += "[font=\"sans-13\"]" + - template.visibleIdentityClasses.map(c => translate(c)).join(translate(", ")) + - "[/font]"; - } + Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(tip => tip).join("\n"); - if (template.auras) - iconTooltip += getAurasTooltip(template); - - if (template.tooltip) - iconTooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]"; - - Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltip; - - // Unhide Details Area Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false; Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true; } @@ -417,7 +390,7 @@ function displayMultiple(selection) let numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits"); numberOfUnits.caption = selection.length; numberOfUnits.tooltip = Object.keys(totalResourcesCarried).map(res => - getCostComponentDisplayIcon(res) + totalResourcesCarried[res] + g_CostDisplayIcons[res] + totalResourcesCarried[res] ).join(" "); // Unhide Details Area diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js index e5900bea4f..c62177ebee 100644 --- a/binaries/data/mods/public/gui/session/selection_panels.js +++ b/binaries/data/mods/public/gui/session/selection_panels.js @@ -301,34 +301,31 @@ g_SelectionPanels.Construction = { "player": data.unitEntState.player }); - let limits = getEntityLimitAndCount(data.playerState, data.item); - if (template.wallSet) template.auras = GetTemplateData(template.wallSet.templates.long).auras; data.button.onPress = function () { startBuildingPlacement(data.item, data.playerState); }; - let tooltip = getEntityNamesFormatted(template); - tooltip += getVisibleEntityClassesFormatted(template); - tooltip += getAurasTooltip(template); + let tooltips = [ + getEntityNamesFormatted, + getVisibleEntityClassesFormatted, + getAurasTooltip, + getEntityTooltip, + getEntityCostTooltip, + getPopulationBonusTooltip + ].map(func => func(template)); - if (template.tooltip) - tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]"; - - tooltip += "\n" + getEntityCostTooltip(template); - tooltip += getPopulationBonusTooltip(template); - - tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers); + let limits = getEntityLimitAndCount(data.playerState, data.item); + tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers)); if (!technologyEnabled) - tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { + tooltips.push(sprintf(translate("Requires %(technology)s"), { "technology": getEntityNames(GetTechnologyData(template.requiredTechnology)) - }); + })); - if (neededResources) - tooltip += getNeededResourcesTooltip(neededResources); + tooltips.push(getNeededResourcesTooltip(neededResources)); - data.button.tooltip = tooltip; + data.button.tooltip = tooltips.filter(tip => tip).join("\n"); let modifier = ""; if (!technologyEnabled || limits.canBeAddedCount == 0) @@ -529,27 +526,26 @@ g_SelectionPanels.Gate = { { data.button.onPress = function() {data.item.callback(data.item); }; - let tooltip = data.item.tooltip; + let tooltips = [data.item.tooltip]; if (data.item.template) { data.template = GetTemplateData(data.item.template); - data.wallCount = data.selection.reduce(function (count, ent) { + data.wallCount = data.selection.reduce(count, ent => { let state = GetEntityState(ent); if (hasClass(state, "LongWall") && !state.gate) ++count; return count; }, 0); - tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount); + tooltips.push(getEntityCostTooltip(data.template, data.wallCount)); data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": multiplyEntityCosts(data.template, data.wallCount) }); - if (data.neededResources) - tooltip += getNeededResourcesTooltip(data.neededResources); + tooltips.push(getNeededResourcesTooltip(data.neededResources)); } - data.button.tooltip = tooltip; + data.button.tooltip = tooltips.filter(tip => tip).join("\n"); data.button.enabled = controlsPlayer(data.unitEntState.player); let gateIcon; @@ -679,7 +675,7 @@ g_SelectionPanels.Queue = { { tooltip += "\n[color=\"red\"]" + translate("Insufficient population capacity:") + "\n[/color]"; tooltip += sprintf(translate("%(population)s %(neededSlots)s"), { - "population": getCostComponentDisplayIcon("population"), + "population": g_CostDisplayIcons.population, "neededSlots": data.item.neededSlots }); } @@ -783,25 +779,28 @@ g_SelectionPanels.Research = { let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]"); let icon = Engine.GetGUIObjectByName("unitResearchIcon[" + position + "]"); - let tooltip = getEntityNamesFormatted(template); - if (template.tooltip) - tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]"; + let tooltips = [ + getEntityNamesFormatted, + getEntityTooltip, + getEntityCostTooltip + ].map(func => func(template)); - tooltip += "\n" + getEntityCostTooltip(template); if (!requirementsPassed) { - tooltip += "\n" + template.requirementsTooltip; + let tip = template.requirementsTooltip; if (template.classRequirements) { let player = data.unitEntState.player; let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0; let remaining = template.classRequirements.number - current; - tooltip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), { "number": remaining }); + tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), { + "number": remaining + }); } + tooltips.push(tip); } - if (neededResources) - tooltip += getNeededResourcesTooltip(neededResources); - button.tooltip = tooltip; + tooltips.push(getNeededResourcesTooltip(neededResources)); + button.tooltip = tooltips.filter(tip => tip).join("\n"); button.onPress = function () { addResearchToQueue(data.unitEntState.id, tech); @@ -988,47 +987,41 @@ g_SelectionPanels.Training = { data.countDisplay.caption = trainNum > 1 ? trainNum : ""; - let tooltip = "[font=\"sans-bold-16\"]" + - colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) + - "[/font]"; - - tooltip += getEntityNamesFormatted(template); - tooltip += getVisibleEntityClassesFormatted(template); - tooltip += getAurasTooltip(template); - - if (template.tooltip) - tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]"; - - tooltip += "\n" + getEntityCostTooltip(template, trainNum, data.unitEntState.id); + let tooltips = [ + "[font=\"sans-bold-16\"]" + + colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) + + "[/font]" + " " + getEntityNamesFormatted(template), + getVisibleEntityClassesFormatted(template), + getAurasTooltip(template), + getEntityTooltip(template), + getEntityCostTooltip(template, trainNum, data.unitEntState.id) + ]; let limits = getEntityLimitAndCount(data.playerState, data.item); + tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers)); - tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers); if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true") - { - if (template.health) - tooltip += "\n[font=\"sans-bold-13\"]" + translate("Health:") + "[/font] " + template.health; - if (template.attack) - tooltip += "\n" + getAttackTooltip(template); - if (template.armour) - tooltip += "\n" + getArmorTooltip(template.armour); - if (template.speed) - tooltip += "\n" + getSpeedTooltip(template); - } + tooltips.push( + getHealthTooltip(template), + getAttackTooltip(template), + getArmorTooltip(template), + getSpeedTooltip(template) + ); - tooltip += "[color=\"" + g_HotkeyColor + "\"]" + + tooltips.push( + "[color=\"" + g_HotkeyColor + "\"]" + " " + formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) + - "[/color]"; + "[/color]"); if (!technologyEnabled) - { - let techName = getEntityNames(GetTechnologyData(template.requiredTechnology)); - tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { "technology": techName }); - } - if (neededResources) - tooltip += getNeededResourcesTooltip(neededResources); + tooltips.push(sprintf(translate("Requires %(technology)s"), { + "technology": getEntityNames(GetTechnologyData(template.requiredTechnology)) + })); - data.button.tooltip = tooltip; + if (neededResources) + tooltips.push(getNeededResourcesTooltip(neededResources)); + + data.button.tooltip = tooltips.filter(tip => tip).join("\n"); let modifier = ""; if (!technologyEnabled || limits.canBeAddedCount == 0) diff --git a/binaries/data/mods/public/gui/session/selection_panels_helpers.js b/binaries/data/mods/public/gui/session/selection_panels_helpers.js index 681b65a98f..6edb2bbdc7 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ b/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -1,20 +1,11 @@ -// Constants -// Barter constants const BARTER_RESOURCE_AMOUNT_TO_SELL = 100; const BARTER_BUNCH_MULTIPLIER = 5; const BARTER_RESOURCES = ["food", "wood", "stone", "metal"]; const BARTER_ACTIONS = ["Sell", "Buy"]; - -// Gate constants const GATE_ACTIONS = ["lock", "unlock"]; -// ============================================== -// BARTER HELPERS -// Resources to sell on barter panel var g_BarterSell = "food"; -// FORMATION HELPERS -// Check if the selection can move into formation, and cache the result function canMoveSelectionIntoFormation(formationTemplate) { if (!(formationTemplate in g_canMoveIntoFormation)) @@ -27,9 +18,6 @@ function canMoveSelectionIntoFormation(formationTemplate) return g_canMoveIntoFormation[formationTemplate]; } -// ============================================== -// STANCE HELPERS - function getStanceDisplayName(name) { switch (name) @@ -69,8 +57,6 @@ function getStanceTooltip(name) } } -// ============================================== -// TRAINING / CONSTRUCTION HELPERS /** * Format entity count/limit message for the tooltip */ @@ -79,17 +65,27 @@ function formatLimitString(trainEntLimit, trainEntCount, trainEntLimitChangers) if (trainEntLimit == undefined) return ""; - var text = "\n\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { "count": trainEntCount, "limit": trainEntLimit }); + var text = "\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { + "count": trainEntCount, + "limit": trainEntLimit + }); if (trainEntCount >= trainEntLimit) text = "[color=\"red\"]" + text + "[/color]"; for (var c in trainEntLimitChangers) { - if (trainEntLimitChangers[c] > 0) - text += "\n" + sprintf(translate("%(changer)s enlarges the limit with %(change)s."), { "changer": translate(c), "change": trainEntLimitChangers[c] }); - else if (trainEntLimitChangers[c] < 0) - text += "\n" + sprintf(translate("%(changer)s lessens the limit with %(change)s."), { "changer": translate(c), "change": (-trainEntLimitChangers[c]) }); + if (!trainEntLimitChangers[c]) + continue; + + let string = trainEntLimitChangers[c] > 0 ? + translate("%(changer)s enlarges the limit with %(change)s.") : + translate("%(changer)s lessens the limit with %(change)s."); + + text += "\n" + sprintf(string, { + "changer": translate(c), + "change": trainEntLimitChangers[c] + }); } return text; } @@ -113,7 +109,6 @@ function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize if (totalBatchTrainingCount < 2) return ""; - var batchTrainingString = ""; var fullBatchesString = ""; if (buildingsCountToTrainFullBatch > 0) { @@ -126,34 +121,26 @@ function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize fullBatchesString = fullBatchSize; } - var remainderBatchString = remainderBatch > 0 ? remainderBatch : ""; - var batchDetailsString = ""; - var action = "[font=\"sans-bold-13\"]" + translate("Shift-click") + "[/font]"; - // We need to display the batch details part if there is either more than // one building with full batch or one building with the full batch and // another with a partial batch + let batchString; if (buildingsCountToTrainFullBatch > 1 || - (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0)) + buildingsCountToTrainFullBatch == 1 && remainderBatch > 0) { if (remainderBatch > 0) - return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."), { - "action": action, - "number": totalBatchTrainingCount, - "fullBatch": fullBatchesString, - "remainderBatch": remainderBatch - }) + "[/font]"; - - return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s)."), { - "action": action, - "number": totalBatchTrainingCount, - "fullBatch": fullBatchesString - }) + "[/font]"; + batchString = translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."); + else + batchString = translate("%(action)s to train %(number)s (%(fullBatch)s)."); } + else + batchString = translate("%(action)s to train %(number)s."); - return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s."), { - "action": action, - "number": totalBatchTrainingCount + return "[font=\"sans-13\"]" + sprintf(batchString, { + "action": "[font=\"sans-bold-13\"]" + translate("Shift-click") + "[/font]", + "number": totalBatchTrainingCount, + "fullBatch": fullBatchesString, + "remainderBatch": remainderBatch }) + "[/font]"; } diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index baf0f89e3d..4d6eb8dfee 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -824,22 +824,17 @@ function updateHeroes() function createHeroTooltip(heroState, template) { - let tooltip = "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]" + "\n" + - sprintf(translate("%(label)s %(current)s / %(max)s"), { - "label": "[font=\"sans-bold-13\"]" + translate("Health:") + "[/font]", - "current": Math.ceil(heroState.hitpoints), - "max": Math.ceil(heroState.maxHitpoints) - }); - - if (heroState.attack) - tooltip += "\n" + getAttackTooltip(heroState); - - tooltip += "\n" + getArmorTooltip(heroState.armour); - - if (template.tooltip) - tooltip += "\n" + template.tooltip; - - return tooltip; + return [ + "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]" + "\n" + + sprintf(translate("%(label)s %(current)s / %(max)s"), { + "label": "[font=\"sans-bold-13\"]" + translate("Health:") + "[/font]", + "current": Math.ceil(heroState.hitpoints), + "max": Math.ceil(heroState.maxHitpoints) + }), + getAttackTooltip(heroState), + getArmorTooltip(heroState), + getEntityTooltip(heroState) + ].filter(tip => tip).join("\n"); } function displayHeroes() diff --git a/binaries/data/mods/public/gui/structree/draw.js b/binaries/data/mods/public/gui/structree/draw.js index 7d3ef88081..4fb1ba2845 100644 --- a/binaries/data/mods/public/gui/structree/draw.js +++ b/binaries/data/mods/public/gui/structree/draw.js @@ -1,5 +1,19 @@ var g_DrawLimits = {}; // GUI limits. Populated by predraw() +var g_TooltipFunctions = [ + getEntityNamesFormatted, + getEntityCostTooltip, + getEntityTooltip, + getAurasTooltip, + getHealthTooltip, + getHealerTooltip, + getAttackTooltip, + getArmorTooltip, + getSpeedTooltip, + getGatherTooltip, + getPopulationBonusTooltip +]; + /** * Draw the structree * @@ -363,54 +377,9 @@ function predraw() * Assemble a tooltip text * * @param template Information about a Unit, a Structure or a Technology - * * @return The tooltip text, formatted. */ function assembleTooltip(template) { - let txt = getEntityNamesFormatted(template); - txt += '\n' + getEntityCostTooltip(template, 1); - - if (template.tooltip) - txt += '\n' + g_TooltipTextFormats.body[0] + translate(template.tooltip) + g_TooltipTextFormats.body[1]; - - if (template.auras) - txt += getAurasTooltip(template); - - if (template.health) - txt += '\n' + sprintf(translate("%(label)s %(details)s"), { - "label": g_TooltipTextFormats.header[0] + translate("Health:") + g_TooltipTextFormats.header[1], - "details": template.health - }); - - if (template.healer) - txt += '\n' + getHealerTooltip(template); - - if (template.attack) - txt += '\n' + getAttackTooltip(template); - - if (template.armour) - txt += '\n' + getArmorTooltip(template.armour); - - if (template.speed) - txt += '\n' + getSpeedTooltip(template); - - if (template.gather) - { - let rates = []; - for (let type in template.gather) - rates.push(sprintf(translate("%(resourceIcon)s %(rate)s"), { - "resourceIcon": getCostComponentDisplayIcon(type), - "rate": template.gather[type] - })); - - txt += '\n' + sprintf(translate("%(label)s %(details)s"), { - "label": g_TooltipTextFormats.header[0] + translate("Gather Rates:") + g_TooltipTextFormats.header[1], - "details": rates.join(" ") - }); - } - - txt += getPopulationBonusTooltip(template); - - return txt; + return g_TooltipFunctions.map(func => func(template)).filter(tip => tip).join("\n"); }