Ticket #3904: tooltip.5.diff

File tooltip.5.diff, 64.0 KB (added by elexis, 8 years ago)
  • binaries/data/mods/public/globalscripts/Templates.js

    function MatchesClassList(classes, match  
    7474 */
    7575function GetTemplateDataHelper(template, player, auraTemplates)
    7676{
    7777    var ret = {};
    7878
     79    // TODO: the second argument of func can be derived from the first
    7980    var func;
    8081    if (player)
    8182        func = ApplyValueModificationsToTemplate;
    8283    else
    8384        func = function(a, val, c, d) { return val; }
    8485
    8586    if (template.Armour)
    86     {
    8787        ret.armour = {
    8888            "hack": func("Armour/Hack", +template.Armour.Hack, player, template),
    8989            "pierce": func("Armour/Pierce", +template.Armour.Pierce, player, template),
    9090            "crush": func("Armour/Crush", +template.Armour.Crush, player, template),
    9191        };
    92     }
    9392
    9493    if (template.Attack)
    9594    {
    9695        let getAttackStat = function(type, stat)
    9796        {
    function GetTemplateDataHelper(template,  
    130129                    "description": aura.auraDescription || null
    131130                };
    132131        }
    133132    }
    134133
     134    if (template.BuildingAI)
     135    {
     136        ret.buildingAI = {};
     137        if (template.BuildingAI.DefaultArrowCount)
     138            ret.buildingAI.defaultArrowCount = func("BuildingAI/DefaultArrowCount", +template.BuildingAI.DefaultArrowCount, player, template);
     139        if (template.BuildingAI.GarrisonArrowMultiplier)
     140            ret.buildingAI.garrisonArrowMultiplier = func("BuildingAI/GarrisonArrowMultiplier", +template.BuildingAI.GarrisonArrowMultiplier, player, template);
     141        if (template.BuildingAI.MaxArrowCount)
     142            ret.buildingAI.maxArrowCount = func("BuildingAI/MaxArrowCount", +template.BuildingAI.MaxArrowCount, player, template);
     143    }
     144
    135145    if (template.BuildRestrictions)
    136146    {
    137147        // required properties
    138148        ret.buildRestrictions = {
    139149            "placementType": template.BuildRestrictions.PlacementType,
    function GetTemplateDataHelper(template,  
    181191            ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]};
    182192        else
    183193            warn("GetTemplateDataHelper(): Unrecognized Footprint type");
    184194    }
    185195
     196    if (template.GarrisonHolder)
     197    {
     198        ret.garrisonHolder = {};
     199        if (template.GarrisonHolder.Max)
     200            ret.garrisonHolder.max = func("GarrisonHolder/Max", +template.GarrisonHolder.Max, player, template);
     201    }
     202
    186203    if (template.Obstruction)
    187204    {
    188205        ret.obstruction = {
    189206            "active": ("" + template.Obstruction.Active == "true"),
    190207            "blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
  • binaries/data/mods/public/gui/common/tooltips.js

    const g_CostDisplayIcons = {  
    88};
    99
    1010const g_TooltipTextFormats = {
    1111    "unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'],
    1212    "header": ['[font="sans-bold-13"]', '[/font]'],
    13     "body": ['[font="sans-13"]', '[/font]']
     13    "body": ['[font="sans-13"]', '[/font]'],
     14    "comma": ['[font="sans-12"]', '[/font]']
    1415};
    1516
     17const g_AttackTypes = {
     18    "Charge": translate("Charge Attack:"),
     19    "Melee": translate("Melee Attack:"),
     20    "Ranged": translate("Ranged Attack:"),
     21    "Capture": translate("Capture Attack:")
     22};
     23
     24const g_DamageTypes = {
     25    "hack": translate("Hack"),
     26    "pierce": translate("Pierce"),
     27    "crush": translate("Crush"),
     28};
     29
     30function bodyFont(text)
     31{
     32    return g_TooltipTextFormats.body[0] + text + g_TooltipTextFormats.body[1];
     33}
     34
     35function headerFont(text)
     36{
     37    return g_TooltipTextFormats.header[0] + text + g_TooltipTextFormats.header[1];
     38}
     39
     40function unitFont(text)
     41{
     42    return g_TooltipTextFormats.unit[0] + text + g_TooltipTextFormats.unit[1];
     43}
     44
     45function commaFont(text)
     46{
     47    return g_TooltipTextFormats.comma[0] + text + g_TooltipTextFormats.comma[1];
     48}
     49
    1650function damageValues(dmg)
    1751{
    1852    if (!dmg)
    1953        return [0, 0, 0];
    2054
    2155    return [dmg.hack || 0, dmg.pierce || 0, dmg.crush || 0];
    2256}
    2357
    24 function damageTypeDetails(dmg)
     58function getEntityTooltip(template)
    2559{
    26     if (!dmg)
    27         return '[font="sans-12"]' + translate("(None)") + '[/font]';
    28 
    29     let dmgArray = [];
    30 
    31     if (dmg.hack)
    32         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    33             "damage": dmg.hack.toFixed(1),
    34             "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1]
    35         }));
     60    if (!template.tooltip)
     61        return "";
    3662
    37     if (dmg.pierce)
    38         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    39             "damage": dmg.pierce.toFixed(1),
    40             "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1]
    41         }));
     63    return bodyFont(template.tooltip);
     64}
    4265
    43     if (dmg.crush)
    44         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    45             "damage": dmg.crush.toFixed(1),
    46             "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1]
    47         }));
     66function getHealthTooltip(template)
     67{
     68    if (!template.health)
     69        return "";
    4870
    49     return dmgArray.join(translate(", "));
     71    return sprintf(translate("%(label)s %(details)s"), {
     72        "label": headerFont(translate("Health:")),
     73        "details": template.health
     74    })
    5075}
    5176
    52 function attackRateDetails(entState, type)
     77function attackRateDetails(template, type)
    5378{
    5479    // Either one arrow shot by UnitAI,
    55     let time = entState.attack[type].repeatTime / 1000;
     80    let time = template.attack[type].repeatTime / 1000;
    5681    let timeString = sprintf(translatePlural("%(time)s %(second)s", "%(time)s %(second)s", time), {
    5782        "time": time,
    58         "second": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", time) + g_TooltipTextFormats.unit[1]
     83        "second": unitFont(translatePlural("second", "seconds", time))
    5984    });
    6085
    6186    // or multiple arrows shot by BuildingAI
    62     if (!entState.buildingAI)
     87    if (!template.buildingAI || type != "Ranged")
    6388        return timeString;
    6489
    65     let arrows = entState.buildingAI.arrowCount;
    66     let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrow)s", "%(arrowcount)s %(arrow)s", arrows), {
     90    // Show either current rate from simulation or default count if the sim is not running
     91    let arrows = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount;
     92    let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrows)s", "%(arrowcount)s %(arrows)s", arrows), {
    6793        "arrowcount": arrows,
    68         "arrow": g_TooltipTextFormats.unit[0] + translatePlural("arrow", "arrows", arrows) + g_TooltipTextFormats.unit[1]
     94        "arrows": unitFont(translatePlural("arrow", "arrows", arrows))
    6995    });
    7096
    7197    return sprintf(translate("%(arrowString)s / %(timeString)s"), {
    7298        "arrowString": arrowString,
    7399        "timeString": timeString
    74100    });
    75101}
    76102
    77 // Converts an armor level into the actual reduction percentage
     103/**
     104 * Converts an armor level into the actual reduction percentage
     105 */
    78106function armorLevelToPercentageString(level)
    79107{
    80     return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
    81     //  return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation.
     108    return sprintf(translate("%(percentage)s%%"), {
     109        "percentage": (100 - Math.round(Math.pow(0.9, level) * 100))
     110    });
    82111}
    83112
    84 function getArmorTooltip(dmg)
     113function getArmorTooltip(template)
    85114{
    86     let label = g_TooltipTextFormats.header[0] + translate("Armor:") + g_TooltipTextFormats.header[1];
    87     if (!dmg)
     115    // TODO showns none currently for techs
     116    let label = headerFont(translate("Armor:"));
     117
     118    if (!template.armour)
    88119        return sprintf(translate("%(label)s %(details)s"), {
    89120            "label": label,
    90121            "details": '[font="sans-12"]' + translate("(None)") + '[/font]'
    91122        });
    92123
    93     let dmgArray = [];
    94     if (dmg.hack)
    95         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
    96             "damage": dmg.hack.toFixed(1),
    97             "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1],
    98             "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.hack) }) + '[/font]'
    99         }));
    100 
    101     if (dmg.pierce)
    102         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
    103             "damage": dmg.pierce.toFixed(1),
    104             "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1],
    105             "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.pierce) }) + '[/font]'
    106         }));
    107 
    108     if (dmg.crush)
    109         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
    110             "damage": dmg.crush.toFixed(1),
    111             "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1],
    112             "armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.crush) }) + '[/font]'
    113         }));
    114 
    115124    return sprintf(translate("%(label)s %(details)s"), {
    116125        "label": label,
    117         "details": dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]')
     126        "details":
     127            Object.keys(g_DamageTypes).filter(
     128                dmgType => template.armour[dmgType]).map(
     129                dmgType => sprintf(
     130                    translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     131                        "damage": template.armour[dmgType].toFixed(1),
     132                        "damageType": unitFont(g_DamageTypes[dmgType]),
     133                        "armorPercentage":
     134                            '[font="sans-10"]' +
     135                            sprintf(translate("(%(armorPercentage)s)"), {
     136                                "armorPercentage": armorLevelToPercentageString(template.armour[dmgType])
     137                            }) + '[/font]'
     138                    })
     139                ).join(commaFont(translate(", ")))
    118140    });
    119141}
    120142
    121143function damageTypesToText(dmg)
    122144{
    123145    if (!dmg)
    124146        return '[font="sans-12"]' + translate("(None)") + '[/font]';
    125147
    126     let dmgArray = [];
    127     if (dmg.hack)
    128         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    129             "damage": dmg.hack.toFixed(1),
    130             "damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1]
    131         }));
    132 
    133     if (dmg.pierce)
    134         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    135             "damage": dmg.pierce.toFixed(1),
    136             "damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1]
    137         }));
    138 
    139     if (dmg.crush)
    140         dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
    141             "damage": dmg.crush.toFixed(1),
    142             "damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1]
    143         }));
    144 
    145     return dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]');
     148    return Object.keys(g_DamageTypes).filter(
     149        dmgType => dmg[dmgType]).map(
     150        dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
     151            "damage": dmg[dmgType].toFixed(1),
     152            "damageType": unitFont(g_DamageTypes[dmgType])
     153        })).join(commaFont(translate(", ")))
    146154}
    147155
    148156function getAttackTypeLabel(type)
    149157{
    150     if (type === "Charge") return translate("Charge Attack:");
    151     if (type === "Melee") return translate("Melee Attack:");
    152     if (type === "Ranged") return translate("Ranged Attack:");
    153     if (type === "Capture") return translate("Capture Attack:");
    154 
    155     warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { "attackType": type }));
    156     return translate("Attack:");
     158    if (!g_AttackTypes[type])
     159    {
     160        warn("Unexpected attack type:" + type);
     161        return translate("Attack:");
     162    }
     163    return g_AttackTypes[type];
    157164}
    158165
     166// TODO: should also show minRange and splash damage
    159167function getAttackTooltip(template)
    160168{
    161     let attacks = [];
    162169    if (!template.attack)
    163170        return "";
    164171
    165     let rateLabel = g_TooltipTextFormats.header[0] + (template.buildingAI ? translate("Interval:") : translate("Rate:")) + g_TooltipTextFormats.header[1];
    166 
     172    let attacks = [];
    167173    for (let type in template.attack)
    168174    {
    169175        if (type == "Slaughter")
    170             continue; // Slaughter is not a real attack, so do not show it.
     176            continue; // Slaughter is used to kill animals, so do not show it.
    171177        if (type == "Charge")
    172178            continue; // Charging isn't implemented yet and shouldn't be displayed.
    173179
    174180        let rate = sprintf(translate("%(label)s %(details)s"), {
    175             "label": rateLabel,
     181            "label": headerFont(template.buildingAI && type == "Ranged" ?
     182                translate("Interval:") :
     183                translate("Rate:")),
    176184            "details": attackRateDetails(template, type)
    177185        });
    178186
    179         let attackLabel = g_TooltipTextFormats.header[0] + getAttackTypeLabel(type) + g_TooltipTextFormats.header[1];
    180         if (type == "Capture")
     187        let attackLabel = headerFont(getAttackTypeLabel(type));
     188        if (type == "Capture" || type != "Ranged")
    181189        {
    182190            attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
    183191                "attackLabel": attackLabel,
    184                 "details": template.attack[type].value,
    185                 "rate": rate
    186             }));
    187             continue;
    188         }
    189         if (type != "Ranged")
    190         {
    191             attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
    192                 "attackLabel": attackLabel,
    193                 "details": damageTypesToText(template.attack[type]),
     192                "details":
     193                    type == "Capture" ?
     194                        template.attack.Capture.value :
     195                        damageTypesToText(template.attack[type]),
    194196                "rate": rate
    195197            }));
    196198            continue;
    197199        }
    198200
    199201        let realRange = template.attack[type].elevationAdaptedRange;
    200202        let range = Math.round(template.attack[type].maxRange);
    201         let rangeLabel = g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1];
    202         let relativeRange = Math.round((realRange - range));
    203         let meters = g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", range) + g_TooltipTextFormats.unit[1];
     203        let relativeRange = realRange ? Math.round(realRange - range) : 0;
    204204
    205         if (relativeRange) // show if it is non-zero
    206             attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(rangeString)s (%(relative)s), %(rate)s"), {
    207                 "attackLabel": attackLabel,
    208                 "details": damageTypesToText(template.attack[type]),
    209                 "rangeLabel": rangeLabel,
    210                 "rangeString": sprintf(
    211                     translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), {
    212                         "range": range,
    213                         "meters": meters
    214                     }),
    215                 "relative": relativeRange > 0 ? "+" + relativeRange : relativeRange,
    216                 "rate": rate
    217             }));
    218         else
    219             attacks.push(sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s, %(rate)s"), {
    220                 "attackLabel": attackLabel,
    221                 "damageTypes": damageTypesToText(template.attack[type]),
    222                 "rangeLabel": rangeLabel,
    223                 "rangeString": sprintf(
    224                     translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), {
    225                         "range": range,
    226                         "meters": meters
    227                     }),
    228                 rate: rate
    229             }));
     205        let rangeString = relativeRange ?
     206            translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s (%(relative)s), %(rate)s") :
     207            translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s, %(rate)s");
     208
     209        attacks.push(sprintf(rangeString, {
     210            "attackLabel": attackLabel,
     211            "damageTypes": damageTypesToText(template.attack[type]),
     212            "rangeLabel": translate("Range:"),
     213            "rangeString": sprintf(
     214                translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), {
     215                    "range": range,
     216                    "meters": unitFont(translatePlural("meter", "meters", range))
     217                }),
     218            "rate": rate,
     219            "relative": relativeRange ? "+" + relativeRange : "",
     220        }));
    230221    }
    231 
    232222    return attacks.join("\n");
    233223}
    234224
    235 function getRepairRateTooltip(rate)
     225function getGarrisonTooltip(template)
     226{
     227    if (!template.garrisonHolder)
     228        return "";
     229
     230    return sprintf(translate("%(label)s: %(garrisonLimit)s"), {
     231        "label": headerFont(translate("Garrison Limit")),
     232        "garrisonLimit": template.garrisonHolder.capacity || template.garrisonHolder.max
     233    });
     234}
     235
     236function getProjectilesTooltip(template)
     237{
     238    if (!template.garrisonHolder || !template.buildingAI)
     239        return "";
     240
     241    let limit = Math.min(
     242        template.buildingAI.maxArrowCount || Infinity,
     243        template.buildingAI.defaultArrowCount +
     244            template.buildingAI.garrisonArrowMultiplier *
     245            (template.garrisonHolder.capacity || template.garrisonHolder.max)
     246    );
     247
     248    if (!limit)
     249        return "";
     250
     251    return [
     252        sprintf(translate("%(label)s: %(value)s"), {
     253            "label": headerFont(translate("Projectile Limit")),
     254            "value": limit
     255        }),
     256
     257        sprintf(translate("%(label)s: %(value)s"), {
     258            "label": headerFont(translateWithContext("projectiles", "Default")),
     259            "value": template.buildingAI.defaultArrowCount
     260        }),
     261
     262        sprintf(translate("%(label)s: %(value)s"), {
     263            "label": headerFont(translateWithContext("projectiles", "Per Unit")),
     264            "value": template.buildingAI.garrisonArrowMultiplier
     265        })
     266    ].join(commaFont(translate(", ")));
     267}
     268
     269function getRepairRateTooltip(template)
    236270{
    237     return "\n" + sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
    238         "repairRateLabel": g_TooltipTextFormats.header[0] + translate("Repair Rate:") + g_TooltipTextFormats.header[1],
    239         "value": Math.round(rate * 10) / 10,
    240         "health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1],
    241         "second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1],
    242         "worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1]
     271    if (!template.repairRate)
     272        return "";
     273
     274    return sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
     275        "repairRateLabel": headerFont(translate("Repair Rate:")),
     276        "value": Math.round(template.repairRate * 10) / 10,
     277        "health": unitFont(translate("health")),
     278        "second": unitFont(translate("second")),
     279        "worker": unitFont(translate("worker"))
    243280    });
    244281}
    245282
    246 function getBuildRateTooltip(rate)
     283function getBuildRateTooltip(template)
    247284{
    248     return "\n" + sprintf(translate("%(buildRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
    249         "buildRateLabel": g_TooltipTextFormats.header[0] + translate("Build Rate:") + g_TooltipTextFormats.header[1],
    250         "value": Math.round(rate * 10) / 10,
    251         "health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1],
    252         "second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1],
    253         "worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1]
     285    if (!template.buildRate)
     286        return "";
     287
     288    return sprintf(translate("%(buildRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
     289        "buildRateLabel": headerFont(translate("Build Rate:")),
     290        "value": Math.round(template.buildRate * 10) / 10,
     291        "health": unitFont(translate("health")),
     292        "second": unitFont(translate("second")),
     293        "worker": unitFont(translate("worker"))
    254294    });
    255295}
    256296
    257297/**
    258298 * Translates a cost component identifier as they are used internally
    function getBuildRateTooltip(rate)  
    261301function getCostComponentDisplayIcon(costComponentName)
    262302{
    263303    if (costComponentName in g_CostDisplayIcons)
    264304        return g_CostDisplayIcons[costComponentName];
    265305
    266     warn(sprintf("The specified cost component, ‘%(component)s’, is not currently supported.", { "component": costComponentName }));
     306    warn("The specified cost component, ‘" + costComponentName + "’, is not currently supported.");
    267307    return "";
    268308}
    269309
    270310/**
    271311 * Multiplies the costs for a template by a given batch size.
    function getEntityCostComponentsTooltipS  
    299339                "cost": totalCosts[type]
    300340            }));
    301341
    302342    return costs;
    303343}
     344function getGatherTooltip(template)
     345{
     346    if (!template.gather)
     347        return "";
     348
     349    return sprintf(translate("%(label)s %(details)s"), {
     350        "label": headerFont(translate("Gather Rates:")),
     351        "details":
     352            Object.keys(template.gather).map(
     353                type => sprintf(translate("%(resourceIcon)s %(rate)s"), {
     354                    "resourceIcon": getCostComponentDisplayIcon(type),
     355                    "rate": template.gather[type]
     356                })
     357            ).join("  ")
     358    });
     359}
    304360
    305361/**
    306362 * Returns an array of strings for a set of wall pieces. If the pieces share
    307363 * resource type requirements, output will be of the form '10 to 30 Stone',
    308364 * otherwise output will be, e.g. '10 Stone, 20 Stone, 30 Stone'.
    function getEntityCostTooltip(template,  
    398454 */
    399455function getPopulationBonusTooltip(template)
    400456{
    401457    let popBonus = "";
    402458    if (template.cost && template.cost.populationBonus)
    403         popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
    404             "label": g_TooltipTextFormats.header[0] + translate("Population Bonus:") + g_TooltipTextFormats.header[1],
     459        popBonus = sprintf(translate("%(label)s %(populationBonus)s"), {
     460            "label": headerFont(translate("Population Bonus:")),
    405461            "populationBonus": template.cost.populationBonus
    406462        });
    407463    return popBonus;
    408464}
    409465
    410466/**
    411467 * Returns a message with the amount of each resource needed to create an entity.
    412468 */
    413469function getNeededResourcesTooltip(resources)
    414470{
     471    if (!resources)
     472        return "";
     473
    415474    let formatted = [];
    416475    for (let resource in resources)
    417476        formatted.push(sprintf(translate("%(component)s %(cost)s"), {
    418477            "component": '[font="sans-12"]' + getCostComponentDisplayIcon(resource) + '[/font]',
    419478            "cost": resources[resource]
    420479        }));
    421480
    422     return '\n\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join("  ");
     481    return '\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join("  ");
    423482}
    424483
    425484function getSpeedTooltip(template)
    426485{
    427486    if (!template.speed)
    428487        return "";
    429488
    430     let label = g_TooltipTextFormats.header[0] + translate("Speed:") + g_TooltipTextFormats.header[1];
     489    let label = headerFont(translate("Speed:"));
    431490    let speeds = [];
    432491
    433492    if (template.speed.walk)
    434493        speeds.push(sprintf(translate("%(speed)s %(movementType)s"), {
    435494            "speed": Math.round(template.speed.walk),
    436             "movementType": g_TooltipTextFormats.unit[0] + translate("Walk") + g_TooltipTextFormats.unit[1]
     495            "movementType": unitFont(translate("Walk"))
    437496        }));
    438497
    439498    if (template.speed.run)
    440499        speeds.push(sprintf(translate("%(speed)s %(movementType)s"), {
    441500            "speed": Math.round(template.speed.run),
    442             "movementType": g_TooltipTextFormats.unit[0] + translate("Run") + g_TooltipTextFormats.unit[1]
     501            "movementType": unitFont(translate("Run"))
    443502        }));
    444503
    445504    return sprintf(translate("%(label)s %(speeds)s"), {
    446505        "label": label,
    447506        "speeds": speeds.join(translate(", "))
    function getSpeedTooltip(template)  
    451510function getHealerTooltip(template)
    452511{
    453512    if (!template.healer)
    454513        return "";
    455514
    456     let healer = [
     515    return [
    457516        sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.HP), {
    458             "label": g_TooltipTextFormats.header[0] + translate("Heal:") + g_TooltipTextFormats.header[1],
     517            "label": headerFont(translate("Heal:")),
    459518            "val": template.healer.HP,
    460519            // Translation: Short for hit points (or health points) that are healed in one healing action
    461             "unit": g_TooltipTextFormats.unit[0] + translatePlural("HP", "HP", template.healer.HP) + g_TooltipTextFormats.unit[1]
     520            "unit": unitFont(translatePlural("HP", "HP", template.healer.HP))
    462521        }),
    463522        sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Range), {
    464             "label": g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1],
     523            "label": headerFont(translate("Range:")),
    465524            "val": template.healer.Range,
    466             "unit": g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", template.healer.Range) + g_TooltipTextFormats.unit[1]
     525            "unit": unitFont(translatePlural("meter", "meters", template.healer.Range))
    467526        }),
    468527        sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Rate/1000), {
    469             "label": g_TooltipTextFormats.header[0] + translate("Rate:") + g_TooltipTextFormats.header[1],
     528            "label": headerFont(translate("Rate:")),
    470529            "val": template.healer.Rate/1000,
    471             "unit": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", template.healer.Rate/1000) + g_TooltipTextFormats.unit[1]
     530            "unit": unitFont(translatePlural("second", "seconds", template.healer.Rate / 1000))
    472531        })
    473     ];
    474     return healer.join(translate(", "));
     532    ].join(translate(", "));
    475533}
    476534
    477535function getAurasTooltip(template)
    478536{
    479537    if (!template.auras)
    480538        return "";
    481539
    482     let txt = "";
    483     for (let aura in template.auras)
    484         txt += '\n' + sprintf(translate("%(auralabel)s %(aurainfo)s"), {
    485             "auralabel": g_TooltipTextFormats.header[0] + sprintf(translate("%(auraname)s:"), {
     540    let tooltips = Object.keys(template.auras).map(
     541        aura => sprintf(translate("%(auralabel)s %(aurainfo)s"), {
     542            "auralabel": headerFont(sprintf(translate("%(auraname)s:"), {
    486543                "auraname": translate(template.auras[aura].name)
    487             }) + g_TooltipTextFormats.header[1],
    488             "aurainfo": g_TooltipTextFormats.body[0] + translate(template.auras[aura].description) + g_TooltipTextFormats.body[1]
    489         });
    490     return txt;
     544            })),
     545            "aurainfo": bodyFont(translate(template.auras[aura].description))
     546        }));
     547    return tooltips.join("\n");
    491548}
    492549
    493550function getEntityNames(template)
    494551{
    495552    if (template.name.specific)
    function getEntityNamesFormatted(templat  
    523580            names += '[font="sans-bold-16"] (' + generic + ')[/font]';
    524581    }
    525582    else if (generic)
    526583        names = '[font="sans-bold-16"]' + generic + "[/font]";
    527584    else
     585        // TODO: translate and proper string
    528586        names = "???";
    529587
    530588    return names;
    531589}
    532590
    533591function getVisibleEntityClassesFormatted(template)
    534592{
    535     let r = "";
    536     if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
    537     {
    538         r += '\n' + g_TooltipTextFormats.header[0] + translate("Classes:") + g_TooltipTextFormats.header[1];
    539         let classes = [];
    540         for (let c of template.visibleIdentityClasses)
    541             classes.push(translate(c));
    542         r += ' ' + g_TooltipTextFormats.body[0] + classes.join(translate(", ")) + g_TooltipTextFormats.body[1];
    543     }
     593    if (!template.visibleIdentityClasses || !template.visibleIdentityClasses.length)
     594        return "";
     595
     596
     597    let r = headerFont(translate("Classes:"));
     598
     599    let classes = [];
     600    for (let c of template.visibleIdentityClasses)
     601        classes.push(translate(c));
     602
     603    r += ' ' + bodyFont(classes.join(translate(", ")));
    544604    return r;
    545605}
  • binaries/data/mods/public/gui/session/selection_details.js

    function displaySingle(entState)  
    286286    {
    287287        Engine.GetGUIObjectByName("playerCivIcon").sprite = "";
    288288        Engine.GetGUIObjectByName("player").tooltip = "";
    289289    }
    290290
    291     // Icon image
    292291    // TODO: we should require all entities to have icons
    293292    Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "bkFillBlack";
    294293
    295     let armorString = getArmorTooltip(entState.armour);
     294    Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = [
     295        getAttackTooltip,
     296        getArmorTooltip,
     297        getRepairRateTooltip,
     298        getBuildRateTooltip,
     299        getGarrisonTooltip,
     300        getProjectilesTooltip
     301    ].map(func => func(entState)).filter(tip => tip).join("\n");
    296302
    297     // Attack and Armor
    298     Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = entState.attack ? (getAttackTooltip(entState) + "\n" + armorString) : armorString;
    299 
    300     // Repair Rate
    301     if (entState.repairRate)
    302         Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getRepairRateTooltip(entState.repairRate);
    303 
    304     // Build Rate
    305     if (entState.buildRate)
    306         Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getBuildRateTooltip(entState.buildRate);
    307 
    308     // Icon Tooltip
    309303    let iconTooltip = "";
    310304
    311305    if (genericName)
    312306        iconTooltip = "[font=\"sans-bold-16\"]" + genericName + "[/font]";
    313307
    function displaySingle(entState)  
    319313            template.visibleIdentityClasses.map(c => translate(c)).join(translate(", ")) +
    320314            "[/font]";
    321315    }
    322316
    323317    if (template.auras)
    324         iconTooltip += getAurasTooltip(template);
     318        iconTooltip += "\n" + getAurasTooltip(template);
    325319
    326320    if (template.tooltip)
    327         iconTooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
     321        iconTooltip += "\n" + getEntityTooltip(template);
    328322
    329323    Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltip;
    330324
    331325    // Unhide Details Area
    332326    Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
  • binaries/data/mods/public/gui/session/selection_panels.js

    g_SelectionPanels.Construction = {  
    299299            neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    300300                "cost": multiplyEntityCosts(template, 1),
    301301                "player": data.unitEntState.player
    302302            });
    303303
    304         let limits = getEntityLimitAndCount(data.playerState, data.item);
    305 
    306304        if (template.wallSet)
    307305            template.auras = GetTemplateData(template.wallSet.templates.long).auras;
    308306
    309307        data.button.onPress = function () { startBuildingPlacement(data.item, data.playerState); };
    310308
    311         let tooltip = getEntityNamesFormatted(template);
    312         tooltip += getVisibleEntityClassesFormatted(template);
    313         tooltip += getAurasTooltip(template);
    314 
    315         if (template.tooltip)
    316             tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
     309        let tooltips = [
     310            getEntityNamesFormatted,
     311            getVisibleEntityClassesFormatted,
     312            getAurasTooltip,
     313            getEntityTooltip,
     314            getEntityCostTooltip,
     315            getGarrisonTooltip,
     316            getProjectilesTooltip,
     317            getPopulationBonusTooltip
     318        ].map(func => func(template));
    317319
    318         tooltip += "\n" + getEntityCostTooltip(template);
    319         tooltip += getPopulationBonusTooltip(template);
    320 
    321         tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
     320        let limits = getEntityLimitAndCount(data.playerState, data.item);
     321        tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers));
    322322
    323323        if (!technologyEnabled)
    324             tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
     324            tooltips.push(sprintf(translate("Requires %(technology)s"), {
    325325                "technology": getEntityNames(GetTechnologyData(template.requiredTechnology))
    326             });
     326            }));
    327327
    328         if (neededResources)
    329             tooltip += getNeededResourcesTooltip(neededResources);
     328        tooltips.push(getNeededResourcesTooltip(neededResources));
    330329
    331         data.button.tooltip = tooltip;
     330        data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    332331
    333332        let modifier = "";
    334333        if (!technologyEnabled || limits.canBeAddedCount == 0)
    335334        {
    336335            data.button.enabled = false;
    g_SelectionPanels.Gate = {  
    527526    },
    528527    "setupButton": function(data)
    529528    {
    530529        data.button.onPress = function() {data.item.callback(data.item); };
    531530
    532         let tooltip = data.item.tooltip;
     531        let tooltips = [data.item.tooltip];
    533532        if (data.item.template)
    534533        {
    535534            data.template = GetTemplateData(data.item.template);
    536             data.wallCount = data.selection.reduce(function (count, ent) {
     535            data.wallCount = data.selection.reduce(count, ent => {
    537536                    let state = GetEntityState(ent);
    538537                    if (hasClass(state, "LongWall") && !state.gate)
    539538                        ++count;
    540539                    return count;
    541540                }, 0);
    542541
    543             tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
     542            tooltips.push(getEntityCostTooltip(data.template, data.wallCount));
    544543
    545544            data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    546545                "cost": multiplyEntityCosts(data.template, data.wallCount)
    547546            });
    548547
    549             if (data.neededResources)
    550                 tooltip += getNeededResourcesTooltip(data.neededResources);
     548            tooltips.push(getNeededResourcesTooltip(data.neededResources));
    551549        }
    552         data.button.tooltip = tooltip;
     550        data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    553551
    554552        data.button.enabled = controlsPlayer(data.unitEntState.player);
    555553        let gateIcon;
    556554        if (data.item.gate)
    557555        {
    g_SelectionPanels.Research = {  
    781779            });
    782780
    783781            let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
    784782            let icon = Engine.GetGUIObjectByName("unitResearchIcon[" + position + "]");
    785783
    786             let tooltip = getEntityNamesFormatted(template);
    787             if (template.tooltip)
    788                 tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
     784            let tooltips = [
     785                getEntityNamesFormatted(template),
     786                getEntityTooltip(template),
     787                getEntityCostTooltip(template)
     788            ];
    789789
    790             tooltip += "\n" + getEntityCostTooltip(template);
    791790            if (!requirementsPassed)
    792791            {
    793                 tooltip += "\n" + template.requirementsTooltip;
     792                let tip = template.requirementsTooltip;
    794793                if (template.classRequirements)
    795794                {
    796795                    let player = data.unitEntState.player;
    797796                    let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0;
    798797                    let remaining = template.classRequirements.number - current;
    799                     tooltip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), { "number": remaining });
     798                    tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), {
     799                        "number": remaining
     800                    });
    800801                }
     802                tooltips.push(tip);
    801803            }
    802             if (neededResources)
    803                 tooltip += getNeededResourcesTooltip(neededResources);
    804             button.tooltip = tooltip;
     804            tooltips.push(getNeededResourcesTooltip(neededResources));
     805            button.tooltip = tooltips.filter(tip => tip).join("\n");
    805806
    806807            button.onPress = function () {
    807808                addResearchToQueue(data.unitEntState.id, tech);
    808809            };
    809810
    g_SelectionPanels.Training = {  
    986987
    987988        data.button.onPress = function() { addTrainingToQueue(data.selection, data.item, data.playerState); };
    988989
    989990        data.countDisplay.caption = trainNum > 1 ? trainNum : "";
    990991
    991         let tooltip = "[font=\"sans-bold-16\"]" +
    992         colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) +
    993             "[/font]";
    994 
    995         tooltip += getEntityNamesFormatted(template);
    996         tooltip += getVisibleEntityClassesFormatted(template);
    997         tooltip += getAurasTooltip(template);
    998 
    999         if (template.tooltip)
    1000             tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
    1001 
    1002         tooltip += "\n" + getEntityCostTooltip(template, trainNum, data.unitEntState.id);
     992        let tooltips = [
     993            "[font=\"sans-bold-16\"]" +
     994                colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) +
     995                "[/font]" + getEntityNamesFormatted(template),
     996            getVisibleEntityClassesFormatted(template),
     997            getAurasTooltip(template),
     998            getEntityTooltip(template),
     999            getEntityCostTooltip(template, trainNum, data.unitEntState.id)
     1000        ];
    10031001
    10041002        let limits = getEntityLimitAndCount(data.playerState, data.item);
     1003        tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers));
    10051004
    1006         tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
    10071005        if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true")
    1008         {
    1009             if (template.health)
    1010                 tooltip += "\n[font=\"sans-bold-13\"]" + translate("Health:") + "[/font] " + template.health;
    1011             if (template.attack)
    1012                 tooltip += "\n" + getAttackTooltip(template);
    1013             if (template.armour)
    1014                 tooltip += "\n" + getArmorTooltip(template.armour);
    1015             if (template.speed)
    1016                 tooltip += "\n" + getSpeedTooltip(template);
    1017         }
     1006            tooltips.push(
     1007                getHealthTooltip(template),
     1008                getAttackTooltip(template),
     1009                getArmorTooltip(template),
     1010                getGarrisonTooltip(template),
     1011                getProjectilesTooltip(template),
     1012                getSpeedTooltip(template)
     1013            );
    10181014
    1019         tooltip += "[color=\"" + g_HotkeyColor + "\"]" +
     1015        tooltips.push(
     1016            "[color=\"" + g_HotkeyColor + "\"]" +
    10201017            formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) +
    1021             "[/color]";
     1018            "[/color]");
    10221019
    10231020        if (!technologyEnabled)
    1024         {
    1025             let techName = getEntityNames(GetTechnologyData(template.requiredTechnology));
    1026             tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { "technology": techName });
    1027         }
     1021            tooltips.push(sprintf(translate("Requires %(technology)s"), {
     1022                "technology": getEntityNames(GetTechnologyData(template.requiredTechnology))
     1023            }));
     1024
    10281025        if (neededResources)
    1029             tooltip += getNeededResourcesTooltip(neededResources);
     1026            tooltips.push(getNeededResourcesTooltip(neededResources));
    10301027
    1031         data.button.tooltip = tooltip;
     1028        data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    10321029
    10331030        let modifier = "";
    10341031        if (!technologyEnabled || limits.canBeAddedCount == 0)
    10351032        {
    10361033            data.button.enabled = false;
  • binaries/data/mods/public/gui/session/selection_panels_helpers.js

    function getStanceTooltip(name)  
    7777function formatLimitString(trainEntLimit, trainEntCount, trainEntLimitChangers)
    7878{
    7979    if (trainEntLimit == undefined)
    8080        return "";
    8181
    82     var text = "\n\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { "count": trainEntCount, "limit": trainEntLimit });
     82    var text = "\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), {
     83        "count": trainEntCount,
     84        "limit": trainEntLimit
     85    });
    8386
    8487    if (trainEntCount >= trainEntLimit)
    8588        text = "[color=\"red\"]" + text + "[/color]";
    8689
    8790    for (var c in trainEntLimitChangers)
    8891    {
    89         if (trainEntLimitChangers[c] > 0)
    90             text += "\n" + sprintf(translate("%(changer)s enlarges the limit with %(change)s."), { "changer": translate(c), "change": trainEntLimitChangers[c] });
    91         else if (trainEntLimitChangers[c] < 0)
    92             text += "\n" + sprintf(translate("%(changer)s lessens the limit with %(change)s."), { "changer": translate(c), "change": (-trainEntLimitChangers[c]) });
     92        if (!trainEntLimitChangers[c])
     93            continue;
     94
     95        let string = trainEntLimitChangers[c] > 0 ?
     96            translate("%(changer)s enlarges the limit with %(change)s.") :
     97            translate("%(changer)s lessens the limit with %(change)s.");
     98
     99        text += "\n" + sprintf(string, {
     100            "changer": translate(c),
     101            "change": trainEntLimitChangers[c]
     102        });
    93103    }
    94104    return text;
    95105}
    96106
    97107/**
    function formatBatchTrainingString(build  
    131141    var action = "[font=\"sans-bold-13\"]" + translate("Shift-click") + "[/font]";
    132142
    133143    // We need to display the batch details part if there is either more than
    134144    // one building with full batch or one building with the full batch and
    135145    // another with a partial batch
    136     if (buildingsCountToTrainFullBatch > 1 ||
    137         (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0))
    138     {
    139         if (remainderBatch > 0)
    140             return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."), {
    141                 "action": action,
    142                 "number": totalBatchTrainingCount,
    143                 "fullBatch": fullBatchesString,
    144                 "remainderBatch": remainderBatch
    145             }) + "[/font]";
    146 
    147         return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s)."), {
    148             "action": action,
    149             "number": totalBatchTrainingCount,
    150             "fullBatch": fullBatchesString
    151         }) + "[/font]";
    152     }
     146    var batchString =
     147        buildingsCountToTrainFullBatch > 1 ||
     148        buildingsCountToTrainFullBatch == 1 && remainderBatch > 0 ?
     149            translate("%(action)s to train %(number)s.") :
     150        remainderBatch ?
     151            translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s).") :
     152            translate("%(action)s to train %(number)s (%(fullBatch)s).");
    153153
    154     return "\n[font=\"sans-13\"]" + sprintf(translate("%(action)s to train %(number)s."), {
     154    return "[font=\"sans-13\"]" + sprintf(batchString, {
    155155        "action": action,
    156         "number": totalBatchTrainingCount
     156        "number": totalBatchTrainingCount,
     157        "fullBatch": fullBatchesString,
     158        "remainderBatch": remainderBatch
    157159    }) + "[/font]";
    158160}
    159161
    160162
  • binaries/data/mods/public/gui/session/session.js

    function updateHeroes()  
    822822    }
    823823}
    824824
    825825function createHeroTooltip(heroState, template)
    826826{
    827     let tooltip = "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]" + "\n" +
    828         sprintf(translate("%(label)s %(current)s / %(max)s"), {
    829             "label": "[font=\"sans-bold-13\"]" + translate("Health:") + "[/font]",
    830             "current": Math.ceil(heroState.hitpoints),
    831             "max": Math.ceil(heroState.maxHitpoints)
    832         });
    833 
    834     if (heroState.attack)
    835         tooltip += "\n" + getAttackTooltip(heroState);
    836 
    837     tooltip += "\n" + getArmorTooltip(heroState.armour);
    838 
    839     if (template.tooltip)
    840         tooltip += "\n" + template.tooltip;
    841 
    842     return tooltip;
     827    return [
     828        "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]" + "\n" +
     829            sprintf(translate("%(label)s %(current)s / %(max)s"), {
     830                "label": "[font=\"sans-bold-13\"]" + translate("Health:") + "[/font]",
     831                "current": Math.ceil(heroState.hitpoints),
     832                "max": Math.ceil(heroState.maxHitpoints)
     833            }),
     834        getAttackTooltip(heroState),
     835        getArmorTooltip(heroState),
     836        getEntityTooltip(heroState)
     837    ].filter(tip => tip).join("\n");
    843838}
    844839
    845840function displayHeroes()
    846841{
    847842    let buttons = Engine.GetGUIObjectByName("unitHeroPanel").children;
  • binaries/data/mods/public/gui/structree/draw.js

     
    11var g_DrawLimits = {}; // GUI limits. Populated by predraw()
    22
     3var g_TooltipFunctions = [
     4    getEntityNamesFormatted,
     5    getEntityCostTooltip,
     6    getEntityTooltip,
     7    getAurasTooltip,
     8    getHealthTooltip,
     9    getHealerTooltip,
     10    getAttackTooltip,
     11    getArmorTooltip,
     12    getGarrisonTooltip,
     13    getProjectilesTooltip,
     14    getSpeedTooltip,
     15    getGatherTooltip,
     16    getPopulationBonusTooltip
     17];
     18
    319/**
    420 * Draw the structree
    521 *
    622 * (Actually resizes and changes visibility of elements, and populates text)
    723 */
    function predraw()  
    361377
    362378/**
    363379 * Assemble a tooltip text
    364380 *
    365381 * @param  template Information about a Unit, a Structure or a Technology
    366  *
    367382 * @return  The tooltip text, formatted.
    368383 */
    369384function assembleTooltip(template)
    370385{
    371     let txt = getEntityNamesFormatted(template);
    372     txt += '\n' + getEntityCostTooltip(template, 1);
    373 
    374     if (template.tooltip)
    375         txt += '\n' + g_TooltipTextFormats.body[0] +  translate(template.tooltip) + g_TooltipTextFormats.body[1];
    376 
    377     if (template.auras)
    378         txt += getAurasTooltip(template);
    379 
    380     if (template.health)
    381         txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
    382             "label": g_TooltipTextFormats.header[0] + translate("Health:") + g_TooltipTextFormats.header[1],
    383             "details": template.health
    384         });
    385 
    386     if (template.healer)
    387         txt += '\n' + getHealerTooltip(template);
    388 
    389     if (template.attack)
    390         txt += '\n' + getAttackTooltip(template);
    391 
    392     if (template.armour)
    393         txt += '\n' + getArmorTooltip(template.armour);
    394 
    395     if (template.speed)
    396         txt += '\n' + getSpeedTooltip(template);
    397 
    398     if (template.gather)
    399     {
    400         let rates = [];
    401         for (let type in template.gather)
    402             rates.push(sprintf(translate("%(resourceIcon)s %(rate)s"), {
    403                 "resourceIcon": getCostComponentDisplayIcon(type),
    404                 "rate": template.gather[type]
    405             }));
    406 
    407         txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
    408             "label": g_TooltipTextFormats.header[0] + translate("Gather Rates:") + g_TooltipTextFormats.header[1],
    409             "details": rates.join("  ")
    410         });
    411     }
    412 
    413     txt += getPopulationBonusTooltip(template);
    414 
    415     return txt;
     386    return g_TooltipFunctions.map(func => func(template)).filter(tip => tip).join("\n");
    416387}
  • binaries/data/mods/public/simulation/templates/other/hellenic_epic_temple.xml

     
    2727  <Identity>
    2828    <Civ>gaia</Civ>
    2929    <GenericName>Epic Temple</GenericName>
    3030    <SpecificName>Naos Parthenos</SpecificName>
    3131    <History>The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis.</History>
    32     <Tooltip>Garrison up to 30 units to heal them at a quick rate.</Tooltip>
     32    <Tooltip>Garrison units to heal them at a quick rate.</Tooltip>
    3333  </Identity>
    3434  <Loot>
    3535    <xp>50</xp>
    3636    <food>0</food>
    3737    <wood>0</wood>
  • binaries/data/mods/public/simulation/templates/structures/athen_fortress.xml

     
    55    <Height>8.0</Height>
    66  </Footprint>
    77  <Identity>
    88    <Civ>athen</Civ>
    99    <SpecificName>Epiteíkhisma</SpecificName>
    10     <Tooltip>Build siege engines. Garrison up to 20 soldiers inside for stout defense.</Tooltip>
     10    <Tooltip>Build siege engines. Garrison soldiers inside for stout defense.</Tooltip>
    1111    <History>Fortresses (also called "Phroúria") were built to guard passes and atop hills in order to command plains and valleys below. One such Athenian fortress, Gyphtokastro, guarded the pass from Attica into Boeotia.</History>
    1212  </Identity>
    1313  <Obstruction>
    1414    <Static width="24.0" depth="26.0"/>
    1515  </Obstruction>
  • binaries/data/mods/public/simulation/templates/structures/athen_wonder.xml

     
    1212  </GarrisonHolder>
    1313  <Identity>
    1414    <Civ>athen</Civ>
    1515    <SpecificName>Naós Parthenṓn</SpecificName>
    1616    <History>The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis.</History>
    17     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at an extremely quick rate.</Tooltip>
     17    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at an extremely quick rate.</Tooltip>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="27.0" depth="57.0"/>
    2121  </Obstruction>
    2222  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/cart_temple.xml

     
    1111  </Footprint>
    1212  <Identity>
    1313    <Civ>cart</Civ>
    1414    <SpecificName>Maqdaš</SpecificName>
    1515    <History>What little we know of the Carthaginian religion has be pieced together from scattered sources. Tanit, a fertility goddess, was one of two principle gods in the Carthaginian pantheon, the other being her consort Ba'al, a deity of Phoenician origin.</History>
    16     <Tooltip>Train priestesses to heal your troops. Train Sacred Band pikemen. Garrison up to 20 units to heal them at a quick rate.</Tooltip>
     16    <Tooltip>Train priestesses to heal your troops. Train Sacred Band pikemen. Garrison units to heal them at a quick rate.</Tooltip>
    1717  </Identity>
    1818  <Obstruction>
    1919    <Static width="17.0" depth="30.0"/>
    2020  </Obstruction>
    2121  <ProductionQueue>
  • binaries/data/mods/public/simulation/templates/structures/cart_wonder.xml

     
    1212  </GarrisonHolder>
    1313  <Identity>
    1414   <Civ>cart</Civ>
    1515    <SpecificName>Temple of Ba'al Hammon</SpecificName>
    1616    <History>Dating from the 2nd Century BC, the Mausoleum of Atban in northern Tunisia is over twenty metres high and was built by the inhabitants of Dougga for a Numidian prince.</History>
    17     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at an extremely quick rate.</Tooltip>
     17    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at an extremely quick rate.</Tooltip>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="27.0" depth="57.0"/>
    2121  </Obstruction>
    2222  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/mace_fortress.xml

     
    1111    <Height>8.0</Height>
    1212  </Footprint>
    1313  <Identity>
    1414    <Civ>mace</Civ>
    1515    <SpecificName>Teíchisma</SpecificName>
    16     <Tooltip>Train Champions and Heroes. Garrison up to 20 soldiers inside for stout defense.</Tooltip>
     16    <Tooltip>Train Champions and Heroes. Garrison soldiers inside for stout defense.</Tooltip>
    1717    <History>The Akropolis was usually a fortified citadel in the upper part of the city. The Athenian Akropolis was renowned for its marvelous temples, among which was the Parthenon, while the Acro-Corinthus was highly prized by the Macedonians for its strategic location and good defenses. Fortresses (also called a "phrourion") were also built to guard passes and atop hills in order to command plains and valleys below.</History>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="24.0" depth="26.0"/>
    2121  </Obstruction>
  • binaries/data/mods/public/simulation/templates/structures/mace_wonder.xml

     
    1212  </GarrisonHolder>
    1313  <Identity>
    1414    <Civ>mace</Civ>
    1515    <SpecificName>Naós Parthenṓn</SpecificName>
    1616    <History>The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis.</History>
    17     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at an extremely quick rate.</Tooltip>
     17    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at an extremely quick rate.</Tooltip>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="27.0" depth="57.0"/>
    2121  </Obstruction>
    2222  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/ptol_barracks.xml

     
    1313    <SpawnEntityOnDeath>rubble/rubble_stone_5x5</SpawnEntityOnDeath>
    1414  </Health>
    1515  <Identity>
    1616    <Civ>ptol</Civ>
    1717    <SpecificName>ḥwt-n-mš'</SpecificName>
    18     <Tooltip>Train Egyptian and Middle-Eastern citizen-soldiers. Research training improvements. Garrison: 10.</Tooltip>
     18    <Tooltip>Train Egyptian and Middle-Eastern citizen-soldiers. Research training improvements.</Tooltip>
    1919  </Identity>
    2020  <Obstruction>
    2121    <Static width="20.0" depth="20.0"/>
    2222  </Obstruction>
    2323  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/ptol_wonder.xml

     
    1212  </GarrisonHolder>
    1313  <Identity>
    1414    <Civ>ptol</Civ>
    1515    <SpecificName>Temple of Edfu</SpecificName>
    1616    <History>The Temple of Edfu is an ancient Egyptian temple located on the west bank of the Nile in the city of Edfu which was known in Greco-Roman times as Apollonopolis Magna, after the chief god Horus-Apollo.The temple, dedicated to the falcon god Horus, was built in the Ptolemaic period between 237 and 57 BCE. In modern times, it is one of the best preserved temples of Egypt.</History>
    17     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at an extremely quick rate.</Tooltip>
     17    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at an extremely quick rate.</Tooltip>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="44.0" depth="60.0"/>
    2121  </Obstruction>
    2222  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/rome_temple_mars.xml

     
    1010  <Identity>
    1111    <Civ>rome</Civ>
    1212    <GenericName>Temple of Mars</GenericName>
    1313    <SpecificName>Aedes Martis</SpecificName>
    1414    <History>Roman temples in general were not meant for congregational worship. Instead the temple housed a statue of whatever deity the temple was dedicated to and what was needed to carry out the ceremonial and cultic practice necessary for worship. Any actual worship activity was performed outside.</History>
    15     <Tooltip>Train healers. Garrison up to 30 units to heal them at a quick rate.</Tooltip>
     15    <Tooltip>Train healers. Garrison units to heal them at a quick rate.</Tooltip>
    1616  </Identity>
    1717  <Obstruction>
    1818    <Static width="20.0" depth="40.0"/>
    1919  </Obstruction>
    2020  <TerritoryInfluence>
  • binaries/data/mods/public/simulation/templates/structures/rome_wonder.xml

     
    1111    <BuffHeal>8</BuffHeal>
    1212  </GarrisonHolder>
    1313  <Identity>
    1414    <Civ>rome</Civ>
    1515    <SpecificName>Aedes Iovis Optimi Maximi</SpecificName>
    16     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at an extremely quick rate.</Tooltip>
     16    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at an extremely quick rate.</Tooltip>
    1717  </Identity>
    1818  <Obstruction>
    1919    <Static width="20.0" depth="40.0"/>
    2020  </Obstruction>
    2121  <VisualActor>
  • binaries/data/mods/public/simulation/templates/structures/sele_fortress.xml

     
    55    <Height>8.0</Height>
    66  </Footprint>
    77  <Identity>
    88    <Civ>sele</Civ>
    99    <SpecificName>Phrourion</SpecificName>
    10     <Tooltip>Build siege engines and train Champions. Garrison up to 20 soldiers inside for stout defense.</Tooltip>
     10    <Tooltip>Build siege engines and train Champions. Garrison soldiers inside for stout defense.</Tooltip>
    1111    <History>Fortresses were built to guard passes and atop hills in order to command plains and valleys below.</History>
    1212  </Identity>
    1313  <Obstruction>
    1414    <Static width="24.0" depth="26.0"/>
    1515  </Obstruction>
  • binaries/data/mods/public/simulation/templates/structures/spart_wonder.xml

     
    1212  </GarrisonHolder>
    1313  <Identity>
    1414    <Civ>spart</Civ>
    1515    <SpecificName>Naós Parthenṓn</SpecificName>
    1616    <History>The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis.</History>
    17     <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at a quick rate.</Tooltip>
     17    <Tooltip>Bring glory to your civilization and add large tracts of land to your empire. Garrison units to heal them at a quick rate.</Tooltip>
    1818  </Identity>
    1919  <Obstruction>
    2020    <Static width="27.0" depth="57.0"/>
    2121  </Obstruction>
    2222  <VisualActor>
  • binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

     
    6464    <Max>3000</Max>
    6565    <SpawnEntityOnDeath>rubble/rubble_stone_6x6</SpawnEntityOnDeath>
    6666  </Health>
    6767  <Identity>
    6868    <GenericName>Civic Center</GenericName>
    69     <Tooltip>Build to acquire large tracts of territory. Train citizens. Garrison: 20.</Tooltip>
     69    <Tooltip>Build to acquire large tracts of territory. Train citizens.</Tooltip>
    7070    <Classes datatype="tokens">Defensive CivCentre</Classes>
    7171    <VisibleClasses datatype="tokens">CivilCentre</VisibleClasses>
    7272    <Icon>structures/civic_centre.png</Icon>
    7373  </Identity>
    7474  <Loot>
  • binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml

     
    2727    <Max>2000</Max>
    2828    <SpawnEntityOnDeath>rubble/rubble_stone_4x6</SpawnEntityOnDeath>
    2929  </Health>
    3030  <Identity>
    3131    <GenericName>Temple</GenericName>
    32     <Tooltip>Train healers. Garrison up to 20 units to heal them at a quick rate (3 HP per second). Research healing and religious improvements.</Tooltip>
     32    <Tooltip>Train healers. Garrison units to heal them at a quick rate (3 HP per second). Research healing and religious improvements.</Tooltip>
    3333    <VisibleClasses datatype="tokens">Town Temple</VisibleClasses>
    3434    <Icon>structures/temple.png</Icon>
    3535    <RequiredTechnology>phase_town</RequiredTechnology>
    3636  </Identity>
    3737  <Loot>
  • binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml

     
    2525    <Max>2000</Max>
    2626    <SpawnEntityOnDeath>rubble/rubble_stone_4x4</SpawnEntityOnDeath>
    2727  </Health>
    2828  <Identity>
    2929    <GenericName>Barracks</GenericName>
    30     <Tooltip>Train citizen-soldiers. Research training improvements. Garrison: 10.</Tooltip>
     30    <Tooltip>Train citizen-soldiers. Research training improvements.</Tooltip>
    3131    <VisibleClasses datatype="tokens">Village Barracks</VisibleClasses>
    3232    <Icon>structures/barracks.png</Icon>
    3333    <RequiredTechnology>phase_village</RequiredTechnology>
    3434  </Identity>
    3535  <Loot>
  • binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml

     
    5959    <Max>4200</Max>
    6060    <SpawnEntityOnDeath>rubble/rubble_stone_6x6</SpawnEntityOnDeath>
    6161  </Health>
    6262  <Identity>
    6363    <GenericName>Fortress</GenericName>
    64     <Tooltip>Train heroes, champions, and siege weapons. Research siege weapon improvements. Garrison: 20.</Tooltip>
     64    <Tooltip>Train heroes, champions, and siege weapons. Research siege weapon improvements.</Tooltip>
    6565    <Classes datatype="tokens">GarrisonFortress</Classes>
    6666    <VisibleClasses datatype="tokens">Defensive City Fortress</VisibleClasses>
    6767    <Icon>structures/fortress.png</Icon>
    6868    <RequiredTechnology>phase_city</RequiredTechnology>
    6969  </Identity>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml

     
    4545  </Health>
    4646  <Identity>
    4747    <GenericName>Light Warship</GenericName>
    4848    <Classes datatype="tokens">Warship Light Bow Ranged</Classes>
    4949    <RequiredTechnology>phase_town</RequiredTechnology>
    50     <Tooltip>Garrison up to 20 units for transport. Garrison increases the firepower up to 10 arrows.</Tooltip>
     50    <Tooltip>Garrison units for transport and to increase firepower.</Tooltip>
    5151  </Identity>
    5252  <ResourceGatherer disable=""/>
    5353  <Sound>
    5454    <SoundGroups>
    5555      <attack>attack/weapon/arrowfly.xml</attack>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml

     
    2121      </Splash>
    2222    </Ranged>
    2323  </Attack>
    2424  <BuildingAI>
    2525    <DefaultArrowCount>1</DefaultArrowCount>
    26     <MaxArrowCount>11</MaxArrowCount>
     26    <MaxArrowCount>10</MaxArrowCount>
    2727    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2828    <GarrisonArrowClasses>Catapult</GarrisonArrowClasses>
    2929  </BuildingAI>
    3030  <Cost>
    3131    <Population>3</Population>
     
    5252  <Health>
    5353    <Max>2000</Max>
    5454  </Health>
    5555  <Identity>
    5656    <GenericName>Heavy Warship</GenericName>
    57     <Tooltip>Garrison up to 10 catapults to increase fire power.</Tooltip>
     57    <Tooltip>Garrison units for transport and to increase firepower.</Tooltip>
    5858    <Classes datatype="tokens">Warship Heavy Ranged</Classes>
    5959    <RequiredTechnology>phase_city</RequiredTechnology>
    6060  </Identity>
    6161  <Sound>
    6262    <SoundGroups>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml

     
    4545  </Health>
    4646  <Identity>
    4747    <GenericName>Medium Warship</GenericName>
    4848    <VisibleClasses datatype="tokens">Warship Medium Ranged</VisibleClasses>
    4949    <RequiredTechnology>phase_town</RequiredTechnology>
    50     <Tooltip>Garrison up to 30 units for transport. Garrison increases the firepower up to 13 arrows.</Tooltip>
     50    <Tooltip>Garrison units for transport and to increase firepower.</Tooltip>
    5151  </Identity>
    5252  <ResourceGatherer disable=""/>
    5353  <Sound>
    5454    <SoundGroups>
    5555      <attack>attack/weapon/arrowfly.xml</attack>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml

     
    5353    <Max>500</Max>
    5454  </Health>
    5555  <Identity>
    5656    <GenericName>Siege Tower</GenericName>
    5757    <Classes datatype="tokens">Ranged SiegeTower</Classes>
    58     <Tooltip>Garrison up to 20 infantry inside to increase arrow count from 0 to 10.</Tooltip>
     58    <Tooltip>Garrison units for transport and to increase firepower.</Tooltip>
    5959  </Identity>
    6060  <Selectable>
    6161    <Overlay>
    6262      <Texture>
    6363        <MainTexture>circle/256x256.png</MainTexture>