Implement an in-game Template Details Viewer

Right-click on icons of units, structures, technologies, flora, fauna,
etc. and view additional details.


Reviewed By: elexis
Fixes: #3212
Differential Revision: https://code.wildfiregames.com/D297
This was SVN commit r21310.
This commit is contained in:
s0600204
2018-02-21 21:39:00 +00:00
parent b94aff0955
commit c8fda40b02
18 changed files with 569 additions and 29 deletions
@@ -390,6 +390,7 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
ret.nativeCiv = template.Identity.Civ;
}
if (template.UnitMotion)
@@ -2,7 +2,10 @@ var g_TooltipTextFormats = {
"unit": { "font": "sans-10", "color": "orange" },
"header": { "font": "sans-bold-13" },
"body": { "font": "sans-13" },
"comma": { "font": "sans-12" }
"comma": { "font": "sans-12" },
"nameSpecificBig": { "font": "sans-bold-16" },
"nameSpecificSmall": { "font": "sans-bold-12" },
"nameGeneric": { "font": "sans-bold-16" }
};
var g_AttackTypes = {
@@ -110,6 +113,12 @@ function getSecondsString(seconds)
});
}
/**
* Entity templates have a `Tooltip` tag in the Identity component.
* (The contents of which are copied to a `tooltip` attribute in globalscripts.)
*
* Technologies have a `tooltip` attribute.
*/
function getEntityTooltip(template)
{
if (!template.tooltip)
@@ -118,6 +127,35 @@ function getEntityTooltip(template)
return bodyFont(template.tooltip);
}
/**
* Technologies have a `description` attribute, and Auras have an `auraDescription`
* attribute, which becomes `description`.
*
* (For technologies, this happens in globalscripts.)
*
* (For auras, this happens either in the Auras component (for session gui) or
* reference/common/load.js (for Reference Suite gui))
*/
function getDescriptionTooltip(template)
{
if (!template.description)
return "";
return bodyFont(template.description);
}
/**
* Entity templates have a `History` tag in the Identity component.
* (The contents of which are copied to a `history` attribute in globalscripts.)
*/
function getHistoryTooltip(template)
{
if (!template.history)
return "";
return bodyFont(template.history);
}
function getHealthTooltip(template)
{
if (!template.health)
@@ -569,7 +607,15 @@ function getEntityCostTooltip(template, entity, buildingsCountToTrainFullBatch,
}
if (template.cost)
return getEntityCostComponentsTooltipString(template, entity, buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch).join(" ");
{
let costs = getEntityCostComponentsTooltipString(template, entity, buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch).join(" ");
if (costs)
// Translation: Label in tooltip showing cost of a unit, structure or technology.
return sprintf(translate("%(label)s %(costs)s"), {
"label": headerFont(translate("Cost:")),
"costs": costs
});
}
return "";
}
@@ -717,15 +763,15 @@ function getEntityNames(template)
function getEntityNamesFormatted(template)
{
if (!template.name.specific)
return '[font="sans-bold-16"]' + template.name.generic + "[/font]";
return setStringTags(template.name.generic, g_TooltipTextFormats.nameSpecificBig);
// Translation: Example: "Epibátēs Athēnaîos [font="sans-bold-16"](Athenian Marine)[/font]"
return sprintf(translate("%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s"), {
"specificName":
'[font="sans-bold-16"]' + template.name.specific[0] + '[/font]' +
'[font="sans-bold-12"]' + template.name.specific.slice(1).toUpperCase() + '[/font]',
setStringTags(template.name.specific[0], g_TooltipTextFormats.nameSpecificBig) +
setStringTags(template.name.specific.slice(1).toUpperCase(), g_TooltipTextFormats.nameSpecificSmall),
"genericName": template.name.generic,
"fontStart": '[font="sans-bold-16"]',
"fontStart": '[font="' + g_TooltipTextFormats.nameGeneric.font + '"]',
"fontEnd": '[/font]'
});
}
@@ -776,3 +822,15 @@ function getLootTooltip(template)
"details": lootLabels.join(" ")
});
}
function showTemplateViewerOnRightClickTooltip()
{
// Translation: Appears in a tooltip to indicate that right-clicking the corresponding GUI element will open the Template Details GUI page.
return translate("Right-click to view more information.");
}
function showTemplateViewerOnClickTooltip()
{
// Translation: Appears in a tooltip to indicate that clicking the corresponding GUI element will open the Template Details GUI page.
return translate("Click to view more information.");
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/modern/setup.xml</include>
<include>common/modern/styles.xml</include>
<include>common/modern/sprites.xml</include>
<include>common/setup_resources.xml</include>
<include>common/sprites.xml</include>
<include>common/styles.xml</include>
<include>reference/common/sprites.xml</include>
<include>reference/common/styles.xml</include>
<include>reference/viewer/styles.xml</include>
<include>reference/viewer/sprites.xml</include>
<include>reference/viewer/viewer.xml</include>
<include>reference/common/setup.xml</include>
</page>
@@ -1,4 +1,4 @@
var g_SelectedCiv = "";
var g_SelectedCiv = "gaia";
var g_CallbackSet = false;
function closePage()
@@ -94,6 +94,11 @@ function getTemplateListsFromUnit(templateName)
if (!templateName || !Engine.TemplateExists(templateName))
return {};
// If this is a non-promotion variant (ie. {civ}_support_female_citizen_house)
// then it is functionally equivalent to another unit being processed, so skip it.
if (getBaseTemplateName(templateName) != templateName)
return {};
let template = loadTemplate(templateName);
let templateLists = loadProductionQueue(template);
@@ -144,7 +149,7 @@ function loadProductionQueue(template)
{
templateName = templateName.replace(/\{(civ|native)\}/g, g_SelectedCiv);
if (Engine.TemplateExists(templateName))
production.units.push(templateName);
production.units.push(getBaseTemplateName(templateName));
}
if (template.ProductionQueue.Technologies && template.ProductionQueue.Technologies._string)
@@ -182,3 +187,46 @@ function loadBuildQueue(template)
return buildQueue;
}
/**
* Returns the name of a template's base form (without `_house`, `_trireme`, or similar),
* or the template's own name if the base is of a different promotion rank.
*/
function getBaseTemplateName(templateName)
{
if (!templateName || !Engine.TemplateExists(templateName))
return undefined;
templateName = removeFiltersFromTemplateName(templateName);
let template = loadTemplate(templateName);
if (dirname(template["@parent"]) != dirname(templateName))
return templateName;
let parentTemplate = loadTemplate(template["@parent"]);
if (parentTemplate.Identity && parentTemplate.Identity.Rank &&
parentTemplate.Identity.Rank != template.Identity.Rank)
return templateName;
if (!parentTemplate.Cost)
return templateName;
for (let res in parentTemplate.Cost.Resources)
if (parentTemplate.Cost.Resources[res])
return getBaseTemplateName(template["@parent"]);
return templateName;
}
function setViewerOnPress(guiObjectName, templateName)
{
let viewerFunc = () => {
Engine.PushGuiPage("page_viewer.xml", {
"templateName": templateName,
"civ": g_SelectedCiv
});
};
Engine.GetGUIObjectByName(guiObjectName).onPress = viewerFunc;
Engine.GetGUIObjectByName(guiObjectName).onPressRight = viewerFunc;
}
@@ -4,13 +4,12 @@
var g_DrawLimits = {};
/**
* These functions are defined in gui/common/tooltips.js
* List of functions that get the statistics of any template or entity,
* formatted in such a way as to appear in a tooltip.
*
* The functions listed are defined in gui/common/tooltips.js
*/
var g_TooltipFunctions = [
getEntityNamesFormatted,
getEntityCostTooltip,
getEntityTooltip,
getAurasTooltip,
var g_StatsFunctions = [
getHealthTooltip,
getHealerTooltip,
getAttackTooltip,
@@ -20,6 +19,7 @@ var g_TooltipFunctions = [
getProjectilesTooltip,
getSpeedTooltip,
getGatherTooltip,
getResourceSupplyTooltip,
getPopulationBonusTooltip,
getResourceTrickleTooltip,
getLootTooltip
@@ -37,3 +37,37 @@ function buildText(template, textFunctions=[], joiner="\n")
{
return textFunctions.map(func => func(template)).filter(tip => tip).join(joiner);
}
/**
* Creates text in the following format:
* Header: value1, value2, ..., valueN
*/
function buildListText(headerString, arrayOfValues)
{
// Translation: Label followed by a list of values.
return sprintf(translate("%(listHeader)s %(listOfValues)s"), {
"listHeader": headerFont(headerString),
// Translation: List separator.
"listOfValues": bodyFont(arrayOfValues.join(translate(", ")))
});
}
/**
* Returns the resources this entity supplies in the specified entity's tooltip
*/
function getResourceSupplyTooltip(template)
{
if (!template.supply)
return "";
let supply = template.supply;
let type = supply.type[0] == "treasure" ? supply.type[1] : supply.type[0];
// Translation: Label in tooltip showing the resource type and quantity of a given resource supply.
return sprintf(translate("%(label)s %(component)s %(amount)s"), {
"label": headerFont(translate("Resource Supply:")),
"component": resourceIcon(type),
// Translation: Marks that a resource supply entity has an unending, infinite, supply of its resource.
"amount": Number.isFinite(+supply.amount) ? supply.amount : translate("∞")
});
}
@@ -24,6 +24,7 @@ function getActualUpgradeData(upgradesInfo)
upgrade.entity = upgrade.entity.replace(/\{(civ|native)\}/g, g_SelectedCiv);
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData, g_ResourceData, g_DamageTypes);
data.name.internal = upgrade.entity;
data.cost = upgrade.cost;
data.icon = upgrade.icon || data.icon;
data.tooltip = upgrade.tooltip || data.tooltip;
@@ -36,12 +36,16 @@ function loadTemplate(templateName)
{
// We need to clone the template because we want to perform some translations.
let data = clone(Engine.GetTemplate(templateName));
translateObjectKeys(data, ["GenericName", "SpecificName", "Tooltip"]);
translateObjectKeys(data, ["GenericName", "SpecificName", "Tooltip", "History"]);
if (data.Auras)
for (let auraID of data.Auras._string.split(/\s+/))
loadAuraData(auraID);
if (data.Identity.Civ != "gaia" && g_SelectedCiv != "gaia" && data.Identity.Civ != g_SelectedCiv)
warn("The \"" + templateName + "\" template has a defined civ of \"" + data.Identity.Civ + "\". " +
"This does not match the currently selected civ \"" + g_SelectedCiv + "\".");
g_TemplateData[templateName] = data;
}
@@ -61,7 +65,7 @@ function loadTechData(templateName)
if (!(templateName in g_TechnologyData))
{
let data = Engine.ReadJSONFile(g_TechnologyPath + templateName + ".json");
translateObjectKeys(data, ["genericName", "tooltip"]);
translateObjectKeys(data, ["genericName", "tooltip", "description"]);
g_TechnologyData[templateName] = data;
}
@@ -100,7 +104,7 @@ function loadAuraData(templateName)
*
* @return {(object|null)} Sanitized object about the requested template or null if entity template doesn't exist.
*/
function loadEntityTemplate (templateName)
function loadEntityTemplate(templateName)
{
if (!Engine.TemplateExists(templateName))
return null;
@@ -109,10 +113,18 @@ function loadEntityTemplate (templateName)
let parsed = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
parsed.name.internal = templateName;
parsed.history = template.Identity.History;
parsed.production = loadProductionQueue(template);
if (template.Builder)
parsed.builder = loadBuildQueue(template);
if (template.Identity.Rank)
parsed.promotion = {
"current_rank": template.Identity.Rank,
"entity": template.Promotion && template.Promotion.Entity
};
if (template.ResourceSupply)
parsed.supply = {
"type": template.ResourceSupply.Type.split("."),
@@ -197,6 +209,7 @@ function loadTechnology(techName)
{
let template = loadTechData(techName);
let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
tech.name.internal = techName;
if (template.pair !== undefined)
{
@@ -1,3 +1,13 @@
/**
* Functions used to collate the contents of a tooltip.
*/
var g_StructreeTooltipFunctions = [
getEntityNamesFormatted,
getEntityCostTooltip,
getEntityTooltip,
getAurasTooltip
].concat(g_StatsFunctions);
/**
* Draw the structree
*
@@ -44,11 +54,12 @@ function draw()
"stretched:session/portraits/"+stru.icon;
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_icon").tooltip =
buildText(stru, g_TooltipFunctions);
compileTooltip(stru);
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_name").caption =
translate(stru.name.specific);
setViewerOnPress("phase["+i+"]_struct["+s+"]_icon", stru.name.internal);
thisEle.hidden = false;
for (let r in g_DrawLimits[pha].prodQuant)
@@ -154,8 +165,9 @@ function draw()
trainer = g_ParsedData.units[trainer];
Engine.GetGUIObjectByName("trainer["+t+"]_icon").sprite = "stretched:session/portraits/"+trainer.icon;
Engine.GetGUIObjectByName("trainer["+t+"]_icon").tooltip = buildText(trainer, g_TooltipFunctions);
Engine.GetGUIObjectByName("trainer["+t+"]_icon").tooltip = compileTooltip(trainer);
Engine.GetGUIObjectByName("trainer["+t+"]_name").caption = translate(trainer.name.specific);
setViewerOnPress("trainer["+t+"]_icon", trainer.name.internal);
thisEle.hidden = false;
let p = 0;
@@ -242,11 +254,17 @@ function drawProdIcon(phase, parentID, rowID, iconID, template)
}
prodEle.sprite = "stretched:session/portraits/"+template.icon;
prodEle.tooltip = buildText(template, g_TooltipFunctions);
prodEle.tooltip = compileTooltip(template);
prodEle.hidden = false;
setViewerOnPress(prodEle.name, template.name.internal);
return true;
}
function compileTooltip(template)
{
return buildText(template, g_StructreeTooltipFunctions) + "\n" + showTemplateViewerOnClickTooltip();
}
/**
* Calculate row position offset (accounting for different number of prod rows per phase).
*
@@ -6,14 +6,14 @@
<repeat count="20" var="s">
<object type="image" style="StructBox" name="phase[k]_struct[s]">
<object type="text" style="StructNameSpecific" name="phase[k]_struct[s]_name"/>
<object type="image" style="StructIcon" name="phase[k]_struct[s]_icon"
<object type="button" style="StructIcon" name="phase[k]_struct[s]_icon"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
/>
<object name="phase[k]_struct[s]_rows">
<repeat count="4" var="r">
<object name="phase[k]_struct[s]_row[r]">
<repeat count="24" var="p">
<object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
<object type="button" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
</repeat>
</object>
</repeat>
@@ -96,12 +96,12 @@
<repeat count="6" var="t">
<object type="image" style="StructBox" name="trainer[t]">
<object type="text" style="StructNameSpecific" name="trainer[t]_name"/>
<object type="image" style="StructIcon" name="trainer[t]_icon"
<object type="button" style="StructIcon" name="trainer[t]_icon"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
/>
<object name="trainer[t]_row">
<repeat count="4" var="p">
<object type="image" style="ProdBox" name="trainer[t]_prod[p]"/>
<object type="button" style="ProdBox" name="trainer[t]_prod[p]"/>
</repeat>
</object>
</object>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<sprites>
<sprite name="IconFrame_Gold">
<image
border="true"
bordercolor="163 163 103"
backcolor="0 0 0 0"
size="0 0 100% 100%"
/>
<image
border="true"
bordercolor="0 0 0"
backcolor="0 0 0 0"
size="1 1 100%-1 100%-1"
/>
</sprite>
</sprites>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<styles>
<style name="IconFrame"
sprite="IconFrame_Gold"
size="0 0 100% 100%"
ghost="true"
/>
<style name="RankGlyph"
size="4 6 20 22"
ghost="true"
/>
</styles>
@@ -0,0 +1,225 @@
/**
* Holder for the template file being displayed.
*/
var g_Template = {};
/**
* Holder for the template lists generated by compileTemplateLists().
*/
var g_TemplateLists = {};
/**
* Used to display textual information and the build/train lists of the
* template being displayed.
*
* At present, these are drawn in the main body of the page.
*/
var g_InfoFunctions = [
getEntityTooltip,
getHistoryTooltip,
getDescriptionTooltip,
getAurasTooltip,
getVisibleEntityClassesFormatted,
getBuiltByText,
getTrainedByText,
getResearchedByText,
getBuildText,
getTrainText,
getResearchText,
getUpgradeText
];
/**
* Override style so we can get a bigger specific name.
*/
g_TooltipTextFormats.nameSpecificBig.font = "sans-bold-20";
g_TooltipTextFormats.nameSpecificSmall.font = "sans-bold-16";
g_TooltipTextFormats.nameGeneric.font = "sans-bold-16";
/**
* Path to unit rank icons.
*/
var g_RankIconPath = "session/icons/ranks/";
/**
* Page initialisation. May also eventually pre-draw/arrange objects.
*
* @param {object} data - Contains the civCode and the name of the template to display.
* @param {string} data.templateName
* @param {string} [data.civ]
* @param {*} [data.callback] - If set and loosely equivalent to true, a callback is
* assumed to be setup ready be called by the Engine upon
* closure of this page.
*/
function init(data)
{
if (!data || !data.templateName)
{
error("Viewer: No template provided");
closePage();
return;
}
if (data.callback)
g_CallbackSet = true;
let templateName = removeFiltersFromTemplateName(data.templateName);
let isTech = techDataExists(templateName);
// Attempt to get the civ code from the template, or, if
// it's a technology, from the researcher's template.
if (!isTech)
{
// Catch and redirect if template is a non-promotion variant of
// another (ie. units/{civ}_support_female_citizen_house).
templateName = getBaseTemplateName(templateName);
g_SelectedCiv = loadTemplate(templateName).Identity.Civ;
}
if (g_SelectedCiv == "gaia" && data.civ)
g_SelectedCiv = data.civ;
g_TemplateLists = compileTemplateLists(g_SelectedCiv);
g_CurrentModifiers = deriveModifications(g_AutoResearchTechList);
g_Template = isTech ? loadTechnology(templateName) : loadEntityTemplate(templateName);
if (!g_Template)
{
error("Viewer: unable to recognise or load template (" + templateName + ")");
closePage();
return;
}
g_StatsFunctions = [getEntityCostTooltip].concat(g_StatsFunctions);
draw();
}
/**
* Populate the UI elements.
*
* @todo (c++ change) Implement and use a function that fetches height of rendered text block from text object.
*/
function draw()
{
Engine.GetGUIObjectByName("entityName").caption = getEntityNamesFormatted(g_Template);
let entityIcon = Engine.GetGUIObjectByName("entityIcon");
entityIcon.sprite = "stretched:session/portraits/" + g_Template.icon;
let entityStats = Engine.GetGUIObjectByName("entityStats");
entityStats.caption = buildText(g_Template, g_StatsFunctions);
// This is something of a crude hack. See above todo.
let entityInfo = Engine.GetGUIObjectByName("entityInfo");
let lines = entityStats.caption.split("\n").length;
let fontSize = +entityStats.font.split("-")[1] + 4;
let infoSize = entityInfo.size;
infoSize.top = Math.max(entityIcon.size.bottom, lines * fontSize + entityStats.size.top) + 8;
entityInfo.size = infoSize;
entityInfo.caption = buildText(g_Template, g_InfoFunctions, "\n\n");
if (g_Template.promotion)
Engine.GetGUIObjectByName("entityRankGlyph").sprite = "stretched:" + g_RankIconPath + g_Template.promotion.current_rank + ".png";
Engine.GetGUIObjectByName("entityRankGlyph").hidden = !g_Template.promotion;
}
function getBuiltByText(template)
{
if (g_SelectedCiv == "gaia" || !g_TemplateLists.structures.has(template.name.internal))
return "";
let builders = g_TemplateLists.structures.get(template.name.internal);
if (!builders.length)
return "";
// Translation: Label before a list of the names of units that build the structure selected.
return buildListText(translate("Built by:"), builders.map(builder => getEntityNames(loadEntityTemplate(builder))));
}
function getTrainedByText(template)
{
if (g_SelectedCiv == "gaia" || !g_TemplateLists.units.has(template.name.internal))
return "";
let trainers = g_TemplateLists.units.get(template.name.internal);
if (!trainers.length)
return "";
// Translation: Label before a list of the names of structures or units that train the unit selected.
return buildListText(translate("Trained by:"), trainers.map(trainer => getEntityNames(loadEntityTemplate(trainer))));
}
function getResearchedByText(template)
{
if (g_SelectedCiv == "gaia" || !g_TemplateLists.techs.has(template.name.internal))
return "";
let researchers = g_TemplateLists.techs.get(template.name.internal);
if (!researchers.length)
return "";
// Translation: Label before a list of names of structures or units that research the technology selected.
return buildListText(translate("Researched at:"), researchers.map(researcher => getEntityNames(loadEntityTemplate(researcher))));
}
/**
* @return {string} List of the names of the buildings the selected unit can build.
*/
function getBuildText(template)
{
if (!template.builder || !template.builder.length)
return "";
// Translation: Label before a list of the names of structures the selected unit can construct or build.
return buildListText(translate("Builds:"),
template.builder.map(prod => getEntityNames(loadEntityTemplate(prod))));
}
/**
* @return {string} List of the names of the technologies the selected structure/unit can research.
*/
function getResearchText(template)
{
if (!template.production || !template.production.techs || !template.production.techs.length)
return "";
let researchNames = [];
for (let tech of template.production.techs)
{
let techTemplate = loadTechnology(tech);
if (techTemplate.reqs)
researchNames.push(getEntityNames(techTemplate));
}
// Translation: Label before a list of the names of technologies the selected unit or structure can research.
return buildListText(translate("Researches:"), researchNames);
}
/**
* @return {string} List of the names of the units the selected unit can train.
*/
function getTrainText(template)
{
if (!template.production || !template.production.units || !template.production.units.length)
return "";
// Translation: Label before a list of the names of units the selected unit or structure can train.
return buildListText(translate("Trains:"),
template.production.units.map(prod => getEntityNames(loadEntityTemplate(prod))));
}
/**
* @return {string} List of the names of the buildings/units the selected structure/unit can upgrade to.
*/
function getUpgradeText(template)
{
if (!template.upgrades)
return "";
// Translation: Label before a list of the names of units or structures the selected unit or structure can be upgradable to.
return buildListText(translate("Upgradable to:"),
template.upgrades.map(upgrade => getEntityNames(upgrade.name ? upgrade : loadEntityTemplate(upgrade.entity))));
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script directory="gui/common/"/>
<script directory="gui/reference/common/"/>
<script directory="gui/reference/viewer/"/>
<!-- Add a translucent black background to fade out whatever's behind this -->
<object type="image" z="0" sprite="BackgroundTranslucent"/>
<object type="image" sprite="ModernDialog" size="50%-250 50%-300 50%+250 50%+300">
<object style="TitleText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">Information</translatableAttribute>
</object>
<object name="entityName" type="text" size="8 16 100%-8 48" text_align="center" text_valign="top" textcolor="white"/>
<object name="entityIcon" type="image"
size="16 48 128+16 128+48"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
>
<object type="image" style="IconFrame"/>
<object type="image" style="RankGlyph" name="entityRankGlyph"/>
</object>
<object name="entityStats" type="text" style="ModernText" size="128+20 44 100%-8 50%" scrollbar="false"/>
<object name="entityInfo" type="text" style="ModernText" size="16 128+48+8 100%-16 100%-48"/>
<!-- Close page -->
<object
type="button"
style="StoneButton"
size="100%-164 100%-44 100%-16 100%-16"
hotkey="cancel"
>
<translatableAttribute id="caption">Close</translatableAttribute>
<action on="Press">closePage();</action>
</object>
</object>
</objects>
@@ -284,6 +284,10 @@ function displaySingle(entState)
// TODO: we should require all entities to have icons
Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "BackgroundBlack";
if (template.icon)
Engine.GetGUIObjectByName("iconBorder").onPressRight = () => {
showTemplateDetails(entState.template);
};
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = [
getAttackTooltip,
@@ -306,7 +310,8 @@ function displaySingle(entState)
iconTooltips = iconTooltips.concat([
getVisibleEntityClassesFormatted,
getAurasTooltip,
getEntityTooltip
getEntityTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template)));
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(tip => tip).join("\n");
@@ -259,6 +259,7 @@ g_SelectionPanels.Construction = {
});
data.button.onPress = function() { startBuildingPlacement(data.item, data.playerState); };
data.button.onPressRight = function() { showTemplateDetails(data.item); };
let tooltips = [
getEntityNamesFormatted,
@@ -267,7 +268,8 @@ g_SelectionPanels.Construction = {
getEntityTooltip,
getEntityCostTooltip,
getGarrisonTooltip,
getPopulationBonusTooltip
getPopulationBonusTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
let limits = getEntityLimitAndCount(data.playerState, data.item);
@@ -742,7 +744,8 @@ g_SelectionPanels.Research = {
let tooltips = [
getEntityNamesFormatted,
getEntityTooltip,
getEntityCostTooltip
getEntityCostTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
if (!requirementsPassed)
@@ -793,6 +796,14 @@ g_SelectionPanels.Research = {
addResearchToQueue(data.item.researchFacilityId, tech);
};
button.onPressRight = function () {
let researcherTemplate;
for (let selectedEntity of data.unitEntStates)
if (selectedEntity.id == data.item.researchFacilityId)
researcherTemplate = selectedEntity.template;
showTemplateDetails(data.item.tech, GetTemplateData(researcherTemplate).nativeCiv);
};
if (data.item.tech.pair)
{
// On mouse enter, show a cross over the other icon
@@ -980,6 +991,9 @@ g_SelectionPanels.Training = {
data.button.onPress = function() {
addTrainingToQueue(data.unitEntStates.map(state => state.id), data.item, data.playerState);
};
data.button.onPressRight = function() {
showTemplateDetails(data.item);
};
data.countDisplay.caption = trainNum > 1 ? trainNum : "";
@@ -1008,6 +1022,7 @@ g_SelectionPanels.Training = {
getSpeedTooltip
].map(func => func(template)));
tooltips.push(showTemplateViewerOnRightClickTooltip());
tooltips.push(
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
@@ -1093,7 +1108,8 @@ g_SelectionPanels.Upgrade = {
getEntityCostComponentsTooltipString(data.item, undefined, data.unitEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
getNeededResourcesTooltip(neededResources),
showTemplateViewerOnRightClickTooltip());
tooltip = tooltips.filter(tip => tip).join("\n");
@@ -1112,6 +1128,10 @@ g_SelectionPanels.Upgrade = {
data.button.enabled = controlsPlayer(data.player);
data.button.tooltip = tooltip;
data.button.onPressRight = function() {
showTemplateDetails(data.item.entity);
};
let modifier = "";
if (!isUpgrading)
if (progress || !technologyEnabled || limits.canBeAddedCount == 0 &&
@@ -1146,6 +1166,25 @@ g_SelectionPanels.Upgrade = {
}
};
/**
* Pauses game and opens the template details viewer for a selected entity or technology.
*
* Technologies don't have a set civ, so we pass along the native civ of
* the template of the entity that's researching it.
*
* @param {string} [civCode] - The template name of the entity that researches the selected technology.
*/
function showTemplateDetails(templateName, civCode)
{
pauseGame();
Engine.PushGuiPage("page_viewer.xml", {
"templateName": templateName,
"callback": "resumeGame",
"civ": civCode
});
}
/**
* If two panels need the same space, so they collide,
* the one appearing first in the order is rendered.
@@ -67,7 +67,7 @@
</object>
<!-- Big unit icon -->
<object size="-8 -8 88 88" type="image" name="iconBorder" sprite="iconBorder" tooltip_style="sessionToolTip">
<object size="-8 -8 88 88" type="button" name="iconBorder" sprite="iconBorder" tooltip_style="sessionToolTip">
<object size="1 1 100%-1 100%-1" type="image" name="icon" ghost="true"/>
<!-- Experience bar -->
@@ -245,6 +245,7 @@
"gui/pregame/**.js",
"gui/reference/common/**.js",
"gui/reference/structree/**.js",
"gui/reference/viewer/**.js",
"gui/replaymenu/**.js",
"gui/savedgames/**.js",
"gui/splashscreen/**.js",
@@ -279,6 +280,7 @@
"gui/options/**.xml",
"gui/pregame/**.xml",
"gui/reference/structree/**.xml",
"gui/reference/viewer/**.xml",
"gui/replaymenu/**.xml",
"gui/savedgames/**.xml",
"gui/splashscreen/**.xml",
@@ -400,6 +402,7 @@
"keywords": {
"GenericName": {},
"SpecificName": {},
"History": {},
"VisibleClasses": {
"splitOnWhitespace": true
},
@@ -431,6 +434,7 @@
"keywords": {
"GenericName": {},
"SpecificName": {},
"History": {},
"VisibleClasses": {
"splitOnWhitespace": true
},
@@ -469,6 +473,7 @@
"keywords": {
"GenericName": {},
"SpecificName": {},
"History": {},
"VisibleClasses": {
"splitOnWhitespace": true
},