Index: binaries/data/mods/public/globalscripts/utility.js =================================================================== --- binaries/data/mods/public/globalscripts/utility.js +++ binaries/data/mods/public/globalscripts/utility.js @@ -61,6 +61,18 @@ return path.slice(path.lastIndexOf("/") + 1); } +/** + * Returns the directories of a given path. + * + * ie. a/b/c/file.ext -> a/b/c + */ +function dirname(path) +{ + if (path.lastIndexOf("/") == -1) + return ""; + return path.slice(0, path.lastIndexOf("/")); +} + /** * Returns names of files found in the given directory, stripping the directory path and file extension. */ Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -2,7 +2,10 @@ "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,10 @@ }); } +/** + * Entity templates have a `Tooltip` tag in the Identity component. + * (The contents of which are copied to a `tooltip` attribute in globalscripts.) + */ function getEntityTooltip(template) { if (!template.tooltip) @@ -118,6 +125,35 @@ 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) @@ -563,7 +599,15 @@ } 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 ""; } @@ -711,15 +755,15 @@ 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]' }); } @@ -770,3 +814,13 @@ "details": lootLabels.join(" ") }); } + +function templateViewerOnClickMessage(rightClickOnly) +{ + if (rightClickOnly) + // 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."); + + // 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."); +} Index: binaries/data/mods/public/gui/page_viewer.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/page_viewer.xml @@ -0,0 +1,19 @@ + + + common/modern/setup.xml + common/modern/styles.xml + common/modern/sprites.xml + + common/setup_resources.xml + common/sprites.xml + common/styles.xml + + reference/common/sprites.xml + reference/common/styles.xml + + reference/viewer/styles.xml + reference/viewer/sprites.xml + reference/viewer/viewer.xml + + reference/common/setup.xml + Index: binaries/data/mods/public/gui/reference/common/core.js =================================================================== --- binaries/data/mods/public/gui/reference/common/core.js +++ binaries/data/mods/public/gui/reference/common/core.js @@ -1,4 +1,4 @@ -var g_SelectedCiv = ""; +var g_SelectedCiv = "gaia"; var g_CallbackSet = false; function closePage() @@ -94,6 +94,11 @@ 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); @@ -152,7 +157,7 @@ { 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) @@ -173,3 +178,45 @@ return production; } + +/** + * Returns the name of a template's base, or the template's own name if the base + * is of a different promotion rank, or a mirage or resource-supplying corpse. + */ +function getBaseTemplateName(templateName) +{ + if (!templateName || !Engine.TemplateExists(templateName)) + return undefined; + + let template = loadTemplate(templateName); + + if (!template["@parent"] || 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] > 0) + 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; +} Index: binaries/data/mods/public/gui/reference/common/draw.js =================================================================== --- binaries/data/mods/public/gui/reference/common/draw.js +++ binaries/data/mods/public/gui/reference/common/draw.js @@ -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 @@ getProjectilesTooltip, getSpeedTooltip, getGatherTooltip, + getResourceSupplyTooltip, getPopulationBonusTooltip, getResourceTrickleTooltip, getLootTooltip @@ -37,3 +37,23 @@ { return textFunctions.map(func => func(template)).filter(tip => tip).join(joiner); } + +/** + * 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("∞") + }); +} Index: binaries/data/mods/public/gui/reference/common/helper.js =================================================================== --- binaries/data/mods/public/gui/reference/common/helper.js +++ binaries/data/mods/public/gui/reference/common/helper.js @@ -24,6 +24,7 @@ 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; Index: binaries/data/mods/public/gui/reference/common/load.js =================================================================== --- binaries/data/mods/public/gui/reference/common/load.js +++ binaries/data/mods/public/gui/reference/common/load.js @@ -36,12 +36,16 @@ { // 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 @@ 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; } @@ -108,8 +112,18 @@ let template = loadTemplate(templateName); let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers); + unit.name.internal = templateName; + + unit.history = template.Identity.History; + unit.production = loadProductionQueue(template); + if (template.Identity.Rank) + unit.promotion = { + "current_rank": template.Identity.Rank, + "entity": template.Promotion && template.Promotion.Entity + }; + if (template.Builder && template.Builder.Entities._string) { unit.builder = []; @@ -124,6 +138,12 @@ if (unit.upgrades) unit.upgrades = getActualUpgradeData(unit.upgrades); + if (template.ResourceSupply) + unit.supply = { + "type": template.ResourceSupply.Type.split("."), + "amount": template.ResourceSupply.Amount, + }; + return unit; } @@ -140,6 +160,9 @@ let template = loadTemplate(templateName); let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers); + structure.name.internal = templateName; + + structure.history = template.Identity.History; structure.production = loadProductionQueue(template); @@ -208,9 +231,32 @@ }); } + if (template.ResourceSupply) + structure.supply = { + "type": template.ResourceSupply.Type.split("."), + "amount": template.ResourceSupply.Amount, + }; + return structure; } +function loadResource(templateName) +{ + let template = loadTemplate(templateName); + let resource = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers); + resource.name.internal = templateName; + + resource.history = template.Identity.History; + + if (template.ResourceSupply) + resource.supply = { + "type": template.ResourceSupply.Type.split("."), + "amount": template.ResourceSupply.Amount, + }; + + return resource; +} + /** * Load and parse technology from json template. * @@ -221,6 +267,7 @@ { let template = loadTechData(techName); let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData); + tech.name.internal = "tech/" + techName; if (template.pair !== undefined) { Index: binaries/data/mods/public/gui/reference/structree/draw.js =================================================================== --- binaries/data/mods/public/gui/reference/structree/draw.js +++ binaries/data/mods/public/gui/reference/structree/draw.js @@ -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 @@ "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 @@ 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 @@ } 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" + templateViewerOnClickMessage(false); +} + /** * Calculate row position offset (accounting for different number of prod rows per phase). * Index: binaries/data/mods/public/gui/reference/structree/rows.xml =================================================================== --- binaries/data/mods/public/gui/reference/structree/rows.xml +++ binaries/data/mods/public/gui/reference/structree/rows.xml @@ -6,14 +6,14 @@ - - + Index: binaries/data/mods/public/gui/reference/structree/structree.xml =================================================================== --- binaries/data/mods/public/gui/reference/structree/structree.xml +++ binaries/data/mods/public/gui/reference/structree/structree.xml @@ -96,12 +96,12 @@ - - + Index: binaries/data/mods/public/gui/reference/viewer/sprites.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/reference/viewer/sprites.xml @@ -0,0 +1,18 @@ + + + + + + + + Index: binaries/data/mods/public/gui/reference/viewer/styles.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/reference/viewer/styles.xml @@ -0,0 +1,14 @@ + + + +