1
0
forked from mirrors/0ad

Some gui code cleanup/restructuring.

Moving some tooltip code that only needs templates to common/ to
allow to use that for the structree mod.

This was SVN commit r16127.
This commit is contained in:
leper
2015-01-08 21:37:59 +00:00
parent 57033850a8
commit 0f21151c4b
5 changed files with 508 additions and 601 deletions
@@ -1,44 +0,0 @@
function updateOrbital()
{
if( !Engine.GetGUIObjectByName( 'arena' ).hidden )
{
g_ballx += g_balldx;
g_bally += g_balldy;
if (g_ballx > 600)
{
g_balldx *= -0.9;
g_ballx = 600-(g_ballx-600);
}
else if (g_ballx < 0)
{
g_balldx *= -0.9;
g_ballx = -g_ballx;
}
if (g_bally > 400)
{
g_balldy *= -0.9;
g_bally = 400-(g_bally-400);
}
else if (g_bally < 0)
{
g_balldy *= -0.9;
g_bally = -g_bally;
}
// Gravitate towards the mouse
var vect_x = g_ballx-g_mousex;
var vect_y = g_bally-g_mousey;
var dsquared = vect_x*vect_x + vect_y*vect_y;
if (dsquared < 1000) dsquared = 1000;
var force = 10000.0 / dsquared;
var mag = Math.sqrt(dsquared);
vect_x /= mag; vect_y /= mag;
g_balldx -= force * vect_x;
g_balldy -= force * vect_y;
var ball = Engine.GetGUIObjectByName('ball');
var r=5;
ball.size = new GUISize(g_ballx-r, g_bally-r, g_ballx+r, g_bally+r);
}
}
@@ -0,0 +1,73 @@
const localisedResourceNames = {
"firstWord": {
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"food": translateWithContext("firstWord", "Food"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"meat": translateWithContext("firstWord", "Meat"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"metal": translateWithContext("firstWord", "Metal"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ore": translateWithContext("firstWord", "Ore"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"rock": translateWithContext("firstWord", "Rock"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ruins": translateWithContext("firstWord", "Ruins"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"stone": translateWithContext("firstWord", "Stone"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"treasure": translateWithContext("firstWord", "Treasure"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"tree": translateWithContext("firstWord", "Tree"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"wood": translateWithContext("firstWord", "Wood"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fruit": translateWithContext("firstWord", "Fruit"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"grain": translateWithContext("firstWord", "Grain"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fish": translateWithContext("firstWord", "Fish"),
},
"withinSentence": {
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"food": translateWithContext("withinSentence", "Food"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"meat": translateWithContext("withinSentence", "Meat"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"metal": translateWithContext("withinSentence", "Metal"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ore": translateWithContext("withinSentence", "Ore"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"rock": translateWithContext("withinSentence", "Rock"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ruins": translateWithContext("withinSentence", "Ruins"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"stone": translateWithContext("withinSentence", "Stone"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"treasure": translateWithContext("withinSentence", "Treasure"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"tree": translateWithContext("withinSentence", "Tree"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"wood": translateWithContext("withinSentence", "Wood"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fruit": translateWithContext("withinSentence", "Fruit"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"grain": translateWithContext("withinSentence", "Grain"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fish": translateWithContext("withinSentence", "Fish"),
}
};
function getLocalizedResourceName(resourceCode, context)
{
if (!localisedResourceNames[context])
{
warn("Internationalization: Unexpected context for resource type localization found: " + context + ". This context is not supported.");
return resourceCode;
}
if (!localisedResourceNames[context][resourceCode])
{
warn("Internationalization: Unexpected resource type found with code " + resourceCode + ". This resource type must be internationalized.");
return resourceCode;
}
return localisedResourceNames[context][resourceCode];
}
@@ -0,0 +1,430 @@
const COST_DISPLAY_NAMES = {
"food": "[icon=\"iconFood\"]",
"wood": "[icon=\"iconWood\"]",
"stone": "[icon=\"iconStone\"]",
"metal": "[icon=\"iconMetal\"]",
"population": "[icon=\"iconPopulation\"]",
"time": "[icon=\"iconTime\"]"
};
// For the unit details panel
function damageValues(dmg)
{
if (!dmg)
return [0, 0, 0];
return [dmg.hack || 0, dmg.pierce || 0, dmg.crush || 0];
}
// For the unit details panel
function damageTypeDetails(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
}));
return dmgArray.join(translate(", "));
}
// TODO color/font
function attackRateDetails(entState)
{
var time = entState.attack.repeatTime / 1000;
if (entState.buildingAI) {
var arrows = Math.max(entState.buildingAI.arrowCount, entState.buildingAI.defaultArrowCount);
return sprintf(translate("%(arrowString)s / %(timeString)s"), {
arrowString: sprintf(translatePlural("%(arrows)s arrow", "%(arrows)s arrows", arrows), { arrows: arrows}),
timeString: sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time })
});
}
return sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time });
}
// 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.
}
// Also for the unit details panel
function armorTypeDetails(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.hack,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
}));
return dmgArray.join(translate(", "));
}
// For the training tooltip
function damageTypesToText(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
}));
return dmgArray.join("[font=\"sans-12\"]" + translate(", ") + "[/font]");
}
// Also for the training tooltip
function armorTypesToText(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.hack,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
}));
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:");
warn(sprintf("Internationalization: Unexpected attack type found with code %(attackType)s. This attack type must be internationalized.", { attackType: type }));
return translate("Attack:");
}
function getEntityAttack(template)
{
var attacks = [];
if (template.attack)
{
// Don't show slaughter attack
delete template.attack['Slaughter'];
for (var type in template.attack)
{
if (type == "Charge")
continue; // Charging isn't implemented yet and shouldn't be displayed.
var attack = "";
var attackLabel = "[font=\"sans-bold-13\"]" + getAttackTypeLabel(type) + "[/font]";
if (type == "Ranged")
{
// Show max attack range if ranged attack, also convert to tiles (4m per tile)
attack = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s"), {
attackLabel: attackLabel,
damageTypes: damageTypesToText(template.attack[type]),
rangeLabel: "[font=\"sans-bold-13\"]" + translate("Range:") + "[/font]",
range: Math.round(template.attack[type].maxRange) + "[font=\"sans-10\"][color=\"orange\"] " + translate("meters") + "[/color][/font]"
});
}
else
{
attack = sprintf(translate("%(attackLabel)s %(damageTypes)s"), {
attackLabel: attackLabel,
damageTypes: damageTypesToText(template.attack[type])
});
}
attacks.push(attack);
}
}
return attacks.join(translate(", "));
}
/**
* Translates a cost component identifier as they are used internally
* (e.g. "population", "food", etc.) to proper display names.
*/
function getCostComponentDisplayName(costComponentName)
{
if (costComponentName in COST_DISPLAY_NAMES)
return COST_DISPLAY_NAMES[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)
{
var totalCosts = {};
for (var 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;
var totalCosts = multiplyEntityCosts(template, trainNum);
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
var costs = [];
if (totalCosts.food) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
if (totalCosts.wood) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
if (totalCosts.metal) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
if (totalCosts.stone) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
if (totalCosts.population) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
if (totalCosts.time) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
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)
{
var out = [];
var resourceCount = {};
// Initialize the acceptable types for '$x to $y $resource' mode.
for (var resource in wallTypes[0].cost)
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
var sameTypes = true;
for (var i = 1; i < wallTypes.length; ++i)
{
for (var 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 (var resource in resourceCount)
{
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
}
if (sameTypes)
{
for (var resource in resourceCount)
{
var resourceMin = Math.min.apply(Math, resourceCount[resource]);
var 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),
minimum: resourceMin,
maximum: resourceMax
}));
}
}
else
for (var 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)
{
var templateLong = GetTemplateData(template.wallSet.templates.long);
var templateMedium = GetTemplateData(template.wallSet.templates.medium);
var templateShort = GetTemplateData(template.wallSet.templates.short);
var templateTower = GetTemplateData(template.wallSet.templates.tower);
var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
var 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)
{
var popBonus = "";
if (template.cost && template.cost.populationBonus)
popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
label: "[font=\"sans-bold-13\"]" + translate("Population Bonus:") + "[/font]",
populationBonus: template.cost.populationBonus
});
return popBonus;
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
var formatted = [];
for (var resource in resources)
formatted.push(sprintf(translate("%(component)s %(cost)s"), {
component: "[font=\"sans-12\"]" + getCostComponentDisplayName(resource) + "[/font]",
cost: resources[resource]
}));
return "\n\n[font=\"sans-bold-13\"][color=\"red\"]" + translate("Insufficient resources:") + "[/color][/font]\n" + formatted.join(" ");
}
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)
{
var names = "";
var generic = template.name.generic;
var 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)
{
var r = ""
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
r += "\n[font=\"sans-bold-13\"]" + translate("Classes:") + "[/font] ";
r += "[font=\"sans-13\"]" + translate(template.visibleIdentityClasses[0]) ;
for (var c = 1; c < template.visibleIdentityClasses.length; c++)
r += ", " + translate(template.visibleIdentityClasses[c]);
r += "[/font]";
}
return r;
}
function getEntitySpeed(template)
{
if (!template.speed)
return "";
var label = "[font=\"sans-bold-13\"]" + translate("Speed:") + "[/font]";
var speeds = [];
if (template.speed.walk)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.walk, movementType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Walk") + "[/color][/font]"}));
if (template.speed.run)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.run, movementType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Run") + "[/color][/font]"}));
return sprintf(translate("%(label)s %(speeds)s"), { label: label, speeds: speeds.join(translate(", ")) })
}
@@ -2,12 +2,14 @@
<objects>
<script file="gui/common/colorFades.js"/>
<script file="gui/common/functions_civinfo.js"/>
<script file="gui/common/functions_utility.js"/>
<script file="gui/common/functions_global_object.js"/>
<script file="gui/common/functions_utility.js"/>
<script file="gui/common/l10n.js"/>
<script file="gui/common/music.js"/>
<script file="gui/common/timer.js"/>
<script file="gui/common/colorFades.js"/>
<script file="gui/common/tooltips.js"/>
<!-- load all scripts in this directory -->
<script directory="gui/session/"/>
@@ -1,17 +1,3 @@
const GEOLOGY = "geology";
const FLORA = "flora";
const FAUNA = "fauna";
const SPECIAL = "special";
const COST_DISPLAY_NAMES = {
"food": "[icon=\"iconFood\"]",
"wood": "[icon=\"iconWood\"]",
"stone": "[icon=\"iconStone\"]",
"metal": "[icon=\"iconMetal\"]",
"population": "[icon=\"iconPopulation\"]",
"time": "[icon=\"iconTime\"]"
};
//-------------------------------- --------------------------------
// Utility functions
//-------------------------------- --------------------------------
@@ -106,8 +92,7 @@ function updatePlayerDataRemove(players, hostGuid)
player.offline = true;
}
//===============================================
// Identity functions
function hasClass(entState, className)
{
// note: use the functions in globalscripts/Templates.js for more versatile matching
@@ -120,449 +105,6 @@ function hasClass(entState, className)
return false;
}
//===============================================
// Atack/Armour functions
// For the unit details panel
function damageValues(dmg)
{
if (dmg)
{
var dmgArray = [];
dmg.hack? dmgArray.push(dmg.hack) : dmgArray.push(0);
dmg.pierce? dmgArray.push(dmg.pierce) : dmgArray.push(0);
dmg.crush? dmgArray.push(dmg.crush) : dmgArray.push(0);
return dmgArray;
}
else
{
return [0, 0, 0];
}
}
// For the unit details panel
function damageTypeDetails(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
}));
return dmgArray.join(translate(", "));
}
function attackRateDetails(entState) {
var time = entState.attack.repeatTime / 1000;
if (entState.buildingAI) {
var arrows = Math.max(entState.buildingAI.arrowCount, entState.buildingAI.defaultArrowCount);
// TODO TODO TODO color, font
return sprintf(translate("%(arrowString)s / %(timeString)s"), {
arrowString: sprintf(translatePlural("%(arrows)s arrow", "%(arrows)s arrows", arrows), { arrows: arrows}),
timeString: sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time })
});
}
// TODO TODO TODO color, font
return sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time });
}
// 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.
}
// Also for the unit details panel
function armorTypeDetails(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.hack,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
}));
return dmgArray.join(translate(", "));
}
// For the training tooltip
function damageTypesToText(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
}));
return dmgArray.join("[font=\"sans-12\"]" + translate(", ") + "[/font]");
}
// Also for the training tooltip
function armorTypesToText(dmg)
{
if (!dmg)
return "[font=\"sans-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.hack,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
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,
damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
}));
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:");
warn(sprintf("Internationalization: Unexpected attack type found with code %(attackType)s. This attack type must be internationalized.", { attackType: type }));
return translate("Attack:");
}
function getEntityAttack(template)
{
var attacks = [];
if (template.attack)
{
// Don't show slaughter attack
delete template.attack['Slaughter'];
for (var type in template.attack)
{
if (type == "Charge")
continue; // Charging isn't implemented yet and shouldn't be displayed.
var attack = "";
var attackLabel = "[font=\"sans-bold-13\"]" + getAttackTypeLabel(type) + "[/font]";
if (type == "Ranged")
{
// Show max attack range if ranged attack, also convert to tiles (4m per tile)
attack = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s"), {
attackLabel: attackLabel,
damageTypes: damageTypesToText(template.attack[type]),
rangeLabel: "[font=\"sans-bold-13\"]" + translate("Range:") + "[/font]",
range: Math.round(template.attack[type].maxRange) + "[font=\"sans-10\"][color=\"orange\"] " + translate("meters") + "[/color][/font]"
});
}
else
{
attack = sprintf(translate("%(attackLabel)s %(damageTypes)s"), {
attackLabel: attackLabel,
damageTypes: damageTypesToText(template.attack[type])
});
}
attacks.push(attack);
}
}
return attacks.join(translate(", "));
}
// ==============================================
// Cost
/**
* Translates a cost component identifier as they are used internally
* (e.g. "population", "food", etc.) to proper display names.
*/
function getCostComponentDisplayName(costComponentName)
{
if (costComponentName in COST_DISPLAY_NAMES)
return COST_DISPLAY_NAMES[costComponentName];
else
{
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)
{
var totalCosts = {};
for (var 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;
var totalCosts = multiplyEntityCosts(template, trainNum);
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
var costs = [];
if (totalCosts.food) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
if (totalCosts.wood) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
if (totalCosts.metal) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
if (totalCosts.stone) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
if (totalCosts.population) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
if (totalCosts.time) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
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)
{
var out = [];
var resourceCount = {};
// Initialize the acceptable types for '$x to $y $resource' mode.
for (var resource in wallTypes[0].cost)
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
var sameTypes = true;
for (var i = 1; i < wallTypes.length; ++i)
{
for (var 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 (var resource in resourceCount)
{
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
}
if (sameTypes)
{
for (var resource in resourceCount)
{
var resourceMin = Math.min.apply(Math, resourceCount[resource]);
var 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),
minimum: resourceMin,
maximum: resourceMax
}));
}
}
else
for (var 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)
{
var cost = "";
// 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)
{
var templateLong = GetTemplateData(template.wallSet.templates.long);
var templateMedium = GetTemplateData(template.wallSet.templates.medium);
var templateShort = GetTemplateData(template.wallSet.templates.short);
var templateTower = GetTemplateData(template.wallSet.templates.tower);
var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
var towerCosts = getEntityCostComponentsTooltipString(templateTower);
cost += " " + sprintf(translate("Walls: %(costs)s"), { costs: wallCosts.join(" ") }) + "\n";
cost += " " + sprintf(translate("Towers: %(costs)s"), { costs: towerCosts.join(" ") });
}
else if (template.cost)
{
var costs = getEntityCostComponentsTooltipString(template, trainNum, entity);
cost = costs.join(" ");
}
else
{
cost = ""; // cleaner than duplicating the sans-bold-13 stuff
}
return cost;
}
/**
* Returns the population bonus information to display in the specified entity's construction button tooltip.
*/
function getPopulationBonusTooltip(template)
{
var popBonus = "";
if (template.cost && template.cost.populationBonus)
popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
label: "[font=\"sans-bold-13\"]" + translate("Population Bonus:") + "[/font]",
populationBonus: template.cost.populationBonus
});
return popBonus;
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
var formatted = [];
for (var resource in resources)
formatted.push(sprintf(translate("%(component)s %(cost)s"), {
component: "[font=\"sans-12\"]" + getCostComponentDisplayName(resource) + "[/font]",
cost: resources[resource]
}));
return "\n\n[font=\"sans-bold-13\"][color=\"red\"]" + translate("Insufficient resources:") + "[/color][/font]\n" + formatted.join(" ");
}
// ==============================================
// IDENTITY INFO
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)
{
var names = "";
var generic = template.name.generic;
var 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)
{
var r = ""
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
r += "\n[font=\"sans-bold-13\"]" + translate("Classes:") + "[/font] ";
r += "[font=\"sans-13\"]" + translate(template.visibleIdentityClasses[0]) ;
for (var c = 1; c < template.visibleIdentityClasses.length; c++)
r += ", " + translate(template.visibleIdentityClasses[c]);
r += "[/font]";
}
return r;
}
function getEntityRankedName(entState)
{
var template = GetTemplateData(entState.template)
var rank = entState.identity.rank;
if (rank)
return sprintf(translate("%(rank)s %(name)s"), { rank: rank, name: template.name.specific });
else
return template.name.specific;
}
function getRankIconSprite(entState)
{
if ("Elite" == entState.identity.rank)
@@ -575,25 +117,6 @@ function getRankIconSprite(entState)
return "";
}
// ==============================================
// OTHER INFO
function getEntitySpeed(template)
{
var speed = "";
if (template.speed)
{
var label = "[font=\"sans-bold-13\"]" + translate("Speed:") + "[/font]";
var speeds = [];
if (template.speed.walk)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.walk, movementType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Walk") + "[/color][/font]"}));
if (template.speed.run)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.run, movementType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Run") + "[/color][/font]"}));
speed = sprintf(translate("%(label)s %(speeds)s"), { label: label, speeds: speeds.join(translate(", ")) })
}
return speed;
}
/**
* Returns a message with the details of the trade gain.
*/
@@ -624,8 +147,6 @@ function getTradingTooltip(gain)
return tooltip;
}
/**
* Returns the entity itself except when garrisoned where it returns its garrisonHolder
*/
@@ -638,78 +159,3 @@ function getEntityOrHolder(ent)
return ent;
}
function getLocalizedResourceName(resourceCode, context)
{
if (!localisedResourceNames[context])
{
warn("Internationalization: Unexpected context for resource type localization found: " + context + ". This context is not supported.");
return resourceCode;
}
if (!localisedResourceNames[context][resourceCode])
{
warn("Internationalization: Unexpected resource type found with code " + resourceCode + ". This resource type must be internationalized.");
return resourceCode;
}
return localisedResourceNames[context][resourceCode];
}
var localisedResourceNames = {};
localisedResourceNames.firstWord = {
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"food": translateWithContext("firstWord", "Food"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"meat": translateWithContext("firstWord", "Meat"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"metal": translateWithContext("firstWord", "Metal"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ore": translateWithContext("firstWord", "Ore"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"rock": translateWithContext("firstWord", "Rock"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ruins": translateWithContext("firstWord", "Ruins"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"stone": translateWithContext("firstWord", "Stone"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"treasure": translateWithContext("firstWord", "Treasure"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"tree": translateWithContext("firstWord", "Tree"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"wood": translateWithContext("firstWord", "Wood"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fruit": translateWithContext("firstWord", "Fruit"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"grain": translateWithContext("firstWord", "Grain"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fish": translateWithContext("firstWord", "Fish"),
};
localisedResourceNames.withinSentence = {
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"food": translateWithContext("withinSentence", "Food"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"meat": translateWithContext("withinSentence", "Meat"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"metal": translateWithContext("withinSentence", "Metal"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ore": translateWithContext("withinSentence", "Ore"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"rock": translateWithContext("withinSentence", "Rock"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ruins": translateWithContext("withinSentence", "Ruins"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"stone": translateWithContext("withinSentence", "Stone"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"treasure": translateWithContext("withinSentence", "Treasure"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"tree": translateWithContext("withinSentence", "Tree"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"wood": translateWithContext("withinSentence", "Wood"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fruit": translateWithContext("withinSentence", "Fruit"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"grain": translateWithContext("withinSentence", "Grain"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fish": translateWithContext("withinSentence", "Fish"),
};