Index: ps/trunk/binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 18153)
+++ ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 18154)
@@ -1,545 +1,545 @@
-const g_CostDisplayNames = {
+const g_CostDisplayIcons = {
"food": '[icon="iconFood"]',
"wood": '[icon="iconWood"]',
"stone": '[icon="iconStone"]',
"metal": '[icon="iconMetal"]',
"population": '[icon="iconPopulation"]',
"time": '[icon="iconTime"]'
};
const g_TooltipTextFormats = {
"unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'],
"header": ['[font="sans-bold-13"]', '[/font]'],
"body": ['[font="sans-13"]', '[/font]']
};
function damageValues(dmg)
{
if (!dmg)
return [0, 0, 0];
return [dmg.hack || 0, dmg.pierce || 0, dmg.crush || 0];
}
function damageTypeDetails(dmg)
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
let dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.hack.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1]
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.pierce.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1]
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.crush.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1]
}));
return dmgArray.join(translate(", "));
}
function attackRateDetails(entState, type)
{
// Either one arrow shot by UnitAI,
let time = entState.attack[type].repeatTime / 1000;
let timeString = sprintf(translatePlural("%(time)s %(second)s", "%(time)s %(second)s", time), {
"time": time,
"second": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", time) + g_TooltipTextFormats.unit[1]
});
// or multiple arrows shot by BuildingAI
if (!entState.buildingAI)
return timeString;
let arrows = entState.buildingAI.arrowCount;
let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrow)s", "%(arrowcount)s %(arrow)s", arrows), {
"arrowcount": arrows,
"arrow": g_TooltipTextFormats.unit[0] + translatePlural("arrow", "arrows", arrows) + g_TooltipTextFormats.unit[1]
});
return sprintf(translate("%(arrowString)s / %(timeString)s"), {
"arrowString": arrowString,
"timeString": timeString
});
}
// Converts an armor level into the actual reduction percentage
function armorLevelToPercentageString(level)
{
return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
// return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation.
}
function getArmorTooltip(dmg)
{
let label = g_TooltipTextFormats.header[0] + translate("Armor:") + g_TooltipTextFormats.header[1];
if (!dmg)
return sprintf(translate("%(label)s %(details)s"), {
"label": label,
"details": '[font="sans-12"]' + translate("(None)") + '[/font]'
});
let dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
"damage": dmg.hack.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1],
"armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.hack) }) + '[/font]'
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
"damage": dmg.pierce.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1],
"armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.pierce) }) + '[/font]'
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
"damage": dmg.crush.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1],
"armorPercentage": '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { "armorPercentage": armorLevelToPercentageString(dmg.crush) }) + '[/font]'
}));
return sprintf(translate("%(label)s %(details)s"), {
"label": label,
"details": dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]')
});
}
function damageTypesToText(dmg)
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
let dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.hack.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Hack") + g_TooltipTextFormats.unit[1]
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.pierce.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Pierce") + g_TooltipTextFormats.unit[1]
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg.crush.toFixed(1),
"damageType": g_TooltipTextFormats.unit[0] + translate("Crush") + g_TooltipTextFormats.unit[1]
}));
return dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]');
}
function getAttackTypeLabel(type)
{
if (type === "Charge") return translate("Charge Attack:");
if (type === "Melee") return translate("Melee Attack:");
if (type === "Ranged") return translate("Ranged Attack:");
if (type === "Capture") return translate("Capture Attack:");
warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { "attackType": type }));
return translate("Attack:");
}
function getAttackTooltip(template)
{
let attacks = [];
if (!template.attack)
return "";
let rateLabel = g_TooltipTextFormats.header[0] + (template.buildingAI ? translate("Interval:") : translate("Rate:")) + g_TooltipTextFormats.header[1];
for (let type in template.attack)
{
if (type == "Slaughter")
continue; // Slaughter is not a real attack, so do not show it.
if (type == "Charge")
continue; // Charging isn't implemented yet and shouldn't be displayed.
let rate = sprintf(translate("%(label)s %(details)s"), {
"label": rateLabel,
"details": attackRateDetails(template, type)
});
let attackLabel = g_TooltipTextFormats.header[0] + getAttackTypeLabel(type) + g_TooltipTextFormats.header[1];
if (type == "Capture")
{
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
"attackLabel": attackLabel,
"details": template.attack[type].value,
"rate": rate
}));
continue;
}
if (type != "Ranged")
{
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
"attackLabel": attackLabel,
"details": damageTypesToText(template.attack[type]),
"rate": rate
}));
continue;
}
let realRange = template.attack[type].elevationAdaptedRange;
let range = Math.round(template.attack[type].maxRange);
let rangeLabel = g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1];
let relativeRange = Math.round((realRange - range));
let meters = g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", range) + g_TooltipTextFormats.unit[1];
if (relativeRange) // show if it is non-zero
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(rangeString)s (%(relative)s), %(rate)s"), {
"attackLabel": attackLabel,
"details": damageTypesToText(template.attack[type]),
"rangeLabel": rangeLabel,
"rangeString": sprintf(
translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), {
"range": range,
"meters": meters
}),
"relative": relativeRange > 0 ? "+" + relativeRange : relativeRange,
"rate": rate
}));
else
attacks.push(sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(rangeString)s, %(rate)s"), {
"attackLabel": attackLabel,
"damageTypes": damageTypesToText(template.attack[type]),
"rangeLabel": rangeLabel,
"rangeString": sprintf(
translatePlural("%(range)s %(meters)s", "%(range)s %(meters)s", range), {
"range": range,
"meters": meters
}),
rate: rate
}));
}
return attacks.join("\n");
}
function getRepairRateTooltip(rate)
{
return "\n" + sprintf(translate("%(repairRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
"repairRateLabel": g_TooltipTextFormats.header[0] + translate("Repair Rate:") + g_TooltipTextFormats.header[1],
"value": Math.round(rate * 10) / 10,
"health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1],
"second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1],
"worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1]
});
}
function getBuildRateTooltip(rate)
{
return "\n" + sprintf(translate("%(buildRateLabel)s %(value)s %(health)s / %(second)s / %(worker)s"), {
"buildRateLabel": g_TooltipTextFormats.header[0] + translate("Build Rate:") + g_TooltipTextFormats.header[1],
"value": Math.round(rate * 10) / 10,
"health": g_TooltipTextFormats.unit[0] + translate("health") + g_TooltipTextFormats.unit[1],
"second": g_TooltipTextFormats.unit[0] + translate("second") + g_TooltipTextFormats.unit[1],
"worker": g_TooltipTextFormats.unit[0] + translate("worker") + g_TooltipTextFormats.unit[1]
});
}
/**
* Translates a cost component identifier as they are used internally
* (e.g. "population", "food", etc.) to proper display names.
*/
-function getCostComponentDisplayName(costComponentName)
+function getCostComponentDisplayIcon(costComponentName)
{
- if (costComponentName in g_CostDisplayNames)
- return g_CostDisplayNames[costComponentName];
+ if (costComponentName in g_CostDisplayIcons)
+ return g_CostDisplayIcons[costComponentName];
warn(sprintf("The specified cost component, ‘%(component)s’, is not currently supported.", { "component": costComponentName }));
return "";
}
/**
* Multiplies the costs for a template by a given batch size.
*/
function multiplyEntityCosts(template, trainNum)
{
let totalCosts = {};
for (let r in template.cost)
totalCosts[r] = Math.floor(template.cost[r] * trainNum);
return totalCosts;
}
/**
* Helper function for getEntityCostTooltip.
*/
function getEntityCostComponentsTooltipString(template, trainNum, entity)
{
if (!trainNum)
trainNum = 1;
let totalCosts = multiplyEntityCosts(template, trainNum);
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
let costs = [];
- for (let type in g_CostDisplayNames)
+ for (let type in g_CostDisplayIcons)
if (totalCosts[type])
costs.push(sprintf(translate("%(component)s %(cost)s"), {
- "component": getCostComponentDisplayName(type),
+ "component": getCostComponentDisplayIcon(type),
"cost": totalCosts[type]
}));
return costs;
}
/**
* Returns an array of strings for a set of wall pieces. If the pieces share
* resource type requirements, output will be of the form '10 to 30 Stone',
* otherwise output will be, e.g. '10 Stone, 20 Stone, 30 Stone'.
*/
function getWallPieceTooltip(wallTypes)
{
let out = [];
let resourceCount = {};
// Initialize the acceptable types for '$x to $y $resource' mode.
for (let resource in wallTypes[0].cost)
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
let sameTypes = true;
for (let i = 1; i < wallTypes.length; ++i)
{
for (let resource in wallTypes[i].cost)
{
// Break out of the same-type mode if this wall requires
// resource types that the first didn't.
if (wallTypes[i].cost[resource] && !resourceCount[resource])
{
sameTypes = false;
break;
}
}
for (let resource in resourceCount)
{
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
}
if (sameTypes)
{
for (let resource in resourceCount)
{
let resourceMin = Math.min.apply(Math, resourceCount[resource]);
let resourceMax = Math.max.apply(Math, resourceCount[resource]);
// Translation: This string is part of the resources cost string on
// the tooltip for wall structures.
out.push(sprintf(translate("%(resourceIcon)s %(minimum)s to %(resourceIcon)s %(maximum)s"), {
- "resourceIcon": getCostComponentDisplayName(resource),
+ "resourceIcon": getCostComponentDisplayIcon(resource),
"minimum": resourceMin,
"maximum": resourceMax
}));
}
}
else
for (let i = 0; i < wallTypes.length; ++i)
out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", "));
return out;
}
/**
* Returns the cost information to display in the specified entity's construction button tooltip.
*/
function getEntityCostTooltip(template, trainNum, entity)
{
// Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of
// their own; the individual wall pieces within it do.
if (template.wallSet)
{
let templateLong = GetTemplateData(template.wallSet.templates.long);
let templateMedium = GetTemplateData(template.wallSet.templates.medium);
let templateShort = GetTemplateData(template.wallSet.templates.short);
let templateTower = GetTemplateData(template.wallSet.templates.tower);
let wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
let towerCosts = getEntityCostComponentsTooltipString(templateTower);
return sprintf(translate("Walls: %(costs)s"), { "costs": wallCosts.join(" ") }) + "\n" +
sprintf(translate("Towers: %(costs)s"), { "costs": towerCosts.join(" ") });
}
if (template.cost)
return getEntityCostComponentsTooltipString(template, trainNum, entity).join(" ");
return "";
}
/**
* Returns the population bonus information to display in the specified entity's construction button tooltip.
*/
function getPopulationBonusTooltip(template)
{
let popBonus = "";
if (template.cost && template.cost.populationBonus)
popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
"label": g_TooltipTextFormats.header[0] + translate("Population Bonus:") + g_TooltipTextFormats.header[1],
"populationBonus": template.cost.populationBonus
});
return popBonus;
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
let formatted = [];
for (let resource in resources)
formatted.push(sprintf(translate("%(component)s %(cost)s"), {
- "component": '[font="sans-12"]' + getCostComponentDisplayName(resource) + '[/font]',
+ "component": '[font="sans-12"]' + getCostComponentDisplayIcon(resource) + '[/font]',
"cost": resources[resource]
}));
return '\n\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join(" ");
}
function getSpeedTooltip(template)
{
if (!template.speed)
return "";
let label = g_TooltipTextFormats.header[0] + translate("Speed:") + g_TooltipTextFormats.header[1];
let speeds = [];
if (template.speed.walk)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), {
"speed": Math.round(template.speed.walk),
"movementType": g_TooltipTextFormats.unit[0] + translate("Walk") + g_TooltipTextFormats.unit[1]
}));
if (template.speed.run)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), {
"speed": Math.round(template.speed.run),
"movementType": g_TooltipTextFormats.unit[0] + translate("Run") + g_TooltipTextFormats.unit[1]
}));
return sprintf(translate("%(label)s %(speeds)s"), {
"label": label,
"speeds": speeds.join(translate(", "))
});
}
function getHealerTooltip(template)
{
if (!template.healer)
return "";
let healer = [
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.HP), {
"label": g_TooltipTextFormats.header[0] + translate("Heal:") + g_TooltipTextFormats.header[1],
"val": template.healer.HP,
// Translation: Short for hitpoints (that are healed in one healing action)
"unit": g_TooltipTextFormats.unit[0] + translatePlural("HP", "HP", template.healer.HP) + g_TooltipTextFormats.unit[1]
}),
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Range), {
"label": g_TooltipTextFormats.header[0] + translate("Range:") + g_TooltipTextFormats.header[1],
"val": template.healer.Range,
"unit": g_TooltipTextFormats.unit[0] + translatePlural("meter", "meters", template.healer.Range) + g_TooltipTextFormats.unit[1]
}),
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", template.healer.Rate/1000), {
"label": g_TooltipTextFormats.header[0] + translate("Rate:") + g_TooltipTextFormats.header[1],
"val": template.healer.Rate/1000,
"unit": g_TooltipTextFormats.unit[0] + translatePlural("second", "seconds", template.healer.Rate/1000) + g_TooltipTextFormats.unit[1]
})
];
return healer.join(translate(", "));
}
function getAurasTooltip(template)
{
if (!template.auras)
return "";
let txt = "";
for (let aura in template.auras)
txt += '\n' + sprintf(translate("%(auralabel)s %(aurainfo)s"), {
"auralabel": g_TooltipTextFormats.header[0] + sprintf(translate("%(auraname)s:"), {
"auraname": translate(template.auras[aura].name)
}) + g_TooltipTextFormats.header[1],
"aurainfo": g_TooltipTextFormats.body[0] + translate(template.auras[aura].description) + g_TooltipTextFormats.body[1]
});
return txt;
}
function getEntityNames(template)
{
if (template.name.specific)
{
if (template.name.generic && template.name.specific != template.name.generic)
return sprintf(translate("%(specificName)s (%(genericName)s)"), {
"specificName": template.name.specific,
"genericName": template.name.generic
});
return template.name.specific;
}
if (template.name.generic)
return template.name.generic;
warn("Entity name requested on an entity without a name, specific or generic.");
return translate("???");
}
function getEntityNamesFormatted(template)
{
let names = "";
let generic = template.name.generic;
let specific = template.name.specific;
if (specific)
{
// drop caps for specific name
names += '[font="sans-bold-16"]' + specific[0] + '[/font]' +
'[font="sans-bold-12"]' + specific.slice(1).toUpperCase() + '[/font]';
if (generic)
names += '[font="sans-bold-16"] (' + generic + ')[/font]';
}
else if (generic)
names = '[font="sans-bold-16"]' + generic + "[/font]";
else
names = "???";
return names;
}
function getVisibleEntityClassesFormatted(template)
{
let r = "";
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
r += '\n' + g_TooltipTextFormats.header[0] + translate("Classes:") + g_TooltipTextFormats.header[1];
let classes = [];
for (let c of template.visibleIdentityClasses)
classes.push(translate(c));
r += ' ' + g_TooltipTextFormats.body[0] + classes.join(translate(", ")) + g_TooltipTextFormats.body[1];
}
return r;
}
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 18153)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 18154)
@@ -1,457 +1,468 @@
function layoutSelectionSingle()
{
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
function layoutSelectionMultiple()
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
function getResourceTypeDisplayName(resourceType)
{
let resourceCode = resourceType.generic;
if (resourceCode == "treasure")
return getLocalizedResourceName(resourceType.specific, "firstWord");
else
return getLocalizedResourceName(resourceCode, "firstWord");
}
// Updates the health bar of garrisoned units
function updateGarrisionHealthBar(entState, selection)
{
if (!entState.garrisonHolder)
return;
// Summing up the Health of every single unit
let totalGarrisionHealth = 0;
let maxGarrisionHealth = 0;
for (let selEnt of selection)
{
let selEntState = GetEntityState(selEnt);
if (selEntState.garrisonHolder)
for (let ent of selEntState.garrisonHolder.entities)
{
let state = GetEntityState(ent);
totalGarrisionHealth += state.hitpoints || 0;
maxGarrisionHealth += state.maxHitpoints || 0;
}
}
// Configuring the health bar
let healthGarrison = Engine.GetGUIObjectByName("healthGarrison");
healthGarrison.hidden = totalGarrisionHealth <= 0;
if (totalGarrisionHealth > 0)
{
let healthBarGarrison = Engine.GetGUIObjectByName("healthBarGarrison");
let healthSize = healthBarGarrison.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, totalGarrisionHealth / maxGarrisionHealth));
healthBarGarrison.size = healthSize;
healthGarrison.tooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), {
"label": "[font=\"sans-bold-13\"]" + translate("Hitpoints:") + "[/font]",
"current": Math.ceil(totalGarrisionHealth),
"max": Math.ceil(maxGarrisionHealth)
});
}
}
// Fills out information that most entities have
function displaySingle(entState)
{
// Get general unit and player data
let template = GetTemplateData(entState.template);
let specificName = template.name.specific;
let genericName = template.name.generic;
// If packed, add that to the generic name (reduces template clutter)
if (genericName && template.pack && template.pack.state == "packed")
genericName = sprintf(translate("%(genericName)s — Packed"), { "genericName": genericName });
let playerState = g_Players[entState.player];
let civName = g_CivData[playerState.civ].Name;
let civEmblem = g_CivData[playerState.civ].Emblem;
let playerName = playerState.name;
let playerColor = playerState.color.r + " " + playerState.color.g + " " + playerState.color.b + " 128";
// Indicate disconnected players by prefixing their name
if (g_Players[entState.player].offline)
playerName = sprintf(translate("\\[OFFLINE] %(player)s"), { "player": playerName });
// Rank
if (entState.identity && entState.identity.rank && entState.identity.classes)
{
Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), {
"rank": translateWithContext("Rank", entState.identity.rank)
});
Engine.GetGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
Engine.GetGUIObjectByName("rankIcon").hidden = false;
}
else
{
Engine.GetGUIObjectByName("rankIcon").hidden = true;
Engine.GetGUIObjectByName("rankIcon").tooltip = "";
}
// Hitpoints
Engine.GetGUIObjectByName("healthSection").hidden = !entState.hitpoints;
if (entState.hitpoints)
{
let unitHealthBar = Engine.GetGUIObjectByName("healthBar");
let healthSize = unitHealthBar.size;
healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
unitHealthBar.size = healthSize;
if (entState.foundation && entState.visibility == "visible" && entState.foundation.numBuilders !== 0)
{
// logic comes from Foundation component.
let speed = Math.pow(entState.foundation.numBuilders, 0.7);
let timeLeft = (1.0 - entState.foundation.progress / 100.0) * template.cost.time;
let timeToCompletion = Math.ceil(timeLeft/speed);
Engine.GetGUIObjectByName("health").tooltip = sprintf(translatePlural("This foundation will be completed in %(seconds)s second.", "This foundation will be completed in %(seconds)s seconds.", timeToCompletion), { "seconds": timeToCompletion });
}
else
Engine.GetGUIObjectByName("health").tooltip = "";
Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
"hitpoints": Math.ceil(entState.hitpoints),
"maxHitpoints": Math.ceil(entState.maxHitpoints)
});
}
// CapturePoints
Engine.GetGUIObjectByName("captureSection").hidden = !entState.capturePoints;
if (entState.capturePoints)
{
let setCaptureBarPart = function(playerID, startSize) {
let unitCaptureBar = Engine.GetGUIObjectByName("captureBar["+playerID+"]");
let sizeObj = unitCaptureBar.size;
sizeObj.rleft = startSize;
let size = 100*Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
sizeObj.rright = startSize + size;
unitCaptureBar.size = sizeObj;
unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
unitCaptureBar.hidden=false;
return startSize + size;
};
// first handle the owner's points, to keep those points on the left for clarity
let size = setCaptureBarPart(entState.player, 0);
for (let i in entState.capturePoints)
if (i != entState.player)
size = setCaptureBarPart(i, size);
Engine.GetGUIObjectByName("captureStats").caption = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
"capturePoints": Math.ceil(entState.capturePoints[entState.player]),
"maxCapturePoints": Math.ceil(entState.maxCapturePoints)
});
}
// Experience
Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
if (entState.promotion)
{
let experienceBar = Engine.GetGUIObjectByName("experienceBar");
let experienceSize = experienceBar.size;
experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
experienceBar.size = experienceSize;
if (entState.promotion.curr < entState.promotion.req)
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
"current": Math.floor(entState.promotion.curr),
"required": entState.promotion.req
});
else
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
"current": Math.floor(entState.promotion.curr)
});
}
// Resource stats
Engine.GetGUIObjectByName("resourceSection").hidden = !entState.resourceSupply;
if (entState.resourceSupply)
{
let resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
sprintf(translate("%(amount)s / %(max)s"), {
"amount": Math.ceil(+entState.resourceSupply.amount),
"max": entState.resourceSupply.max
});
let resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
let unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
let resourceSize = unitResourceBar.size;
resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { "resource": resourceType });
Engine.GetGUIObjectByName("resourceStats").caption = resources;
if (entState.hitpoints)
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("captureSection").size;
else
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size;
}
// Resource carrying
if (entState.resourceCarrying && entState.resourceCarrying.length)
{
// We should only be carrying one resource type at once, so just display the first
let carried = entState.resourceCarrying[0];
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
}
// Use the same indicators for traders
else if (entState.trader && entState.trader.goods.amount)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
let totalGain = entState.trader.goods.amount.traderGain;
if (entState.trader.goods.amount.market1Gain)
totalGain += entState.trader.goods.amount.market1Gain;
if (entState.trader.goods.amount.market2Gain)
totalGain += entState.trader.goods.amount.market2Gain;
Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(gain)s"), { "gain": getTradingTooltip(entState.trader.goods.amount) });
}
// And for number of workers
else if (entState.foundation && entState.visibility == "visible")
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
if (entState.foundation.numBuilders !== 0)
{
let speedup = Math.pow((entState.foundation.numBuilders+1)/entState.foundation.numBuilders, 0.7);
let timeLeft = (1.0 - entState.foundation.progress / 100.0) * template.cost.time;
let timeSpeedup = Math.ceil(timeLeft - timeLeft/speedup);
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translatePlural("Number of builders.\nTasking another to this foundation would speed construction up by %(speedup)s second.", "Number of builders.\nTasking another to this foundation would speed construction up by %(speedup)s seconds.", timeSpeedup), { "speedup": timeSpeedup });
}
else
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
}
else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.repairable.numBuilders + " ";
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
}
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && entState.visibility == "visible")
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), {
"amount": entState.resourceSupply.numGatherers,
"max": entState.resourceSupply.maxGatherers
}) + " ";
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
}
else
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = true;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = true;
}
// Set Player details
Engine.GetGUIObjectByName("specific").caption = specificName;
Engine.GetGUIObjectByName("player").caption = playerName;
Engine.GetGUIObjectByName("playerColorBackground").sprite = "color: " + playerColor;
if (genericName !== specificName)
Engine.GetGUIObjectByName("generic").caption = sprintf(translate("(%(genericName)s)"), { "genericName": genericName });
else
Engine.GetGUIObjectByName("generic").caption = "";
if ("gaia" != playerState.civ)
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem;
Engine.GetGUIObjectByName("player").tooltip = civName;
}
else
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "";
Engine.GetGUIObjectByName("player").tooltip = "";
}
// Icon image
// TODO: we should require all entities to have icons
Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "bkFillBlack";
let armorString = getArmorTooltip(entState.armour);
// Attack and Armor
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = entState.attack ? (getAttackTooltip(entState) + "\n" + armorString) : armorString;
// Repair Rate
if (entState.repairRate)
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getRepairRateTooltip(entState.repairRate);
// Build Rate
if (entState.buildRate)
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip += getBuildRateTooltip(entState.buildRate);
// Icon Tooltip
let iconTooltip = "";
if (genericName)
iconTooltip = "[font=\"sans-bold-16\"]" + genericName + "[/font]";
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
iconTooltip += "\n[font=\"sans-bold-13\"]" + translate("Classes:") + "[/font] ";
iconTooltip += "[font=\"sans-13\"]" + template.visibleIdentityClasses.map(c => translate(c)).join(", ") + "[/font]";
}
if (template.auras)
iconTooltip += getAurasTooltip(template);
if (template.tooltip)
iconTooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltip;
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
// Fills out information for multiple entities
function displayMultiple(selection)
{
let averageHealth = 0;
let maxHealth = 0;
let maxCapturePoints = 0;
let capturePoints = (new Array(g_MaxPlayers + 1)).fill(0);
let playerID = 0;
+ let totalResourcesCarried = {};
for (let i = 0; i < selection.length; ++i)
{
let entState = GetEntityState(selection[i]);
if (!entState)
continue;
playerID = entState.player; // trust that all selected entities have the same owner
if (entState.hitpoints)
{
averageHealth += entState.hitpoints;
maxHealth += entState.maxHitpoints;
}
if (entState.capturePoints)
{
maxCapturePoints += entState.maxCapturePoints;
capturePoints = entState.capturePoints.map((v, i) => v + capturePoints[i]);
}
+
+ if (entState.resourceCarrying && entState.resourceCarrying.length)
+ {
+ let carrying = entState.resourceCarrying[0];
+ totalResourcesCarried[carrying.type] = (totalResourcesCarried[carrying.type] || 0) + carrying.amount;
+ }
}
Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
if (averageHealth > 0)
{
let unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
let healthSize = unitHealthBar.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
unitHealthBar.size = healthSize;
Engine.GetGUIObjectByName("healthMultiple").tooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), {
"label": "[font=\"sans-bold-13\"]" + translate("Hitpoints:") + "[/font]",
"current": Math.ceil(averageHealth),
"max": Math.ceil(maxHealth)
});
}
Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
if (maxCapturePoints > 0)
{
let setCaptureBarPart = function(playerID, startSize)
{
let unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple["+playerID+"]");
let sizeObj = unitCaptureBar.size;
sizeObj.rtop = startSize;
let size = 100*Math.max(0, Math.min(1, capturePoints[playerID] / maxCapturePoints));
sizeObj.rbottom = startSize + size;
unitCaptureBar.size = sizeObj;
unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
unitCaptureBar.hidden = false;
return startSize + size;
};
let size = 0;
for (let i in capturePoints)
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);
Engine.GetGUIObjectByName("captureMultiple").tooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), {
"label": "[font=\"sans-bold-13\"]" + translate("Capture points:") + "[/font]",
"current": Math.ceil(capturePoints[playerID]),
"max": Math.ceil(maxCapturePoints)
});
}
- Engine.GetGUIObjectByName("numberOfUnits").caption = selection.length;
+ let numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits");
+ numberOfUnits.caption = selection.length;
+ numberOfUnits.tooltip = Object.keys(totalResourcesCarried).map(res =>
+ getCostComponentDisplayIcon(res) + totalResourcesCarried[res]
+ ).join(" ");
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates middle entity Selection Details Panel and left Unit Commands Panel
function updateSelectionDetails()
{
let supplementalDetailsPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
let detailsPanel = Engine.GetGUIObjectByName("selectionDetails");
let commandsPanel = Engine.GetGUIObjectByName("unitCommands");
let selection = g_Selection.toList();
if (selection.length == 0)
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands();
supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true;
commandsPanel.hidden = true;
return;
}
/* If the unit has no data (e.g. it was killed), don't try displaying any
data for it. (TODO: it should probably be removed from the selection too;
also need to handle multi-unit selections) */
let entState = GetExtendedEntityState(selection[0]);
if (!entState)
return;
// Fill out general info and display it
if (selection.length == 1)
displaySingle(entState);
else
displayMultiple(selection);
// Show basic details.
detailsPanel.hidden = false;
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
// Show health bar for garrisoned units if the garrison panel is visible
if (Engine.GetGUIObjectByName("unitGarrisonPanel") && !Engine.GetGUIObjectByName("unitGarrisonPanel").hidden)
updateGarrisionHealthBar(entState, selection);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 18153)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 18154)
@@ -1,1177 +1,1166 @@
/**
* Contains the layout and button settings per selection panel
*
* getItems returns a list of basic items used to fill the panel.
* This method is obligated. If the items list is empty, the panel
* won't be rendered.
*
* Then there's a loop over all items provided. In the loop,
* the item and some other standard data is added to a data object.
*
* The standard data is
* var data = {
* "i": index
* "item": item coming from the getItems function
* "selection": list of currently selected items
* "playerState": playerState
* "unitEntState": first selected entity state
* "rowLength": rowLength
* "numberOfItems": number of items that will be processed
* "button": gui Button object
* "icon": gui Icon object
* "guiSelection": gui button Selection overlay
* "countDisplay": gui caption space
* };
*
* Then, addData is called, and can be used to abort the processing
* of the current item by returning false.
* It should return true if you want the panel to be filled.
*
* addData is used to add data to the data object on top
* (or instead of) the standard data.
* addData is not obligated, the function will just continue
* with the content setters if no addData is present.
*
* After the addData, all functions starting with "set" are called.
* These are used to set various parts of content.
*/
/* cache some formation info */
var g_availableFormations = new Map(); // available formations per player
var g_formationsInfo = new Map();
var g_SelectionPanels = {};
// ALERT
g_SelectionPanels.Alert = {
"getMaxNumberOfItems": function()
{
return 2;
},
"getItems": function(unitEntState)
{
if (!unitEntState.alertRaiser)
return [];
return ["increase", "end"];
},
"setAction": function(data)
{
data.button.onPress = function() {
if (data.item == "increase")
increaseAlertLevel();
else if (data.item == "end")
endOfAlert();
};
},
"setTooltip": function(data)
{
if (data.item == "increase")
{
if (data.unitEntState.alertRaiser.hasRaisedAlert)
data.button.tooltip = translate("Increase the alert level to protect more units");
else
data.button.tooltip = translate("Raise an alert!");
}
else if (data.item == "end")
data.button.tooltip = translate("End of alert.");
},
"setGraphics": function(data)
{
if (data.item == "increase")
{
data.button.hidden = !data.unitEntState.alertRaiser.canIncreaseLevel;
if (data.unitEntState.alertRaiser.hasRaisedAlert)
data.icon.sprite = "stretched:session/icons/bell_level2.png";
else
data.icon.sprite = "stretched:session/icons/bell_level1.png";
}
else if (data.item == "end")
{
data.button.hidden = !data.unitEntState.alertRaiser.hasRaisedAlert;
data.icon.sprite = "stretched:session/icons/bell_level0.png";
}
data.button.enabled = !data.button.hidden && controlsPlayer(data.unitEntState.player);
}
};
// BARTER
g_SelectionPanels.Barter = {
"getMaxNumberOfItems": function()
{
return 4;
},
"rowLength": 4,
"getItems": function(unitEntState, selection)
{
if (!unitEntState.barterMarket)
return [];
// ["food", "wood", "stone", "metal"]
return BARTER_RESOURCES;
},
"addData": function(data)
{
// data.item is the resource name in this case
data.button = {};
data.icon = {};
data.amount = {};
for (var a of BARTER_ACTIONS)
{
data.button[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Button["+data.i+"]");
data.icon[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Icon["+data.i+"]");
data.amount[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Amount["+data.i+"]");
}
data.selectionIcon = Engine.GetGUIObjectByName("unitBarterSellSelection["+data.i+"]");
data.amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
if (Engine.HotkeyIsPressed("session.massbarter"))
data.amountToSell *= BARTER_BUNCH_MULTIPLIER;
data.isSelected = data.item == g_barterSell;
return true;
},
"setCountDisplay": function(data)
{
data.amount.Sell.caption = "-" + data.amountToSell;
var sellPrice = data.unitEntState.barterMarket.prices.sell[g_barterSell];
var buyPrice = data.unitEntState.barterMarket.prices.buy[data.item];
data.amount.Buy.caption = "+" + Math.round(sellPrice / buyPrice * data.amountToSell);
},
"setTooltip": function(data)
{
var resource = getLocalizedResourceName(data.item, "withinSentence");
data.button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
data.button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });
},
"setAction": function(data)
{
data.button.Sell.onPress = function() { g_barterSell = data.item; };
var exchangeResourcesParameters = {
"sell": g_barterSell,
"buy": data.item,
"amount": data.amountToSell
};
data.button.Buy.onPress = function() { exchangeResources(exchangeResourcesParameters); };
},
"setGraphics": function(data)
{
var grayscale = data.isSelected ? "color: 0 0 0 100:grayscale:" : "";
// do we have enough of this resource to sell?
var neededRes = {};
neededRes[data.item] = data.amountToSell;
var canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": neededRes,
"player": data.unitEntState.player
}) ? "color:255 0 0 80:" : "";
// Let's see if we have enough resources to barter.
neededRes = {};
neededRes[g_barterSell] = data.amountToSell;
var canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": neededRes,
"player": data.unitEntState.player
}) ? "color:255 0 0 80:" : "";
data.icon.Sell.sprite = canSellCurrent + "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
data.icon.Buy.sprite = canBuyAny + "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
data.button.Buy.hidden = data.isSelected;
data.button.Buy.enabled = controlsPlayer(data.unitEntState.player);
data.button.Sell.hidden = false;
data.selectionIcon.hidden = !data.isSelected;
},
"setPosition": function(data)
{
setPanelObjectPosition(data.button.Sell, data.i, data.rowLength);
setPanelObjectPosition(data.button.Buy, data.i + data.rowLength, data.rowLength);
}
};
// COMMAND
g_SelectionPanels.Command = {
"getMaxNumberOfItems": function()
{
return 6;
},
"getItems": function(unitEntState)
{
let commands = [];
for (let c in g_EntityCommands)
{
var info = g_EntityCommands[c].getInfo(unitEntState);
if (!info)
continue;
info.name = c;
commands.push(info);
}
return commands;
},
"setTooltip": function(data)
{
data.button.tooltip = data.item.tooltip;
},
"setAction": function(data)
{
data.button.onPress = function() {
if (data.item.callback)
data.item.callback(data.item);
else
performCommand(data.unitEntState.id, data.item.name);
};
},
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.item.count || "";
},
"setGraphics": function(data)
{
data.button.enabled = controlsPlayer(data.unitEntState.player);
let grayscale = data.button.enabled ? "" : "grayscale:";
data.icon.sprite = "stretched:" + grayscale + "session/icons/" + data.item.icon;
},
"setPosition": function(data)
{
var size = data.button.size;
// count on square buttons, so size.bottom is the width too
var spacer = size.bottom + 1;
// relative to the center ( = 50%)
size.rleft = size.rright = 50;
// offset from the center calculation
size.left = (data.i - data.numberOfItems/2) * spacer;
size.right = size.left + size.bottom;
data.button.size = size;
}
};
//ALLY COMMAND
g_SelectionPanels.AllyCommand = {
"getMaxNumberOfItems": function()
{
return 2;
},
"getItems": function(unitEntState)
{
var commands = [];
for (var c in g_AllyEntityCommands)
{
var info = g_AllyEntityCommands[c].getInfo(unitEntState);
if (!info)
continue;
info.name = c;
commands.push(info);
}
return commands;
},
"setTooltip": function(data)
{
data.button.tooltip = data.item.tooltip;
},
"setAction": function(data)
{
data.button.onPress = function() {
if (data.item.callback)
data.item.callback(data.item);
else
performAllyCommand(data.unitEntState.id, data.item.name);
};
},
"conflictsWith": ["Command"],
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.item.count || "";
},
"setGraphics": function(data)
{
data.button.enabled = data.item.count != undefined && data.item.count > 0;
let grayscale = data.button.enabled ? "" : "grayscale:";
data.icon.sprite = "stretched:" + grayscale + "session/icons/" + data.item.icon;
},
"setPosition": function(data)
{
var size = data.button.size;
// count on square buttons, so size.bottom is the width too
var spacer = size.bottom + 1;
// relative to the center ( = 50%)
size.rleft = size.rright = 50;
// offset from the center calculation
size.left = (data.i - data.numberOfItems/2) * spacer;
size.right = size.left + size.bottom;
data.button.size = size;
}
};
// CONSTRUCTION
g_SelectionPanels.Construction = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function()
{
return getAllBuildableEntitiesFromSelection();
},
"addData": function(data)
{
data.entType = data.item;
data.template = GetTemplateData(data.entType);
if (!data.template) // abort if no template
return false;
data.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": data.template.requiredTechnology,
"player": data.unitEntState.player
});
if (data.template.cost)
data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.template, 1),
"player": data.unitEntState.player
});
data.limits = getEntityLimitAndCount(data.playerState, data.entType);
if (data.template.wallSet)
data.template.auras = GetTemplateData(data.template.wallSet.templates.long).auras;
return true;
},
"setAction": function(data)
{
data.button.onPress = function () { startBuildingPlacement(data.item, data.playerState); };
},
"setTooltip": function(data)
{
var tooltip = getEntityNamesFormatted(data.template);
tooltip += getVisibleEntityClassesFormatted(data.template);
tooltip += getAurasTooltip(data.template);
if (data.template.tooltip)
tooltip += "\n[font=\"sans-13\"]" + data.template.tooltip + "[/font]";
tooltip += "\n" + getEntityCostTooltip(data.template);
tooltip += getPopulationBonusTooltip(data.template);
tooltip += formatLimitString(data.limits.entLimit, data.limits.entCount, data.limits.entLimitChangers);
if (!data.technologyEnabled)
tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
"technology": getEntityNames(GetTechnologyData(data.template.requiredTechnology))
});
if (data.neededResources)
tooltip += getNeededResourcesTooltip(data.neededResources);
data.button.tooltip = tooltip;
return true;
},
"setGraphics": function(data)
{
var modifier = "";
if (!data.technologyEnabled || data.limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier += "color: 0 0 0 127:";
modifier += "grayscale:";
}
else if (data.neededResources)
{
data.button.enabled = false;
modifier += resourcesToAlphaMask(data.neededResources) +":";
}
else
data.button.enabled = controlsPlayer(data.unitEntState.player);
if (data.template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + data.template.icon;
},
"setPosition": function(data)
{
var index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
}
};
// FORMATION
g_SelectionPanels.Formation = {
"getMaxNumberOfItems": function()
{
return 16;
},
"rowLength": 4,
"conflictsWith": ["Garrison"],
"getItems": function(unitEntState)
{
if (!hasClass(unitEntState, "Unit") || hasClass(unitEntState, "Animal"))
return [];
if (!g_availableFormations.has(unitEntState.player))
g_availableFormations.set(unitEntState.player, Engine.GuiInterfaceCall("GetAvailableFormations", unitEntState.player));
return g_availableFormations.get(unitEntState.player);
},
"addData": function(data)
{
if (!g_formationsInfo.has(data.item))
g_formationsInfo.set(data.item, Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", { "templateName": data.item }));
data.formationInfo = g_formationsInfo.get(data.item);
data.formationOk = canMoveSelectionIntoFormation(data.item);
data.formationSelected = Engine.GuiInterfaceCall("IsFormationSelected", {
"ents": data.selection,
"formationTemplate": data.item
});
return true;
},
"setAction": function(data)
{
data.button.onPress = function() { performFormation(data.unitEntState.id, data.item); };
},
"setTooltip": function(data)
{
var tooltip = translate(data.formationInfo.name);
if (!data.formationOk && data.formationInfo.tooltip)
tooltip += "\n" + "[color=\"red\"]" + translate(data.formationInfo.tooltip) + "[/color]";
data.button.tooltip = tooltip;
},
"setGraphics": function(data)
{
data.button.enabled = data.formationOk && controlsPlayer(data.unitEntState.player);
var grayscale = data.formationOk ? "" : "grayscale:";
data.guiSelection.hidden = !data.formationSelected;
data.icon.sprite = "stretched:"+grayscale+"session/icons/"+data.formationInfo.icon;
}
};
// GARRISON
g_SelectionPanels.Garrison = {
"getMaxNumberOfItems": function()
{
return 12;
},
"rowLength": 4,
"getItems": function(unitEntState, selection)
{
if (!unitEntState.garrisonHolder)
return [];
var groups = new EntityGroups();
for (var ent of selection)
{
var state = GetEntityState(ent);
if (state.garrisonHolder)
groups.add(state.garrisonHolder.entities);
}
return groups.getEntsGrouped();
},
"addData": function(data)
{
data.entType = data.item.template;
data.template = GetTemplateData(data.entType);
if (!data.template)
return false;
data.name = getEntityNames(data.template);
data.count = data.item.ents.length;
return true;
},
"setAction": function(data)
{
data.button.onPress = function() { unloadTemplate(data.item.template); };
},
"setTooltip": function(data)
{
var tooltip = sprintf(translate("Unload %(name)s"), { "name": data.name }) + "\n";
tooltip += translate("Single-click to unload 1. Shift-click to unload all of this type.");
data.button.tooltip = tooltip;
},
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.count || "";
},
"setGraphics": function(data)
{
var grayscale = "";
var ents = data.item.ents;
var entplayer = GetEntityState(ents[0]).player;
data.button.sprite = "color:" + rgbToGuiColor(g_Players[entplayer].color) +":";
if (!controlsPlayer(data.unitEntState.player) && !controlsPlayer(entplayer))
{
data.button.enabled = false;
grayscale = "grayscale:";
}
data.icon.sprite = "stretched:" + grayscale + "session/portraits/" + data.template.icon;
}
};
// GATE
g_SelectionPanels.Gate = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function(unitEntState, selection)
{
// Allow long wall pieces to be converted to gates
var longWallTypes = {};
var walls = [];
var gates = [];
for (var ent of selection)
{
var state = GetEntityState(ent);
if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
{
var gateTemplate = getWallGateTemplate(state.id);
if (gateTemplate)
{
var tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
if (!tooltipString)
{
warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
tooltipString = "";
}
walls.push({
"tooltip": translate(tooltipString),
"template": gateTemplate,
"callback": function (item) { transformWallToGate(item.template); }
});
}
// We only need one entity per type.
longWallTypes[state.template] = true;
}
else if (state.gate && !gates.length)
{
gates.push({
"gate": state.gate,
"tooltip": translate("Lock Gate"),
"locked": true,
"callback": function (item) { lockGate(item.locked); }
});
gates.push({
"gate": state.gate,
"tooltip": translate("Unlock Gate"),
"locked": false,
"callback": function (item) { lockGate(item.locked); }
});
}
// Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
else if (state.gate && state.gate.locked != gates[0].gate.locked)
for (var j = 0; j < gates.length; ++j)
delete gates[j].gate.locked;
}
// Place wall conversion options after gate lock/unlock icons.
var items = gates.concat(walls);
return items;
},
"setAction": function(data)
{
data.button.onPress = function() {data.item.callback(data.item); };
},
"setTooltip": function(data)
{
var tooltip = data.item.tooltip;
if (data.item.template)
{
data.template = GetTemplateData(data.item.template);
data.wallCount = data.selection.reduce(function (count, ent) {
var state = GetEntityState(ent);
if (hasClass(state, "LongWall") && !state.gate)
++count;
return count;
}, 0);
tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount);
data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.template, data.wallCount)
});
if (data.neededResources)
tooltip += getNeededResourcesTooltip(data.neededResources);
}
data.button.tooltip = tooltip;
},
"setGraphics": function(data)
{
data.button.enabled = controlsPlayer(data.unitEntState.player);
var gateIcon;
if (data.item.gate)
{
// If already a gate, show locking actions
gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
if (data.item.gate.locked === undefined)
data.guiSelection.hidden = false;
else
data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
}
else
{
// otherwise show gate upgrade icon
var template = GetTemplateData(data.item.template);
if (!template)
return;
gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
data.guiSelection.hidden = true;
}
data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
},
"setPosition": function(data)
{
var index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
}
};
// PACK
g_SelectionPanels.Pack = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function(unitEntState, selection)
{
var checks = {};
for (var ent of selection)
{
var state = GetEntityState(ent);
if (!state.pack)
continue;
if (state.pack.progress == 0)
{
if (!state.pack.packed)
checks.packButton = true;
else if (state.pack.packed)
checks.unpackButton = true;
}
else
{
// Already un/packing - show cancel button
if (!state.pack.packed)
checks.packCancelButton = true;
else if (state.pack.packed)
checks.unpackCancelButton = true;
}
}
var items = [];
if (checks.packButton)
items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
if (checks.unpackButton)
items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
if (checks.packCancelButton)
items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
if (checks.unpackCancelButton)
items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
return items;
},
"setAction": function(data)
{
data.button.onPress = function() {data.item.callback(data.item); };
},
"setTooltip": function(data)
{
data.button.tooltip = data.item.tooltip;
},
"setGraphics": function(data)
{
if (data.item.packing)
data.icon.sprite = "stretched:session/icons/cancel.png";
else if (data.item.packed)
data.icon.sprite = "stretched:session/icons/unpack.png";
else
data.icon.sprite = "stretched:session/icons/pack.png";
data.button.enabled = controlsPlayer(data.unitEntState.player);
},
"setPosition": function(data)
{
var index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
}
};
// QUEUE
g_SelectionPanels.Queue = {
"getMaxNumberOfItems": function()
{
return 16;
},
"getItems": function(unitEntState, selection)
{
return getTrainingQueueItems(selection);
},
"resizePanel": function(numberOfItems, rowLength)
{
var numRows = Math.ceil(numberOfItems / rowLength);
var panel = Engine.GetGUIObjectByName("unitQueuePanel");
var size = panel.size;
var buttonSize = Engine.GetGUIObjectByName("unitQueueButton[0]").size.bottom;
var margin = 4;
size.top = size.bottom - numRows*buttonSize - (numRows+2)*margin;
panel.size = size;
},
"addData": function(data)
{
// differentiate between units and techs
if (data.item.unitTemplate)
{
data.entType = data.item.unitTemplate;
data.template = GetTemplateData(data.entType);
}
else if (data.item.technologyTemplate)
{
data.entType = data.item.technologyTemplate;
data.template = GetTechnologyData(data.entType);
}
data.progress = Math.round(data.item.progress*100) + "%";
return data.template;
},
"setAction": function(data)
{
data.button.onPress = function() { removeFromProductionQueue(data.item.producingEnt, data.item.id); };
},
"setTooltip": function(data)
{
var tooltip = getEntityNames(data.template);
if (data.item.neededSlots)
{
tooltip += "\n[color=\"red\"]" + translate("Insufficient population capacity:") + "\n[/color]";
tooltip += sprintf(translate("%(population)s %(neededSlots)s"), {
- "population": getCostComponentDisplayName("population"),
+ "population": getCostComponentDisplayIcon("population"),
"neededSlots": data.item.neededSlots
});
}
data.button.tooltip = tooltip;
},
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.item.count > 1 ? data.item.count : "";
},
"setProgressDisplay": function(data)
{
// show the progress number for the first item
if (data.i == 0)
Engine.GetGUIObjectByName("queueProgress").caption = data.progress;
var guiObject = Engine.GetGUIObjectByName("unitQueueProgressSlider["+data.i+"]");
var size = guiObject.size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(data.item.progress * (size.right - size.left));
guiObject.size = size;
},
"setGraphics": function(data)
{
if (data.template.icon)
data.icon.sprite = "stretched:session/portraits/" + data.template.icon;
data.button.enabled = controlsPlayer(data.unitEntState.player);
}
};
// RESEARCH
g_SelectionPanels.Research = {
"getMaxNumberOfItems": function()
{
return 8;
},
"getItems": function(unitEntState, selection)
{
// TODO 8 is the row lenght, make variable
if (getNumberOfRightPanelButtons() > 8 && selection.length > 1)
return [];
for (var ent of selection)
{
var entState = GetEntityState(ent);
if (entState.production && entState.production.technologies.length)
return entState.production.technologies;
}
return [];
},
"hideItem": function(i, rowLength) // called when no item is found
{
Engine.GetGUIObjectByName("unitResearchButton["+i+"]").hidden = true;
// We also remove the paired tech and the pair symbol
Engine.GetGUIObjectByName("unitResearchButton["+(i+rowLength)+"]").hidden = true;
Engine.GetGUIObjectByName("unitResearchPair["+i+"]").hidden = true;
},
"addData": function(data)
{
data.entType = data.item.pair ? [data.item.top, data.item.bottom] : [data.item];
data.template = data.entType.map(GetTechnologyData);
// abort if no template found for any of the techs
if (data.template.some(v => !v))
return false;
// index one row below
var shiftedIndex = data.i + data.rowLength;
data.positions = data.item.pair ? [data.i, shiftedIndex] : [shiftedIndex];
data.positionsToHide = data.item.pair ? [] : [data.i];
// add top buttons to the data
data.button = data.positions.map(p => Engine.GetGUIObjectByName("unitResearchButton["+p+"]"));
data.buttonsToHide = data.positionsToHide.map(p => Engine.GetGUIObjectByName("unitResearchButton["+p+"]"));
data.icon = data.positions.map(p => Engine.GetGUIObjectByName("unitResearchIcon["+p+"]"));
data.unchosenIcon = data.positions.map(p => Engine.GetGUIObjectByName("unitResearchUnchosenIcon["+p+"]"));
data.neededResources = data.template.map(t => Engine.GuiInterfaceCall("GetNeededResources", {
"cost": t.cost,
"player": data.unitEntState.player
}));
data.requirementsPassed = data.entType.map(e => Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
"tech": e,
"player": data.unitEntState.player
}));
data.pair = Engine.GetGUIObjectByName("unitResearchPair["+data.i+"]");
return true;
},
"setTooltip": function(data)
{
for (var i in data.entType)
{
var tooltip = "";
var template = data.template[i];
tooltip = getEntityNamesFormatted(template);
if (template.tooltip)
tooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
tooltip += "\n" + getEntityCostTooltip(template);
if (!data.requirementsPassed[i])
{
tooltip += "\n" + template.requirementsTooltip;
if (template.classRequirements)
{
var player = data.unitEntState.player;
var current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0;
var remaining = template.classRequirements.number - current;
tooltip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), { "number": remaining });
}
}
if (data.neededResources[i])
tooltip += getNeededResourcesTooltip(data.neededResources[i]);
data.button[i].tooltip = tooltip;
}
},
"setAction": function(data)
{
for (var i in data.entType)
{
// array containing the indices other buttons
var others = Object.keys(data.template);
others.splice(i, 1);
var button = data.button[i];
// as we're in a loop, we need to limit the scope with a closure
// else the last value of the loop will be taken, rather than the current one
button.onpress = (function(template) { return function () { addResearchToQueue(data.unitEntState.id, template); }; })(data.entType[i]);
// on mouse enter, show a cross over the other icons
button.onmouseenter = (function(others, icons) {
return function() {
for (var j of others)
icons[j].hidden = false;
};
})(others, data.unchosenIcon);
button.onmouseleave = (function(others, icons) {
return function() {
for (var j of others)
icons[j].hidden = true;
};
})(others, data.unchosenIcon);
}
},
"setGraphics": function(data)
{
for (var i in data.entType)
{
let button = data.button[i];
button.hidden = false;
var modifier = "";
if (!data.requirementsPassed[i])
{
button.enabled = false;
modifier += "color: 0 0 0 127:";
modifier += "grayscale:";
}
else if (data.neededResources[i])
{
button.enabled = false;
modifier += resourcesToAlphaMask(data.neededResources[i]) + ":";
}
else
button.enabled = controlsPlayer(data.unitEntState.player);
if (data.template[i].icon)
data.icon[i].sprite = modifier + "stretched:session/portraits/" + data.template[i].icon;
}
for (let button of data.buttonsToHide)
button.hidden = true;
// show the tech connector
data.pair.hidden = data.item.pair == null;
},
"setPosition": function(data)
{
for (var i in data.button)
setPanelObjectPosition(data.button[i], data.positions[i], data.rowLength);
setPanelObjectPosition(data.pair, data.i, data.rowLength);
}
};
// SELECTION
g_SelectionPanels.Selection = {
"getMaxNumberOfItems": function()
{
return 16;
},
"rowLength": 4,
"getItems": function(unitEntState, selection)
{
if (selection.length < 2)
return [];
return g_Selection.groups.getTemplateNames();
},
"addData": function(data)
{
data.entType = data.item;
data.template = GetTemplateData(data.entType);
if (!data.template)
return false;
data.name = getEntityNames(data.template);
var ents = g_Selection.groups.getEntsByName(data.item);
data.count = ents.length;
for (var ent of ents)
{
var state = GetEntityState(ent);
if (state.resourceCarrying && state.resourceCarrying.length !== 0)
{
if (!data.carried)
data.carried = {};
var carrying = state.resourceCarrying[0];
if (data.carried[carrying.type])
data.carried[carrying.type] += carrying.amount;
else
data.carried[carrying.type] = carrying.amount;
}
if (state.trader && state.trader.goods && state.trader.goods.amount)
{
if (!data.carried)
data.carried = {};
var amount = state.trader.goods.amount;
var type = state.trader.goods.type;
var totalGain = amount.traderGain;
if (amount.market1Gain)
totalGain += amount.market1Gain;
if (amount.market2Gain)
totalGain += amount.market2Gain;
if (data.carried[type])
data.carried[type] += totalGain;
else
data.carried[type] = totalGain;
}
}
return true;
},
"setTooltip": function(data)
{
+ let tooltip = data.name;
if (data.carried)
- {
- var str = data.name + "\n";
- var ress = ["food", "wood", "stone", "metal"];
- for (var i = 0; i < 4; ++i)
- {
- if (data.carried[ress[i]])
- {
- str += getCostComponentDisplayName(ress[i]) + data.carried[ress[i]];
- if (i !== 3)
- str += " ";
- }
- }
- data.button.tooltip = str;
- }
- else
- data.button.tooltip = data.name;
+ tooltip += "\n" + Object.keys(data.carried).map(res =>
+ getCostComponentDisplayIcon(res) + data.carried[res]
+ ).join(" ");
+ data.button.tooltip = tooltip;
},
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.count || "";
},
"setAction": function(data)
{
data.button.onpressright = function() { changePrimarySelectionGroup(data.item, true); };
data.button.onpress = function() { changePrimarySelectionGroup(data.item, false); };
},
"setGraphics": function(data)
{
if (data.template.icon)
data.icon.sprite = "stretched:session/portraits/" + data.template.icon;
}
};
// STANCE
g_SelectionPanels.Stance = {
"getMaxNumberOfItems": function()
{
return 5;
},
"getItems": function(unitEntState)
{
if (!unitEntState.unitAI || !hasClass(unitEntState, "Unit") || hasClass(unitEntState, "Animal"))
return [];
return unitEntState.unitAI.possibleStances;
},
"addData": function(data)
{
data.stanceSelected = Engine.GuiInterfaceCall("IsStanceSelected", {
"ents": data.selection,
"stance": data.item
});
return true;
},
"setAction": function(data)
{
data.button.onPress = function() { performStance(data.unitEntState, data.item); };
},
"setTooltip": function(data)
{
data.button.tooltip = getStanceDisplayName(data.item) + "\n[font=\"sans-13\"]" + getStanceTooltip(data.item) + "[/font]";
},
"setGraphics": function(data)
{
data.guiSelection.hidden = !data.stanceSelected;
data.icon.sprite = "stretched:session/icons/stances/"+data.item+".png";
data.button.enabled = controlsPlayer(data.unitEntState.player);
}
};
// TRAINING
g_SelectionPanels.Training = {
"getMaxNumberOfItems": function()
{
return 24 - getNumberOfRightPanelButtons();
},
"getItems": function()
{
return getAllTrainableEntitiesFromSelection();
},
"addData": function(data)
{
data.entType = data.item;
data.template = GetTemplateData(data.entType);
if (!data.template)
return false;
data.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": data.template.requiredTechnology,
"player": data.unitEntState.player
});
var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
getTrainingBatchStatus(data.playerState, data.unitEntState.id, data.entType, data.selection);
data.buildingsCountToTrainFullBatch = buildingsCountToTrainFullBatch;
data.fullBatchSize = fullBatchSize;
data.remainderBatch = remainderBatch;
data.trainNum = buildingsCountToTrainFullBatch || 1; // train at least one unit
if (Engine.HotkeyIsPressed("session.batchtrain"))
data.trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
if (data.template.cost)
data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.template, data.trainNum),
"player": data.unitEntState.player
});
return true;
},
"setAction": function(data)
{
data.button.onPress = function() { addTrainingToQueue(data.selection, data.item, data.playerState); };
},
"setCountDisplay": function(data)
{
data.countDisplay.caption = data.trainNum > 1 ? data.trainNum : "";
},
"setTooltip": function(data)
{
var tooltip = "";
var key = Engine.ConfigDB_GetValue("user", "hotkey.session.queueunit." + (data.i + 1));
if (key)
tooltip += "[color=\"255 251 131\"][font=\"sans-bold-16\"]\\[" + key + "][/font][/color] ";
tooltip += getEntityNamesFormatted(data.template);
tooltip += getVisibleEntityClassesFormatted(data.template);
tooltip += getAurasTooltip(data.template);
if (data.template.tooltip)
tooltip += "\n[font=\"sans-13\"]" + data.template.tooltip + "[/font]";
tooltip += "\n" + getEntityCostTooltip(data.template, data.trainNum, data.unitEntState.id);
data.limits = getEntityLimitAndCount(data.playerState, data.entType);
tooltip += formatLimitString(data.limits.entLimit, data.limits.entCount, data.limits.entLimitChangers);
if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true")
{
if (data.template.health)
tooltip += "\n[font=\"sans-bold-13\"]" + translate("Health:") + "[/font] " + data.template.health;
if (data.template.attack)
tooltip += "\n" + getAttackTooltip(data.template);
if (data.template.armour)
tooltip += "\n" + getArmorTooltip(data.template.armour);
if (data.template.speed)
tooltip += "\n" + getSpeedTooltip(data.template);
}
tooltip += "[color=\"255 251 131\"]" + formatBatchTrainingString(data.buildingsCountToTrainFullBatch, data.fullBatchSize, data.remainderBatch) + "[/color]";
if (!data.technologyEnabled)
{
var techName = getEntityNames(GetTechnologyData(data.template.requiredTechnology));
tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { "technology": techName });
}
if (data.neededResources)
tooltip += getNeededResourcesTooltip(data.neededResources);
data.button.tooltip = tooltip;
},
// disable and enable buttons in the same way as when you do for the construction
"setGraphics": g_SelectionPanels.Construction.setGraphics,
"setPosition": function(data)
{
var index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
}
};
/**
* If two panels need the same space, so they collide,
* the one appearing first in the order is rendered.
*
* Note that the panel needs to appear in the list to get rendered.
*/
var g_PanelsOrder = [
// LEFT PANE
"Barter", // must always be visible on markets
"Garrison", // more important than Formation, as you want to see the garrisoned units in ships
"Alert",
"Formation",
"Stance", // normal together with formation
// RIGHT PANE
"Gate", // must always be shown on gates
"Pack", // must always be shown on packable entities
"Training",
"Construction",
"Research", // normal together with training
// UNIQUE PANES (importance doesn't matter)
"Command",
"AllyCommand",
"Queue",
"Selection",
];
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml (revision 18153)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml (revision 18154)
@@ -1,47 +1,47 @@
Index: ps/trunk/binaries/data/mods/public/gui/structree/draw.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/structree/draw.js (revision 18153)
+++ ps/trunk/binaries/data/mods/public/gui/structree/draw.js (revision 18154)
@@ -1,382 +1,382 @@
var g_DrawLimits = {}; // GUI limits. Populated by predraw()
/**
* Draw the structree
*
* (Actually resizes and changes visibility of elements, and populates text)
*/
function draw()
{
// Set basic state (positioning of elements mainly), but only once
if (!Object.keys(g_DrawLimits).length)
predraw();
let defWidth = 96;
let defMargin = 4;
let phaseList = g_ParsedData.phaseList;
Engine.GetGUIObjectByName("civEmblem").sprite = "stretched:"+g_CivData[g_SelectedCiv].Emblem;
Engine.GetGUIObjectByName("civName").caption = g_CivData[g_SelectedCiv].Name;
Engine.GetGUIObjectByName("civHistory").caption = g_CivData[g_SelectedCiv].History;
let i = 0;
for (let pha of phaseList)
{
let s = 0;
let y = 0;
for (let stru of g_CivData[g_SelectedCiv].buildList[pha])
{
let thisEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
if (thisEle === undefined)
{
error("\""+g_SelectedCiv+"\" has more structures in phase "+pha+" than can be supported by the current GUI layout");
break;
}
let c = 0;
let rowCounts = [];
stru = g_ParsedData.structures[stru];
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_icon").sprite = "stretched:session/portraits/"+stru.icon;
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_icon").tooltip = assembleTooltip(stru);
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_name").caption = translate(stru.name.specific);
thisEle.hidden = false;
for (let r in g_DrawLimits[pha].prodQuant)
{
let p = 0;
r = +r; // force int
let prod_pha = phaseList[phaseList.indexOf(pha) + r];
if (stru.production.units[prod_pha])
{
for (let prod of stru.production.units[prod_pha])
{
prod = g_ParsedData.units[prod];
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
if (stru.wallset && prod_pha == pha)
{
for (let prod of [stru.wallset.gate, stru.wallset.tower])
{
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
if (stru.production.technology[prod_pha])
{
for (let prod of stru.production.technology[prod_pha])
{
prod = (depath(prod).slice(0,5) == "phase") ? g_ParsedData.phases[prod] : g_ParsedData.techs[prod];
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
rowCounts[r] = p;
if (p>c)
c = p;
hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]_prod[", p, "]");
}
let size = thisEle.size;
size.left = y;
size.right = size.left + ((c*24 < defWidth) ? defWidth : c*24) + 4;
y = size.right + defMargin;
thisEle.size = size;
let eleWidth = size.right - size.left;
let r;
for (r in rowCounts)
{
let wid = rowCounts[r] * 24 - 4;
let phaEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]");
size = phaEle.size;
size.left = (eleWidth - wid)/2;
phaEle.size = size;
}
++r;
hideRemaining("phase["+i+"]_struct["+s+"]_row[", r, "]");
++s;
}
hideRemaining("phase["+i+"]_struct[", s, "]");
++i;
}
let t = 0;
for (let trainer of g_CivData[g_SelectedCiv].trainList)
{
let thisEle = Engine.GetGUIObjectByName("trainer["+t+"]");
if (thisEle === undefined)
{
error("\""+g_SelectedCiv+"\" has more unit trainers than can be supported by the current GUI layout");
break;
}
trainer = g_ParsedData.units[trainer];
Engine.GetGUIObjectByName("trainer["+t+"]_icon").sprite = "stretched:session/portraits/"+trainer.icon;
Engine.GetGUIObjectByName("trainer["+t+"]_icon").tooltip = assembleTooltip(trainer);
Engine.GetGUIObjectByName("trainer["+t+"]_name").caption = translate(trainer.name.specific);
thisEle.hidden = false;
let p = 0;
for (let prod of trainer.trainer)
{
prod = g_ParsedData.units[prod];
if (!drawProdIcon(null, t, null, p, prod))
break;
p++;
}
hideRemaining("trainer["+t+"]_prod[", p, "]");
let size = thisEle.size;
size.right = size.left + ((p*24 < defWidth)?defWidth:p*24)+4;
thisEle.size = size;
let eleWidth = size.right - size.left;
let wid = p * 24 - 4;
let phaEle = Engine.GetGUIObjectByName("trainer["+t+"]_row");
size = phaEle.size;
size.left = (eleWidth - wid)/2;
phaEle.size = size;
++t;
}
hideRemaining("trainer[", t, "]");
let size = Engine.GetGUIObjectByName("display_tree").size;
size.right = t > 0 ? -124 : -4;
Engine.GetGUIObjectByName("display_tree").size = size;
Engine.GetGUIObjectByName("display_trainers").hidden = t == 0;
}
function drawProdIcon(pha, s, r, p, prod)
{
let prodEle = Engine.GetGUIObjectByName("phase["+pha+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
if (pha === null)
prodEle = Engine.GetGUIObjectByName("trainer["+s+"]_prod["+p+"]");
if (prodEle === undefined)
{
error("The "+(pha === null ? "trainer units" : "structures")+" of \""+g_SelectedCiv+"\" have more production icons than can be supported by the current GUI layout");
return false;
}
prodEle.sprite = "stretched:session/portraits/"+prod.icon;
prodEle.tooltip = assembleTooltip(prod);
prodEle.hidden = false;
return true;
}
/**
* Calculate row position offset (accounting for different number of prod rows per phase).
*/
function getPositionOffset(idx)
{
let phases = g_ParsedData.phaseList.length;
let size = 92*idx; // text, image and offset
size += 24 * (phases*idx - (idx-1)*idx/2); // phase rows (phase-currphase+1 per row)
return size;
}
function hideRemaining(prefix, idx, suffix)
{
let obj = Engine.GetGUIObjectByName(prefix+idx+suffix);
while (obj)
{
obj.hidden = true;
++idx;
obj = Engine.GetGUIObjectByName(prefix+idx+suffix);
}
}
/**
* Positions certain elements that only need to be positioned once
* (as does not reposition automatically).
*
* Also detects limits on what the GUI can display by iterating through the set
* elements of the GUI. These limits are then used by draw().
*/
function predraw()
{
let phaseList = g_ParsedData.phaseList;
let initIconSize = Engine.GetGUIObjectByName("phase[0]_struct[0]_row[0]_prod[0]").size;
let phaseCount = phaseList.length;
let i = 0;
for (let pha of phaseList)
{
let offset = getPositionOffset(i);
// Align the phase row
Engine.GetGUIObjectByName("phase["+i+"]").size = "8 16+"+offset+" 100% 100%";
// Set phase icon
let phaseIcon = Engine.GetGUIObjectByName("phase["+i+"]_phase");
phaseIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[pha].icon;
phaseIcon.size = "16 32+"+offset+" 48+16 48+32+"+offset;
// Position prod bars
let j = 1;
for (; j < phaseCount - i; ++j)
{
let prodBar = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]");
prodBar.size = "40 1+"+(24*j)+"+98+"+offset+" 100%-8 1+"+(24*j)+"+98+"+offset+"+22";
// Set phase icon
let prodBarIcon = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]_icon");
prodBarIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[phaseList[i+j]].icon;
}
// Hide remaining prod bars
hideRemaining("phase["+i+"]_bar[", j-1, "]");
let s = 0;
let ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
g_DrawLimits[pha] = {
"structQuant": 0,
"prodQuant": []
};
do
{
// Position production icons
for (let r in phaseList.slice(phaseList.indexOf(pha)))
{
let p=1;
let prodEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
do
{
let prodsize = prodEle.size;
prodsize.left = (initIconSize.right+4) * p;
prodsize.right = (initIconSize.right+4) * (p+1) - 4;
prodEle.size = prodsize;
p++;
prodEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
} while (prodEle !== undefined);
// Set quantity of productions in this row
g_DrawLimits[pha].prodQuant[r] = p;
// Position the prod row
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]").size = "4 100%-"+24*(phaseCount - i - r)+" 100%-4 100%";
}
// Hide unused struct rows
for (let j = phaseCount - i; j < phaseCount; ++j)
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+j+"]").hidden = true;
let size = ele.size;
size.bottom += Object.keys(g_DrawLimits[pha].prodQuant).length*24;
ele.size = size;
s++;
ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
} while (ele !== undefined);
// Set quantity of structures in each phase
g_DrawLimits[pha].structQuant = s;
++i;
}
hideRemaining("phase[", i, "]");
hideRemaining("phase[", i, "]_bar");
let t = 0;
let ele = Engine.GetGUIObjectByName("trainer["+t+"]");
g_DrawLimits.trainer = {
"trainerQuant": 0,
"prodQuant": 0
};
let x = 4;
do
{
let p = 0;
let prodEle = Engine.GetGUIObjectByName("trainer["+t+"]_prod["+p+"]");
do
{
let prodsize = prodEle.size;
prodsize.left = (initIconSize.right+4) * p;
prodsize.right = (initIconSize.right+4) * (p+1) - 4;
prodEle.size = prodsize;
p++;
prodEle = Engine.GetGUIObjectByName("trainer["+t+"]_prod["+p+"]");
} while (prodEle !== undefined);
Engine.GetGUIObjectByName("trainer["+t+"]_row").size = "4 100%-24"+" 100%-4 100%";
g_DrawLimits.trainer.prodQuant = p;
let size = ele.size;
size.top += x;
size.bottom += x + 24;
x += size.bottom - size.top + 8;
ele.size = size;
t++;
ele = Engine.GetGUIObjectByName("trainer["+t+"]");
} while (ele !== undefined);
g_DrawLimits.trainer.trainerQuant = t;
}
/**
* Assemble a tooltip text
*
* @param template Information about a Unit, a Structure or a Technology
*
* @return The tooltip text, formatted.
*/
function assembleTooltip(template)
{
let txt = getEntityNamesFormatted(template);
txt += '\n' + getEntityCostTooltip(template, 1);
if (template.tooltip)
txt += '\n' + g_TooltipTextFormats.body[0] + translate(template.tooltip) + g_TooltipTextFormats.body[1];
if (template.auras)
txt += getAurasTooltip(template);
if (template.health)
txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
"label": g_TooltipTextFormats.header[0] + translate("Health:") + g_TooltipTextFormats.header[1],
"details": template.health
});
if (template.healer)
txt += '\n' + getHealerTooltip(template);
if (template.attack)
txt += '\n' + getAttackTooltip(template);
if (template.armour)
txt += '\n' + getArmorTooltip(template.armour);
if (template.speed)
txt += '\n' + getSpeedTooltip(template);
if (template.gather)
{
let rates = [];
for (let type in template.gather)
rates.push(sprintf(translate("%(resourceIcon)s %(rate)s"), {
- "resourceIcon": getCostComponentDisplayName(type),
+ "resourceIcon": getCostComponentDisplayIcon(type),
"rate": template.gather[type]
}));
txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
"label": g_TooltipTextFormats.header[0] + translate("Gather Rates:") + g_TooltipTextFormats.header[1],
"details": rates.join(" ")
});
}
txt += getPopulationBonusTooltip(template);
return txt;
}