2013-08-25 20:06:08 -07:00
|
|
|
function layoutSelectionSingle()
|
|
|
|
|
{
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
|
|
|
|
|
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function layoutSelectionMultiple()
|
|
|
|
|
{
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
|
|
|
|
|
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-25 00:24:05 -07:00
|
|
|
// Updates the health bar of garrisoned units
|
2016-09-25 12:38:10 -07:00
|
|
|
function updateGarrisonHealthBar(entState, selection)
|
2016-04-25 00:24:05 -07:00
|
|
|
{
|
|
|
|
|
if (!entState.garrisonHolder)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Summing up the Health of every single unit
|
2016-09-25 12:38:10 -07:00
|
|
|
let totalGarrisonHealth = 0;
|
|
|
|
|
let maxGarrisonHealth = 0;
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const selEnt of selection)
|
2016-04-25 00:24:05 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const selEntState = GetEntityState(selEnt);
|
2016-04-25 00:24:05 -07:00
|
|
|
if (selEntState.garrisonHolder)
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const ent of selEntState.garrisonHolder.entities)
|
2016-04-25 00:24:05 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const state = GetEntityState(ent);
|
2016-09-25 12:38:10 -07:00
|
|
|
totalGarrisonHealth += state.hitpoints || 0;
|
|
|
|
|
maxGarrisonHealth += state.maxHitpoints || 0;
|
2016-04-25 00:24:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configuring the health bar
|
2025-05-10 07:21:01 -07:00
|
|
|
const healthGarrison = Engine.GetGUIObjectByName("healthGarrison");
|
2016-09-25 12:38:10 -07:00
|
|
|
healthGarrison.hidden = totalGarrisonHealth <= 0;
|
|
|
|
|
if (totalGarrisonHealth > 0)
|
2016-04-25 00:24:05 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const healthBarGarrison = Engine.GetGUIObjectByName("healthBarGarrison");
|
2025-05-29 15:23:46 -07:00
|
|
|
healthBarGarrison.size.rtop = 100 - 100 * Math.max(0, Math.min(1, totalGarrisonHealth / maxGarrisonHealth));
|
2017-04-09 16:10:50 -07:00
|
|
|
healthGarrison.tooltip = getCurrentHealthTooltip({
|
|
|
|
|
"hitpoints": totalGarrisonHealth,
|
|
|
|
|
"maxHitpoints": maxGarrisonHealth
|
2016-04-25 00:24:05 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-25 20:06:08 -07:00
|
|
|
// Fills out information that most entities have
|
2015-11-28 03:23:04 -08:00
|
|
|
function displaySingle(entState)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2026-02-21 09:03:34 -08:00
|
|
|
const primaryName = g_SpecificNamesPrimary ? entState.name.specific : entState.name.generic;
|
2021-03-24 23:58:47 -07:00
|
|
|
let secondaryName;
|
|
|
|
|
if (g_ShowSecondaryNames)
|
2026-02-21 09:03:34 -08:00
|
|
|
secondaryName = g_SpecificNamesPrimary ? entState.name.generic : entState.name.specific;
|
2021-03-24 23:58:47 -07:00
|
|
|
|
|
|
|
|
// If packed, add that to the generic name (reduces template clutter).
|
2026-02-21 09:03:34 -08:00
|
|
|
if (entState.pack?.state === "packed")
|
2021-03-24 23:58:47 -07:00
|
|
|
{
|
|
|
|
|
if (secondaryName && g_ShowSecondaryNames)
|
|
|
|
|
secondaryName = sprintf(translate("%(secondaryName)s — Packed"), { "secondaryName": secondaryName });
|
|
|
|
|
else
|
|
|
|
|
secondaryName = sprintf(translate("Packed"));
|
|
|
|
|
}
|
2025-05-10 07:21:01 -07:00
|
|
|
const playerState = g_Players[entState.player];
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const civName = g_CivData[playerState.civ].Name;
|
|
|
|
|
const civEmblem = g_CivData[playerState.civ].Emblem;
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2015-12-20 09:15:21 -08:00
|
|
|
let playerName = playerState.name;
|
2013-08-25 20:06:08 -07:00
|
|
|
|
|
|
|
|
// Indicate disconnected players by prefixing their name
|
2016-02-19 06:36:36 -08:00
|
|
|
if (g_Players[entState.player].offline)
|
2015-12-20 09:15:21 -08:00
|
|
|
playerName = sprintf(translate("\\[OFFLINE] %(player)s"), { "player": playerName });
|
2013-08-25 20:06:08 -07:00
|
|
|
|
|
|
|
|
// Rank
|
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
|
|
|
if (entState.rank && entState.identityClasses)
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
|
|
|
const rankTooltip = (entState.rank && GetTechnologyData("unit_" + entState.rank.toLowerCase())?.tooltip) || "";
|
2015-12-20 09:15:21 -08:00
|
|
|
Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), {
|
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
|
|
|
"rank": translateWithContext("Rank", entState.rank)
|
|
|
|
|
}) + rankTooltip;
|
|
|
|
|
Engine.GetGUIObjectByName("rankIcon").sprite = "stretched:session/icons/ranks/" + entState.rank + ".png";
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("rankIcon").hidden = false;
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2013-08-25 20:06:08 -07:00
|
|
|
else
|
|
|
|
|
{
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("rankIcon").hidden = true;
|
|
|
|
|
Engine.GetGUIObjectByName("rankIcon").tooltip = "";
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2015-01-09 09:08:12 -08:00
|
|
|
|
2019-09-15 02:24:52 -07:00
|
|
|
if (entState.statusEffects)
|
|
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const statusEffectsSection = Engine.GetGUIObjectByName("statusEffectsIcons");
|
2020-01-27 08:51:25 -08:00
|
|
|
statusEffectsSection.hidden = false;
|
2025-05-10 07:21:01 -07:00
|
|
|
const statusIcons = statusEffectsSection.children;
|
2019-09-15 02:24:52 -07:00
|
|
|
let i = 0;
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const effectCode in entState.statusEffects)
|
2019-09-15 02:24:52 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const effect = entState.statusEffects[effectCode];
|
2019-09-15 02:24:52 -07:00
|
|
|
statusIcons[i].hidden = false;
|
2020-11-11 12:07:30 -08:00
|
|
|
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + g_StatusEffectsMetadata.getIcon(effect.baseCode) + ".png";
|
|
|
|
|
statusIcons[i].tooltip = getStatusEffectsTooltip(effect.baseCode, effect, false);
|
2025-05-29 15:23:46 -07:00
|
|
|
statusIcons[i].size.top = i * 18;
|
|
|
|
|
statusIcons[i].size.bottom = i * 18 + 16;
|
2020-06-09 04:47:16 -07:00
|
|
|
|
|
|
|
|
if (++i >= statusIcons.length)
|
|
|
|
|
break;
|
2019-09-15 02:24:52 -07:00
|
|
|
}
|
|
|
|
|
for (; i < statusIcons.length; ++i)
|
|
|
|
|
statusIcons[i].hidden = true;
|
|
|
|
|
}
|
2020-01-27 08:51:25 -08:00
|
|
|
else
|
|
|
|
|
Engine.GetGUIObjectByName("statusEffectsIcons").hidden = true;
|
2019-09-15 02:24:52 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const showHealth = entState.hitpoints;
|
|
|
|
|
const showResource = entState.resourceSupply;
|
|
|
|
|
const showCapture = entState.capturePoints;
|
2017-05-27 13:38:05 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const healthSection = Engine.GetGUIObjectByName("healthSection");
|
|
|
|
|
const captureSection = Engine.GetGUIObjectByName("captureSection");
|
|
|
|
|
const resourceSection = Engine.GetGUIObjectByName("resourceSection");
|
|
|
|
|
const sectionPosTop = Engine.GetGUIObjectByName("sectionPosTop");
|
|
|
|
|
const sectionPosMiddle = Engine.GetGUIObjectByName("sectionPosMiddle");
|
|
|
|
|
const sectionPosBottom = Engine.GetGUIObjectByName("sectionPosBottom");
|
2017-05-27 13:38:05 -07:00
|
|
|
|
2014-01-02 08:22:03 -08:00
|
|
|
// Hitpoints
|
2017-05-27 13:38:05 -07:00
|
|
|
healthSection.hidden = !showHealth;
|
|
|
|
|
if (showHealth)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2025-05-29 15:23:46 -07:00
|
|
|
Engine.GetGUIObjectByName("healthBar").size.rright = 100 * Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
|
2014-04-20 13:51:48 -07:00
|
|
|
Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
|
2015-12-20 09:15:21 -08:00
|
|
|
"hitpoints": Math.ceil(entState.hitpoints),
|
2016-04-09 00:35:20 -07:00
|
|
|
"maxHitpoints": Math.ceil(entState.maxHitpoints)
|
2014-04-20 13:51:48 -07:00
|
|
|
});
|
2017-05-27 13:38:05 -07:00
|
|
|
|
|
|
|
|
healthSection.size = sectionPosTop.size;
|
|
|
|
|
captureSection.size = showResource ? sectionPosMiddle.size : sectionPosBottom.size;
|
|
|
|
|
resourceSection.size = showResource ? sectionPosBottom.size : sectionPosMiddle.size;
|
|
|
|
|
}
|
2020-12-05 23:22:51 -08:00
|
|
|
else if (showResource)
|
2017-05-27 13:38:05 -07:00
|
|
|
{
|
|
|
|
|
captureSection.size = sectionPosBottom.size;
|
|
|
|
|
resourceSection.size = sectionPosTop.size;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
2020-12-05 23:22:51 -08:00
|
|
|
else if (showCapture)
|
|
|
|
|
captureSection.size = sectionPosTop.size;
|
2015-04-20 00:45:45 -07:00
|
|
|
|
|
|
|
|
// CapturePoints
|
2017-05-27 13:38:05 -07:00
|
|
|
captureSection.hidden = !entState.capturePoints;
|
2015-04-20 00:45:45 -07:00
|
|
|
if (entState.capturePoints)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
const setCaptureBarPart = function(playerID, startSize)
|
|
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const unitCaptureBar = Engine.GetGUIObjectByName("captureBar[" + playerID + "]");
|
2015-04-20 00:45:45 -07:00
|
|
|
|
2025-05-29 15:23:46 -07:00
|
|
|
const width = 100 * Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
|
|
|
|
|
unitCaptureBar.size.rleft = startSize;
|
|
|
|
|
unitCaptureBar.size.rright = startSize + width;
|
|
|
|
|
|
2019-10-11 05:28:12 -07:00
|
|
|
unitCaptureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(playerID, 128);
|
2017-10-21 10:31:05 -07:00
|
|
|
unitCaptureBar.hidden = false;
|
2025-05-29 15:23:46 -07:00
|
|
|
return startSize + width;
|
2015-04-23 11:58:39 -07:00
|
|
|
};
|
2015-04-20 00:45:45 -07:00
|
|
|
|
|
|
|
|
// first handle the owner's points, to keep those points on the left for clarity
|
|
|
|
|
let size = setCaptureBarPart(entState.player, 0);
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const i in entState.capturePoints)
|
2015-04-20 00:45:45 -07:00
|
|
|
if (i != entState.player)
|
|
|
|
|
size = setCaptureBarPart(i, size);
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const captureText = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
|
2015-12-20 09:15:21 -08:00
|
|
|
"capturePoints": Math.ceil(entState.capturePoints[entState.player]),
|
2016-04-09 00:35:20 -07:00
|
|
|
"maxCapturePoints": Math.ceil(entState.maxCapturePoints)
|
2015-04-20 00:45:45 -07:00
|
|
|
});
|
2017-05-27 13:38:05 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const showSmallCapture = showResource && showHealth;
|
2017-10-21 10:31:05 -07:00
|
|
|
Engine.GetGUIObjectByName("captureStats").caption = showSmallCapture ? "" : captureText;
|
2022-11-27 19:17:37 -08:00
|
|
|
Engine.GetGUIObjectByName("captureTooltip").tooltip = showSmallCapture ? getCurrentCaptureTooltip(entState) : translate("Capture Points");
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2015-01-09 09:08:12 -08:00
|
|
|
|
2013-08-25 20:06:08 -07:00
|
|
|
// Experience
|
2015-04-20 00:45:45 -07:00
|
|
|
Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
|
2013-08-25 20:06:08 -07:00
|
|
|
if (entState.promotion)
|
|
|
|
|
{
|
2025-05-29 15:23:46 -07:00
|
|
|
Engine.GetGUIObjectByName("experienceBar").size.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / (+entState.promotion.req || 1))));
|
2016-08-27 08:33:22 -07:00
|
|
|
|
2014-01-02 08:22:03 -08:00
|
|
|
if (entState.promotion.curr < entState.promotion.req)
|
2014-04-20 13:51:48 -07:00
|
|
|
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
|
2015-12-20 09:15:21 -08:00
|
|
|
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
|
|
|
|
|
"current": Math.floor(entState.promotion.curr),
|
2021-10-31 00:00:39 -07:00
|
|
|
"required": Math.ceil(entState.promotion.req)
|
2014-04-20 13:51:48 -07:00
|
|
|
});
|
|
|
|
|
else
|
|
|
|
|
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
|
2015-12-20 09:15:21 -08:00
|
|
|
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
|
|
|
|
|
"current": Math.floor(entState.promotion.curr)
|
2014-04-20 13:51:48 -07:00
|
|
|
});
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resource stats
|
2017-05-27 13:38:05 -07:00
|
|
|
resourceSection.hidden = !showResource;
|
2013-08-25 20:06:08 -07:00
|
|
|
if (entState.resourceSupply)
|
|
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
sprintf(translate("%(amount)s / %(max)s"), {
|
|
|
|
|
"amount": Math.ceil(+entState.resourceSupply.amount),
|
|
|
|
|
"max": entState.resourceSupply.max
|
|
|
|
|
});
|
2015-12-20 09:15:21 -08:00
|
|
|
|
2025-05-29 15:23:46 -07:00
|
|
|
Engine.GetGUIObjectByName("resourceBar").size.rright = entState.resourceSupply.isInfinite ? 100 :
|
2017-10-21 10:31:05 -07:00
|
|
|
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
|
2016-11-19 06:29:45 -08:00
|
|
|
|
|
|
|
|
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), {
|
2021-03-02 23:47:38 -08:00
|
|
|
"resource": resourceNameFirstWord(entState.resourceSupply.type.generic)
|
2016-11-19 06:29:45 -08:00
|
|
|
});
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("resourceStats").caption = resources;
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const resourceCarryingIcon = Engine.GetGUIObjectByName("resourceCarryingIcon");
|
|
|
|
|
const resourceCarryingText = Engine.GetGUIObjectByName("resourceCarryingText");
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.hidden = false;
|
|
|
|
|
resourceCarryingText.hidden = false;
|
|
|
|
|
|
2014-01-02 08:22:03 -08:00
|
|
|
// Resource carrying
|
2013-08-25 20:06:08 -07:00
|
|
|
if (entState.resourceCarrying && entState.resourceCarrying.length)
|
|
|
|
|
{
|
|
|
|
|
// We should only be carrying one resource type at once, so just display the first
|
2025-05-10 07:21:01 -07:00
|
|
|
const carried = entState.resourceCarrying[0];
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.sprite = "stretched:session/icons/resources/" + carried.type + ".png";
|
|
|
|
|
resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
|
|
|
|
|
resourceCarryingIcon.tooltip = "";
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
// Use the same indicators for traders
|
|
|
|
|
else if (entState.trader && entState.trader.goods.amount)
|
|
|
|
|
{
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.sprite = "stretched:session/icons/resources/" + entState.trader.goods.type + ".png";
|
2015-12-20 09:15:21 -08:00
|
|
|
let totalGain = entState.trader.goods.amount.traderGain;
|
2013-08-25 20:06:08 -07:00
|
|
|
if (entState.trader.goods.amount.market1Gain)
|
|
|
|
|
totalGain += entState.trader.goods.amount.market1Gain;
|
|
|
|
|
if (entState.trader.goods.amount.market2Gain)
|
|
|
|
|
totalGain += entState.trader.goods.amount.market2Gain;
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingText.caption = totalGain;
|
|
|
|
|
resourceCarryingIcon.tooltip = sprintf(translate("Gain: %(gain)s"), {
|
2017-02-23 15:34:35 -08:00
|
|
|
"gain": getTradingTooltip(entState.trader.goods.amount)
|
|
|
|
|
});
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
|
|
|
|
// And for number of workers
|
2018-01-29 16:02:10 -08:00
|
|
|
else if (entState.foundation)
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
|
|
|
|
|
resourceCarryingIcon.tooltip = getBuildTimeTooltip(entState);
|
2020-12-26 22:56:37 -08:00
|
|
|
resourceCarryingText.caption = entState.foundation.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
|
|
|
|
|
"number": entState.foundation.numBuilders,
|
|
|
|
|
"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.foundation.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
|
|
|
|
|
}) : "";
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2018-03-11 10:07:12 -07:00
|
|
|
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
|
|
|
|
|
resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), {
|
2015-12-20 09:15:21 -08:00
|
|
|
"amount": entState.resourceSupply.numGatherers,
|
|
|
|
|
"max": entState.resourceSupply.maxGatherers
|
2018-03-11 10:27:07 -07:00
|
|
|
});
|
2014-04-20 13:51:48 -07:00
|
|
|
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2018-03-11 10:07:12 -07:00
|
|
|
else if (entState.repairable && entState.needsRepair)
|
|
|
|
|
{
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
|
|
|
|
|
resourceCarryingIcon.tooltip = getRepairTimeTooltip(entState);
|
2020-12-26 22:56:37 -08:00
|
|
|
resourceCarryingText.caption = entState.repairable.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
|
|
|
|
|
"number": entState.repairable.numBuilders,
|
|
|
|
|
"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.repairable.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
|
|
|
|
|
}) : "";
|
2018-03-11 10:07:12 -07:00
|
|
|
}
|
2014-01-02 08:22:03 -08:00
|
|
|
else
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2018-03-11 10:27:07 -07:00
|
|
|
resourceCarryingIcon.hidden = true;
|
|
|
|
|
resourceCarryingText.hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("player").caption = playerName;
|
2019-10-11 05:28:12 -07:00
|
|
|
|
|
|
|
|
Engine.GetGUIObjectByName("playerColorBackground").sprite =
|
|
|
|
|
"color:" + g_DiplomacyColors.getPlayerColor(entState.player, 128);
|
|
|
|
|
|
2021-10-12 13:25:09 -07:00
|
|
|
const hideSecondary = !secondaryName || primaryName == secondaryName;
|
|
|
|
|
|
|
|
|
|
const primaryObject = Engine.GetGUIObjectByName("primary");
|
|
|
|
|
primaryObject.caption = primaryName;
|
2025-05-29 15:23:46 -07:00
|
|
|
primaryObject.size.rbottom = hideSecondary ? 100 : 50;
|
2021-10-12 13:25:09 -07:00
|
|
|
|
|
|
|
|
const secondaryObject = Engine.GetGUIObjectByName("secondary");
|
|
|
|
|
secondaryObject.caption = hideSecondary ? "" :
|
2021-03-24 23:58:47 -07:00
|
|
|
sprintf(translate("(%(secondaryName)s)"), {
|
|
|
|
|
"secondaryName": secondaryName
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
});
|
2021-10-12 13:25:09 -07:00
|
|
|
secondaryObject.hidden = hideSecondary;
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2024-10-10 11:16:34 -07:00
|
|
|
const isGaia = playerState.civ == "gaia";
|
2021-04-11 04:27:53 -07:00
|
|
|
Engine.GetGUIObjectByName("playerCivIcon").sprite = isGaia ? "" : "cropped:1.0, 0.15625 center:grayscale:" + civEmblem;
|
2024-10-10 11:16:34 -07:00
|
|
|
|
|
|
|
|
if (isGaia)
|
|
|
|
|
{
|
|
|
|
|
Engine.GetGUIObjectByName("phaseEmblems").sprite = "";
|
|
|
|
|
Engine.GetGUIObjectByName("civilizationTooltip").tooltip = "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
let civilizationTooltip = civName;
|
|
|
|
|
let civPhaseEmblems = "session/panel_phase_emblems_hidden.png";
|
|
|
|
|
|
|
|
|
|
// Reveal phases to mutual allies and observers
|
|
|
|
|
if (g_ViewedPlayer == -1 || playerState.isMutualAlly[g_ViewedPlayer])
|
|
|
|
|
{
|
2025-05-03 05:12:49 -07:00
|
|
|
const civPhase = g_SimState.players[entState.player].phase;
|
2024-10-10 11:16:34 -07:00
|
|
|
civPhaseEmblems = "session/panel_phase_emblems_" + civPhase + ".png";
|
|
|
|
|
const civPhaseData = GetTechnologyData("phase_" + civPhase + "_" + playerState.civ, playerState.civ) ||
|
|
|
|
|
GetTechnologyData("phase_" + civPhase, playerState.civ);
|
|
|
|
|
civilizationTooltip += " — " + getEntityNames(civPhaseData);
|
|
|
|
|
}
|
|
|
|
|
Engine.GetGUIObjectByName("phaseEmblems").sprite = "cropped:1.0, 1.0 center:" + civPhaseEmblems;
|
|
|
|
|
Engine.GetGUIObjectByName("civilizationTooltip").tooltip = civilizationTooltip;
|
|
|
|
|
}
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2015-12-20 09:15:21 -08:00
|
|
|
// TODO: we should require all entities to have icons
|
2026-02-21 09:03:34 -08:00
|
|
|
Engine.GetGUIObjectByName("icon").sprite = entState.icon ? ("stretched:session/portraits/" + entState.icon) : "BackgroundBlack";
|
|
|
|
|
if (entState.icon)
|
2024-09-02 04:22:18 -07:00
|
|
|
{
|
|
|
|
|
const iconBorder = Engine.GetGUIObjectByName("iconBorder");
|
|
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
iconBorder.onPress = () =>
|
|
|
|
|
{
|
2024-09-02 04:22:18 -07:00
|
|
|
setCameraFollow(entState.id);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
iconBorder.onPressRight = () =>
|
|
|
|
|
{
|
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
|
|
|
showTemplateDetails(entState.templateName, playerState.civ);
|
2018-02-21 13:39:00 -08:00
|
|
|
};
|
2024-09-02 04:22:18 -07:00
|
|
|
}
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const detailedTooltip = [
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
getAttackTooltip,
|
2016-07-01 06:32:34 -07:00
|
|
|
getHealerTooltip,
|
2020-08-27 03:24:59 -07:00
|
|
|
getResistanceTooltip,
|
2016-11-19 06:34:02 -08:00
|
|
|
getGatherTooltip,
|
2016-07-02 19:25:57 -07:00
|
|
|
getSpeedTooltip,
|
2016-06-29 18:31:40 -07:00
|
|
|
getGarrisonTooltip,
|
2021-03-26 03:18:30 -07:00
|
|
|
getTurretsTooltip,
|
2020-12-14 10:17:59 -08:00
|
|
|
getPopulationBonusTooltip,
|
2016-09-18 18:21:09 -07:00
|
|
|
getProjectilesTooltip,
|
2017-04-07 23:45:22 -07:00
|
|
|
getResourceTrickleTooltip,
|
2021-04-03 23:52:20 -07:00
|
|
|
getUpkeepTooltip,
|
2016-09-18 18:21:09 -07:00
|
|
|
getLootTooltip
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
].map(func => func(entState)).filter(tip => tip).join("\n");
|
2020-12-27 00:12:44 -08:00
|
|
|
if (detailedTooltip)
|
|
|
|
|
{
|
|
|
|
|
Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = false;
|
|
|
|
|
Engine.GetGUIObjectByName("attackAndResistanceStats").tooltip = detailedTooltip;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = true;
|
2014-01-02 08:22:03 -08:00
|
|
|
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
let iconTooltips = [];
|
2013-08-03 12:20:20 -07:00
|
|
|
|
2021-03-24 23:58:47 -07:00
|
|
|
iconTooltips.push(setStringTags(primaryName, g_TooltipTextFormats.namePrimaryBig));
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
iconTooltips = iconTooltips.concat([
|
|
|
|
|
getVisibleEntityClassesFormatted,
|
|
|
|
|
getAurasTooltip,
|
2018-02-21 13:39:00 -08:00
|
|
|
getEntityTooltip,
|
2024-09-02 04:22:18 -07:00
|
|
|
getTreasureTooltip
|
2026-02-21 09:03:34 -08:00
|
|
|
].map(func => func(entState)));
|
2014-06-09 07:15:05 -07:00
|
|
|
|
2024-09-02 04:22:18 -07:00
|
|
|
const leftClickTooltip = hasClass(entState, "Unit") ? getFollowOnLeftClickTooltip() : getFocusOnLeftClickTooltip();
|
|
|
|
|
iconTooltips.push(leftClickTooltip + " " + getTemplateViewerOnRightClickTooltip());
|
|
|
|
|
|
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
|
|
|
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(tip => tip).join("\n");
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
|
|
|
|
|
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fills out information for multiple entities
|
2016-09-25 12:38:10 -07:00
|
|
|
function displayMultiple(entStates)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2015-12-20 09:15:21 -08:00
|
|
|
let averageHealth = 0;
|
|
|
|
|
let maxHealth = 0;
|
|
|
|
|
let maxCapturePoints = 0;
|
2016-04-28 09:39:09 -07:00
|
|
|
let capturePoints = (new Array(g_MaxPlayers + 1)).fill(0);
|
2015-12-20 09:15:21 -08:00
|
|
|
let playerID = 0;
|
2025-05-10 07:21:01 -07:00
|
|
|
const totalCarrying = {};
|
|
|
|
|
const totalLoot = {};
|
2021-02-28 02:02:03 -08:00
|
|
|
let garrisonSize = 0;
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const entState of entStates)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2015-04-20 00:45:45 -07:00
|
|
|
playerID = entState.player; // trust that all selected entities have the same owner
|
|
|
|
|
if (entState.hitpoints)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2015-04-20 00:45:45 -07:00
|
|
|
averageHealth += entState.hitpoints;
|
|
|
|
|
maxHealth += entState.maxHitpoints;
|
|
|
|
|
}
|
|
|
|
|
if (entState.capturePoints)
|
|
|
|
|
{
|
|
|
|
|
maxCapturePoints += entState.maxCapturePoints;
|
2015-12-20 09:15:21 -08:00
|
|
|
capturePoints = entState.capturePoints.map((v, i) => v + capturePoints[i]);
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
2016-05-10 17:07:38 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const carrying = calculateCarriedResources(
|
2018-01-22 10:02:38 -08:00
|
|
|
entState.resourceCarrying || null,
|
2016-09-18 18:21:09 -07:00
|
|
|
entState.trader && entState.trader.goods
|
|
|
|
|
);
|
|
|
|
|
|
2018-01-23 09:53:42 -08:00
|
|
|
if (entState.loot)
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const type in entState.loot)
|
2018-01-23 09:53:42 -08:00
|
|
|
totalLoot[type] = (totalLoot[type] || 0) + entState.loot[type];
|
2016-09-18 18:21:09 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const type in carrying)
|
2016-05-10 17:07:38 -07:00
|
|
|
{
|
2016-09-18 18:21:09 -07:00
|
|
|
totalCarrying[type] = (totalCarrying[type] || 0) + carrying[type];
|
|
|
|
|
totalLoot[type] = (totalLoot[type] || 0) + carrying[type];
|
2016-05-10 17:07:38 -07:00
|
|
|
}
|
2021-02-28 02:02:03 -08:00
|
|
|
|
|
|
|
|
if (entState.garrisonable)
|
|
|
|
|
garrisonSize += entState.garrisonable.size;
|
|
|
|
|
|
|
|
|
|
if (entState.garrisonHolder)
|
|
|
|
|
garrisonSize += entState.garrisonHolder.occupiedSlots;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
2015-04-20 00:45:45 -07:00
|
|
|
Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
|
2013-08-25 20:06:08 -07:00
|
|
|
if (averageHealth > 0)
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2025-05-29 15:23:46 -07:00
|
|
|
Engine.GetGUIObjectByName("healthBarMultiple").size.rtop = 100 - 100 * Math.max(0, Math.min(1, averageHealth / maxHealth));
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2017-04-09 16:10:50 -07:00
|
|
|
Engine.GetGUIObjectByName("healthMultiple").tooltip = getCurrentHealthTooltip({
|
|
|
|
|
"hitpoints": averageHealth,
|
|
|
|
|
"maxHitpoints": maxHealth
|
2015-12-20 09:15:21 -08:00
|
|
|
});
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
2015-04-20 00:45:45 -07:00
|
|
|
|
|
|
|
|
Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
|
|
|
|
|
if (maxCapturePoints > 0)
|
2013-08-25 20:06:08 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const setCaptureBarPart = function(pID, startSize)
|
2015-04-20 00:45:45 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple[" + pID + "]");
|
2015-04-20 00:45:45 -07:00
|
|
|
|
2025-05-29 15:23:46 -07:00
|
|
|
const height = 100 * Math.max(0, Math.min(1, capturePoints[pID] / maxCapturePoints));
|
|
|
|
|
unitCaptureBar.size.rtop = startSize;
|
|
|
|
|
unitCaptureBar.size.rbottom = startSize + height;
|
|
|
|
|
|
2019-10-11 05:28:12 -07:00
|
|
|
unitCaptureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(pID, 128);
|
2016-04-28 09:39:09 -07:00
|
|
|
unitCaptureBar.hidden = false;
|
2025-05-29 15:23:46 -07:00
|
|
|
return startSize + height;
|
2015-09-19 05:55:58 -07:00
|
|
|
};
|
2015-04-20 00:45:45 -07:00
|
|
|
|
|
|
|
|
let size = 0;
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const i in capturePoints)
|
2015-04-20 00:45:45 -07:00
|
|
|
if (i != playerID)
|
|
|
|
|
size = setCaptureBarPart(i, size);
|
|
|
|
|
|
|
|
|
|
// last handle the owner's points, to keep those points on the bottom for clarity
|
|
|
|
|
setCaptureBarPart(playerID, size);
|
|
|
|
|
|
2017-10-21 10:31:05 -07:00
|
|
|
Engine.GetGUIObjectByName("captureMultiple").tooltip = getCurrentHealthTooltip(
|
|
|
|
|
{
|
2017-04-09 16:10:50 -07:00
|
|
|
"hitpoints": capturePoints[playerID],
|
|
|
|
|
"maxHitpoints": maxCapturePoints
|
|
|
|
|
},
|
2017-08-19 04:47:24 -07:00
|
|
|
translate("Capture Points:"));
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
2015-09-19 05:55:58 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits");
|
2016-09-25 12:38:10 -07:00
|
|
|
numberOfUnits.caption = entStates.length;
|
2016-09-18 18:21:09 -07:00
|
|
|
numberOfUnits.tooltip = "";
|
|
|
|
|
|
2021-02-28 02:02:03 -08:00
|
|
|
if (garrisonSize)
|
|
|
|
|
numberOfUnits.tooltip = sprintf(translate("%(label)s: %(details)s\n"), {
|
|
|
|
|
"label": headerFont(translate("Garrison Size")),
|
|
|
|
|
"details": bodyFont(garrisonSize)
|
|
|
|
|
});
|
|
|
|
|
|
2016-09-18 18:21:09 -07:00
|
|
|
if (Object.keys(totalCarrying).length)
|
|
|
|
|
numberOfUnits.tooltip = sprintf(translate("%(label)s %(details)s\n"), {
|
|
|
|
|
"label": headerFont(translate("Carrying:")),
|
2016-11-19 06:29:45 -08:00
|
|
|
"details": bodyFont(Object.keys(totalCarrying).filter(
|
|
|
|
|
res => totalCarrying[res] != 0).map(
|
2016-09-18 18:21:09 -07:00
|
|
|
res => sprintf(translate("%(type)s %(amount)s"),
|
2016-11-30 07:35:06 -08:00
|
|
|
{ "type": resourceIcon(res), "amount": totalCarrying[res] })).join(" "))
|
2016-09-18 18:21:09 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (Object.keys(totalLoot).length)
|
|
|
|
|
numberOfUnits.tooltip += sprintf(translate("%(label)s %(details)s"), {
|
|
|
|
|
"label": headerFont(translate("Loot:")),
|
2016-11-19 06:29:45 -08:00
|
|
|
"details": bodyFont(Object.keys(totalLoot).filter(
|
|
|
|
|
res => totalLoot[res] != 0).map(
|
2016-09-18 18:21:09 -07:00
|
|
|
res => sprintf(translate("%(type)s %(amount)s"),
|
2016-11-30 07:35:06 -08:00
|
|
|
{ "type": resourceIcon(res), "amount": totalLoot[res] })).join(" "))
|
2016-09-18 18:21:09 -07:00
|
|
|
});
|
2013-08-25 20:06:08 -07:00
|
|
|
|
|
|
|
|
// Unhide Details Area
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
|
|
|
|
|
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-25 00:24:05 -07:00
|
|
|
// Updates middle entity Selection Details Panel and left Unit Commands Panel
|
2013-08-25 20:06:08 -07:00
|
|
|
function updateSelectionDetails()
|
|
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const supplementalDetailsPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
|
|
|
|
|
const detailsPanel = Engine.GetGUIObjectByName("selectionDetails");
|
|
|
|
|
const commandsPanel = Engine.GetGUIObjectByName("unitCommands");
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const entStates = [];
|
2014-03-16 16:29:27 -07:00
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const sel of g_Selection.toList())
|
2016-09-25 12:38:10 -07:00
|
|
|
{
|
2025-05-10 07:21:01 -07:00
|
|
|
const entState = GetEntityState(sel);
|
2016-09-25 12:38:10 -07:00
|
|
|
if (!entState)
|
|
|
|
|
continue;
|
|
|
|
|
entStates.push(entState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entStates.length == 0)
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2014-01-04 02:14:53 -08:00
|
|
|
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
|
|
|
|
|
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
|
2014-01-02 08:22:03 -08:00
|
|
|
hideUnitCommands();
|
2014-03-16 16:29:27 -07:00
|
|
|
|
2014-01-02 08:22:03 -08:00
|
|
|
supplementalDetailsPanel.hidden = true;
|
|
|
|
|
detailsPanel.hidden = true;
|
2013-08-25 20:06:08 -07:00
|
|
|
commandsPanel.hidden = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill out general info and display it
|
2016-09-25 12:38:10 -07:00
|
|
|
if (entStates.length == 1)
|
|
|
|
|
displaySingle(entStates[0]);
|
2013-08-25 20:06:08 -07:00
|
|
|
else
|
2016-09-25 12:38:10 -07:00
|
|
|
displayMultiple(entStates);
|
2013-08-25 20:06:08 -07:00
|
|
|
|
2014-03-16 16:29:27 -07:00
|
|
|
// Show basic details.
|
2013-08-25 20:06:08 -07:00
|
|
|
detailsPanel.hidden = false;
|
|
|
|
|
|
2016-01-09 14:01:08 -08:00
|
|
|
// Fill out commands panel for specific unit selected (or first unit of primary group)
|
2016-09-25 12:38:10 -07:00
|
|
|
updateUnitCommands(entStates, supplementalDetailsPanel, commandsPanel);
|
2016-04-25 00:24:05 -07:00
|
|
|
|
|
|
|
|
// Show health bar for garrisoned units if the garrison panel is visible
|
|
|
|
|
if (Engine.GetGUIObjectByName("unitGarrisonPanel") && !Engine.GetGUIObjectByName("unitGarrisonPanel").hidden)
|
2016-09-25 12:38:10 -07:00
|
|
|
updateGarrisonHealthBar(entStates[0], g_Selection.toList());
|
2013-08-25 20:06:08 -07:00
|
|
|
}
|
2017-02-23 15:34:35 -08:00
|
|
|
|
|
|
|
|
function tradingGainString(gain, owner)
|
|
|
|
|
{
|
|
|
|
|
// Translation: Used in the trading gain tooltip
|
|
|
|
|
return sprintf(translate("%(gain)s (%(player)s)"), {
|
|
|
|
|
"gain": gain,
|
|
|
|
|
"player": GetSimState().players[owner].name
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a message with the details of the trade gain.
|
|
|
|
|
*/
|
|
|
|
|
function getTradingTooltip(gain)
|
|
|
|
|
{
|
|
|
|
|
if (!gain)
|
|
|
|
|
return "";
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
const markets = [
|
2017-02-23 15:34:35 -08:00
|
|
|
{ "gain": gain.market1Gain, "owner": gain.market1Owner },
|
|
|
|
|
{ "gain": gain.market2Gain, "owner": gain.market2Owner }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let primaryGain = gain.traderGain;
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const market of markets)
|
2017-02-23 15:34:35 -08:00
|
|
|
if (market.gain && market.owner == gain.traderOwner)
|
|
|
|
|
// Translation: Used in the trading gain tooltip to concatenate profits of different players
|
|
|
|
|
primaryGain += translate("+") + market.gain;
|
|
|
|
|
|
|
|
|
|
let tooltip = tradingGainString(primaryGain, gain.traderOwner);
|
|
|
|
|
|
2025-05-10 07:21:01 -07:00
|
|
|
for (const market of markets)
|
2017-02-23 15:34:35 -08:00
|
|
|
if (market.gain && market.owner != gain.traderOwner)
|
|
|
|
|
tooltip +=
|
|
|
|
|
translateWithContext("Separation mark in an enumeration", ", ") +
|
|
|
|
|
tradingGainString(market.gain, market.owner);
|
|
|
|
|
|
|
|
|
|
return tooltip;
|
|
|
|
|
}
|