Rewrite Structure Tree and Template Viewer to use OOP principles
Commenters: elexis, Stan, Angen Refs.: #5387 Differential Revision: https://code.wildfiregames.com/D2734 This was SVN commit r23808.
This commit is contained in:
parent
365dbd91fc
commit
b2842e8021
@ -621,6 +621,26 @@ function getGatherTooltip(template)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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("∞")
|
||||
});
|
||||
}
|
||||
|
||||
function getResourceTrickleTooltip(template)
|
||||
{
|
||||
if (!template.resourceTrickle)
|
||||
|
@ -0,0 +1,27 @@
|
||||
class CivInfoButton
|
||||
{
|
||||
constructor(parentPage)
|
||||
{
|
||||
this.parentPage = parentPage;
|
||||
|
||||
this.civInfoButton = Engine.GetGUIObjectByName("civInfoButton");
|
||||
this.civInfoButton.onPress = this.onPress.bind(this);
|
||||
this.civInfoButton.caption = this.Caption;
|
||||
this.civInfoButton.tooltip = colorizeHotkey(this.Tooltip, this.Hotkey);
|
||||
}
|
||||
|
||||
onPress()
|
||||
{
|
||||
Engine.PopGuiPage({ "civ": this.parentPage.activeCiv, "nextPage": "page_civinfo.xml" });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CivInfoButton.prototype.Caption =
|
||||
translate("Civilization Overview");
|
||||
|
||||
CivInfoButton.prototype.Hotkey =
|
||||
"civinfo";
|
||||
|
||||
CivInfoButton.prototype.Tooltip =
|
||||
translate("%(hotkey)s: Switch to Civilization Overview.");
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object
|
||||
name="civInfoButton"
|
||||
type="button"
|
||||
style="StoneButton"
|
||||
size="100%-421 100%-44 100%-221 100%-16"
|
||||
hotkey="civinfo"
|
||||
/>
|
@ -0,0 +1,16 @@
|
||||
class CloseButton
|
||||
{
|
||||
constructor(parentPage)
|
||||
{
|
||||
this.closeButton = Engine.GetGUIObjectByName("closeButton");
|
||||
this.closeButton.onPress = parentPage.closePage.bind(parentPage);
|
||||
this.closeButton.caption = this.Caption;
|
||||
this.closeButton.tooltip = colorizeHotkey(parentPage.CloseButtonTooltip, this.Hotkey);
|
||||
}
|
||||
}
|
||||
|
||||
CloseButton.prototype.Caption =
|
||||
translate("Close");
|
||||
|
||||
CloseButton.prototype.Hotkey =
|
||||
"close";
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object
|
||||
name="closeButton"
|
||||
type="button"
|
||||
style="StoneButton"
|
||||
size="100%-216 100%-44 100%-16 100%-16"
|
||||
hotkey="cancel"
|
||||
/>
|
@ -0,0 +1,63 @@
|
||||
class CivSelectDropdown
|
||||
{
|
||||
constructor(civData)
|
||||
{
|
||||
this.handlers = new Set();
|
||||
|
||||
let civList = Object.keys(civData).map(civ => ({
|
||||
"name": civData[civ].Name,
|
||||
"code": civ,
|
||||
})).sort(sortNameIgnoreCase);
|
||||
|
||||
this.civSelectionHeading = Engine.GetGUIObjectByName("civSelectionHeading");
|
||||
this.civSelectionHeading.caption = this.Caption;
|
||||
|
||||
this.civSelection = Engine.GetGUIObjectByName("civSelection");
|
||||
this.civSelection.list = civList.map(c => c.name);
|
||||
this.civSelection.list_data = civList.map(c => c.code);
|
||||
this.civSelection.onSelectionChange = () => this.onSelectionChange(this);
|
||||
}
|
||||
|
||||
onSelectionChange()
|
||||
{
|
||||
let civCode = this.civSelection.list_data[this.civSelection.selected];
|
||||
|
||||
for (let handler of this.handlers)
|
||||
handler(civCode);
|
||||
}
|
||||
|
||||
registerHandler(handler)
|
||||
{
|
||||
this.handlers.add(handler);
|
||||
}
|
||||
|
||||
unregisterHandler(handler)
|
||||
{
|
||||
this.handlers.delete(handler);
|
||||
}
|
||||
|
||||
hasCivs()
|
||||
{
|
||||
return this.civSelection.list.length != 0;
|
||||
}
|
||||
|
||||
selectCiv(civCode)
|
||||
{
|
||||
if (!civCode)
|
||||
return;
|
||||
|
||||
let index = this.civSelection.list_data.indexOf(civCode);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
this.civSelection.selected = index;
|
||||
}
|
||||
|
||||
selectFirstCiv()
|
||||
{
|
||||
this.civSelection.selected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CivSelectDropdown.prototype.Caption =
|
||||
translate("Civilization:");
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object size="16 10 100%-16 30">
|
||||
<object
|
||||
name="civSelectionHeading"
|
||||
type="text"
|
||||
font="sans-bold-16"
|
||||
textcolor="white"
|
||||
text_align="right"
|
||||
size="0 10 100%-188 48"
|
||||
/>
|
||||
<object name="civSelection" type="dropdown" style="ModernDropDown" size="100%-180 8 100% 34" dropdown_size="424"/>
|
||||
</object>
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* This class contains code common to the Structure Tree, Template Viewer, and any other "Reference Page" that may be added in the future.
|
||||
*/
|
||||
class ReferencePage
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.civData = loadCivData(true, false);
|
||||
|
||||
this.TemplateLoader = new TemplateLoader();
|
||||
this.TemplateLister = new TemplateLister(this.TemplateLoader);
|
||||
this.TemplateParser = new TemplateParser(this.TemplateLoader);
|
||||
|
||||
this.activeCiv = this.TemplateLoader.DefaultCiv;
|
||||
|
||||
this.currentTemplateLists = {};
|
||||
}
|
||||
|
||||
setActiveCiv(civCode)
|
||||
{
|
||||
if (civCode == this.TemplateLoader.DefaultCiv)
|
||||
return;
|
||||
|
||||
this.activeCiv = civCode;
|
||||
|
||||
this.currentTemplateLists = this.TemplateLister.compileTemplateLists(this.activeCiv, this.civData);
|
||||
this.TemplateParser.deriveModifications(this.activeCiv);
|
||||
this.TemplateParser.derivePhaseList(this.currentTemplateLists.techs.keys(), this.activeCiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatanates the return values of the array of passed functions.
|
||||
*
|
||||
* @param {object} template
|
||||
* @param {array} textFunctions
|
||||
* @param {string} joiner
|
||||
* @return {string} The built text.
|
||||
*/
|
||||
static buildText(template, textFunctions=[], joiner="\n")
|
||||
{
|
||||
return textFunctions.map(func => func(template)).filter(tip => tip).join(joiner);
|
||||
}
|
||||
}
|
||||
|
||||
ReferencePage.prototype.IconPath = "session/portraits/";
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
ReferencePage.prototype.StatsFunctions = [
|
||||
getHealthTooltip,
|
||||
getHealerTooltip,
|
||||
getAttackTooltip,
|
||||
getSplashDamageTooltip,
|
||||
getArmorTooltip,
|
||||
getGarrisonTooltip,
|
||||
getProjectilesTooltip,
|
||||
getSpeedTooltip,
|
||||
getGatherTooltip,
|
||||
getResourceSupplyTooltip,
|
||||
getPopulationBonusTooltip,
|
||||
getResourceTrickleTooltip,
|
||||
getLootTooltip
|
||||
];
|
137
binaries/data/mods/public/gui/reference/common/TemplateLister.js
Normal file
137
binaries/data/mods/public/gui/reference/common/TemplateLister.js
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* This class compiles and stores lists of which templates can be built/trained/researched by other templates.
|
||||
*/
|
||||
class TemplateLister
|
||||
{
|
||||
constructor(TemplateLoader)
|
||||
{
|
||||
this.TemplateLoader = TemplateLoader;
|
||||
this.templateLists = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile lists of templates buildable/trainable/researchable of a given civ.
|
||||
*
|
||||
* @param {object} civCode
|
||||
* @param {object} civData - Data defining every civ in the game.
|
||||
*/
|
||||
compileTemplateLists(civCode, civData)
|
||||
{
|
||||
if (this.hasTemplateLists(civCode))
|
||||
return this.templateLists.get(civCode);
|
||||
|
||||
let templatesToParse = civData[civCode].StartEntities.map(entity => entity.Template);
|
||||
|
||||
let templateLists = {
|
||||
"units": new Map(),
|
||||
"structures": new Map(),
|
||||
"techs": new Map(),
|
||||
"wallsetPieces": new Map()
|
||||
};
|
||||
|
||||
do
|
||||
{
|
||||
const templatesThisIteration = templatesToParse;
|
||||
templatesToParse = [];
|
||||
|
||||
for (let templateBeingParsed of templatesThisIteration)
|
||||
{
|
||||
let list = this.deriveTemplateListsFromTemplate(templateBeingParsed, civCode);
|
||||
for (let type in list)
|
||||
for (let templateName of list[type])
|
||||
if (!templateLists[type].has(templateName))
|
||||
{
|
||||
templateLists[type].set(templateName, [templateBeingParsed]);
|
||||
if (type != "techs")
|
||||
templatesToParse.push(templateName);
|
||||
}
|
||||
else if (templateLists[type].get(templateName).indexOf(templateBeingParsed) == -1)
|
||||
templateLists[type].get(templateName).push(templateBeingParsed);
|
||||
}
|
||||
} while (templatesToParse.length);
|
||||
|
||||
// Expand/filter tech pairs
|
||||
for (let [techCode, researcherList] of templateLists.techs)
|
||||
{
|
||||
if (!this.TemplateLoader.isPairTech(techCode))
|
||||
continue;
|
||||
|
||||
for (let subTech of this.TemplateLoader.loadTechnologyPairTemplate(techCode, civCode).techs)
|
||||
if (!templateLists.techs.has(subTech))
|
||||
templateLists.techs.set(subTech, researcherList);
|
||||
else
|
||||
for (let researcher of researcherList)
|
||||
if (templateLists.techs.get(subTech).indexOf(researcher) == -1)
|
||||
templateLists.techs.get(subTech).push(researcher);
|
||||
|
||||
templateLists.techs.delete(techCode);
|
||||
}
|
||||
|
||||
// Remove wallset pieces, as they've served their purpose.
|
||||
delete templateLists.wallsetPieces;
|
||||
|
||||
this.templateLists.set(civCode, templateLists);
|
||||
return this.templateLists.get(civCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a civ's template list.
|
||||
*
|
||||
* Note: this civ must have gone through the compilation process above!
|
||||
*
|
||||
* @param {string} civCode
|
||||
* @return {object} containing lists of template names, grouped by type.
|
||||
*/
|
||||
getTemplateLists(civCode)
|
||||
{
|
||||
if (this.hasTemplateLists(civCode))
|
||||
return this.templateLists.get(civCode);
|
||||
|
||||
error("Template lists of \"" + civCode + "\" requested, but this civ has not been loaded.");
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the civ of the given civCode has been loaded into cache.
|
||||
*
|
||||
* @param {string} civCode
|
||||
* @return {boolean}
|
||||
*/
|
||||
hasTemplateLists(civCode)
|
||||
{
|
||||
return this.templateLists.has(civCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles lists of buildable, trainable, or researchable entities from
|
||||
* a named template.
|
||||
*/
|
||||
deriveTemplateListsFromTemplate(templateName, civCode)
|
||||
{
|
||||
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 (this.TemplateLoader.getBaseTemplateName(templateName, civCode) != templateName)
|
||||
return {};
|
||||
|
||||
let template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);
|
||||
|
||||
let templateLists = this.TemplateLoader.deriveProductionQueue(template, civCode);
|
||||
templateLists.structures = this.TemplateLoader.deriveBuildQueue(template, civCode);
|
||||
|
||||
if (template.WallSet)
|
||||
{
|
||||
templateLists.wallsetPieces = [];
|
||||
for (let segment in template.WallSet.Templates)
|
||||
{
|
||||
segment = template.WallSet.Templates[segment].replace(/\{(civ|native)\}/g, civCode);
|
||||
if (Engine.TemplateExists(segment))
|
||||
templateLists.wallsetPieces.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return templateLists;
|
||||
}
|
||||
}
|
251
binaries/data/mods/public/gui/reference/common/TemplateLoader.js
Normal file
251
binaries/data/mods/public/gui/reference/common/TemplateLoader.js
Normal file
@ -0,0 +1,251 @@
|
||||
/**
|
||||
* This class handles the loading of files.
|
||||
*/
|
||||
class TemplateLoader
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
/**
|
||||
* Raw Data Caches.
|
||||
*/
|
||||
this.auraData = {};
|
||||
this.technologyData = {};
|
||||
this.templateData = {};
|
||||
|
||||
/**
|
||||
* Partly-composed data.
|
||||
*/
|
||||
this.autoResearchTechList = this.findAllAutoResearchedTechs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads raw aura template.
|
||||
*
|
||||
* Loads from local cache if available, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
loadAuraTemplate(templateName)
|
||||
{
|
||||
if (!(templateName in this.auraData))
|
||||
{
|
||||
let data = Engine.ReadJSONFile(this.AuraPath + templateName + ".json");
|
||||
translateObjectKeys(data, this.AuraTranslateKeys);
|
||||
|
||||
this.auraData[templateName] = data;
|
||||
}
|
||||
|
||||
return this.auraData[templateName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads raw entity template.
|
||||
*
|
||||
* Loads from local cache if data present, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @param {string} civCode
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
loadEntityTemplate(templateName, civCode)
|
||||
{
|
||||
if (!(templateName in this.templateData))
|
||||
{
|
||||
// We need to clone the template because we want to perform some translations.
|
||||
let data = clone(Engine.GetTemplate(templateName));
|
||||
translateObjectKeys(data, this.EntityTranslateKeys);
|
||||
|
||||
if (data.Auras)
|
||||
for (let auraID of data.Auras._string.split(/\s+/))
|
||||
this.loadAuraTemplate(auraID);
|
||||
|
||||
if (data.Identity.Civ != this.DefaultCiv && civCode != this.DefaultCiv && data.Identity.Civ != civCode)
|
||||
warn("The \"" + templateName + "\" template has a defined civ of \"" + data.Identity.Civ + "\". " +
|
||||
"This does not match the currently selected civ \"" + civCode + "\".");
|
||||
|
||||
this.templateData[templateName] = data;
|
||||
}
|
||||
|
||||
return this.templateData[templateName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads raw technology template.
|
||||
*
|
||||
* Loads from local cache if available, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
loadTechnologyTemplate(templateName)
|
||||
{
|
||||
if (!(templateName in this.technologyData))
|
||||
{
|
||||
let data = Engine.ReadJSONFile(this.TechnologyPath + templateName + ".json");
|
||||
translateObjectKeys(data, this.TechnologyTranslateKeys);
|
||||
|
||||
this.technologyData[templateName] = data;
|
||||
}
|
||||
|
||||
return this.technologyData[templateName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} templateName
|
||||
* @param {string} civCode
|
||||
* @return {object} Contains a list and the requirements of the techs in the pair
|
||||
*/
|
||||
loadTechnologyPairTemplate(templateName, civCode)
|
||||
{
|
||||
let template = this.loadTechnologyTemplate(templateName);
|
||||
return {
|
||||
"techs": [template.top, template.bottom],
|
||||
"reqs": DeriveTechnologyRequirements(template, civCode)
|
||||
};
|
||||
}
|
||||
|
||||
deriveProductionQueue(template, civCode)
|
||||
{
|
||||
let production = {
|
||||
"techs": [],
|
||||
"units": []
|
||||
};
|
||||
|
||||
if (!template.ProductionQueue)
|
||||
return production;
|
||||
|
||||
if (template.ProductionQueue.Entities && template.ProductionQueue.Entities._string)
|
||||
for (let templateName of template.ProductionQueue.Entities._string.split(" "))
|
||||
{
|
||||
templateName = templateName.replace(/\{(civ|native)\}/g, civCode);
|
||||
if (Engine.TemplateExists(templateName))
|
||||
production.units.push(this.getBaseTemplateName(templateName, civCode));
|
||||
}
|
||||
|
||||
if (template.ProductionQueue.Technologies && template.ProductionQueue.Technologies._string)
|
||||
for (let technologyName of template.ProductionQueue.Technologies._string.split(" "))
|
||||
{
|
||||
if (technologyName.indexOf("{civ}") != -1)
|
||||
{
|
||||
let civTechName = technologyName.replace("{civ}", civCode);
|
||||
technologyName = TechnologyTemplateExists(civTechName) ? civTechName : technologyName.replace("{civ}", "generic");
|
||||
}
|
||||
|
||||
if (this.isPairTech(technologyName))
|
||||
Array.prototype.push.apply(production.techs, this.loadTechnologyPairTemplate(technologyName, civCode).techs);
|
||||
else
|
||||
production.techs.push(technologyName);
|
||||
}
|
||||
|
||||
return production;
|
||||
}
|
||||
|
||||
deriveBuildQueue(template, civCode)
|
||||
{
|
||||
let buildQueue = [];
|
||||
|
||||
if (!template.Builder || !template.Builder.Entities._string)
|
||||
return buildQueue;
|
||||
|
||||
for (let build of template.Builder.Entities._string.split(" "))
|
||||
{
|
||||
build = build.replace(/\{(civ|native)\}/g, civCode);
|
||||
if (Engine.TemplateExists(build))
|
||||
buildQueue.push(build);
|
||||
}
|
||||
|
||||
return buildQueue;
|
||||
}
|
||||
|
||||
deriveModifications(civCode)
|
||||
{
|
||||
let techData = [];
|
||||
for (let techName of this.autoResearchTechList)
|
||||
techData.push(GetTechnologyBasicDataHelper(this.loadTechnologyTemplate(techName), civCode));
|
||||
|
||||
return DeriveModificationsFromTechnologies(techData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crudely iterates through every tech JSON file and identifies those
|
||||
* that are auto-researched.
|
||||
*
|
||||
* @return {array} List of techs that are researched automatically
|
||||
*/
|
||||
findAllAutoResearchedTechs()
|
||||
{
|
||||
let techList = [];
|
||||
for (let templateName of listFiles(this.TechnologyPath, ".json", true))
|
||||
{
|
||||
let data = this.loadTechnologyTemplate(templateName);
|
||||
if (data && data.autoResearch)
|
||||
techList.push(templateName);
|
||||
}
|
||||
return techList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
getBaseTemplateName(templateName, civCode)
|
||||
{
|
||||
if (!templateName || !Engine.TemplateExists(templateName))
|
||||
return undefined;
|
||||
|
||||
templateName = removeFiltersFromTemplateName(templateName);
|
||||
let template = this.loadEntityTemplate(templateName, civCode);
|
||||
|
||||
if (!dirname(templateName) || dirname(template["@parent"]) != dirname(templateName))
|
||||
return templateName;
|
||||
|
||||
let parentTemplate = this.loadEntityTemplate(template["@parent"], civCode);
|
||||
|
||||
if (parentTemplate.Identity && parentTemplate.Identity.Rank &&
|
||||
parentTemplate.Identity.Rank != template.Identity.Rank)
|
||||
return templateName;
|
||||
|
||||
if (!parentTemplate.Cost)
|
||||
return templateName;
|
||||
|
||||
if (parentTemplate.Upgrade)
|
||||
for (let upgrade in parentTemplate.Upgrade)
|
||||
if (parentTemplate.Upgrade[upgrade].Entity)
|
||||
return templateName;
|
||||
|
||||
for (let res in parentTemplate.Cost.Resources)
|
||||
if (+parentTemplate.Cost.Resources[res])
|
||||
return this.getBaseTemplateName(template["@parent"], civCode);
|
||||
|
||||
return templateName;
|
||||
}
|
||||
|
||||
isPairTech(technologyCode)
|
||||
{
|
||||
return !!this.loadTechnologyTemplate(technologyCode).top;
|
||||
}
|
||||
|
||||
isPhaseTech(technologyCode)
|
||||
{
|
||||
return basename(technologyCode).startsWith("phase");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Paths to certain files.
|
||||
*
|
||||
* It might be nice if we could get these from somewhere, instead of having them hardcoded here.
|
||||
*/
|
||||
TemplateLoader.prototype.AuraPath = "simulation/data/auras/";
|
||||
TemplateLoader.prototype.TechnologyPath = "simulation/data/technologies/";
|
||||
|
||||
TemplateLoader.prototype.DefaultCiv = "gaia";
|
||||
|
||||
/**
|
||||
* Keys of template values that are to be translated on load.
|
||||
*/
|
||||
TemplateLoader.prototype.AuraTranslateKeys = ["auraName", "auraDescription"];
|
||||
TemplateLoader.prototype.EntityTranslateKeys = ["GenericName", "SpecificName", "Tooltip", "History"];
|
||||
TemplateLoader.prototype.TechnologyTranslateKeys = ["genericName", "tooltip", "description"];
|
321
binaries/data/mods/public/gui/reference/common/TemplateParser.js
Normal file
321
binaries/data/mods/public/gui/reference/common/TemplateParser.js
Normal file
@ -0,0 +1,321 @@
|
||||
/**
|
||||
* This class parses and stores parsed template data.
|
||||
*/
|
||||
class TemplateParser
|
||||
{
|
||||
constructor(TemplateLoader)
|
||||
{
|
||||
this.TemplateLoader = TemplateLoader;
|
||||
|
||||
/**
|
||||
* Parsed Data Stores
|
||||
*/
|
||||
this.entities = {};
|
||||
this.techs = {};
|
||||
this.phases = {};
|
||||
this.modifiers = {};
|
||||
|
||||
this.phaseList = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse a structure, unit, resource, etc from its entity template file.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @param {string} civCode
|
||||
* @return {(object|null)} Sanitized object about the requested template or null if entity template doesn't exist.
|
||||
*/
|
||||
getEntity(templateName, civCode)
|
||||
{
|
||||
if (templateName in this.entities)
|
||||
return this.entities[templateName];
|
||||
|
||||
if (!Engine.TemplateExists(templateName))
|
||||
return null;
|
||||
|
||||
let template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);
|
||||
let parsed = GetTemplateDataHelper(template, null, this.TemplateLoader.auraData, this.modifiers[civCode] || {});
|
||||
parsed.name.internal = templateName;
|
||||
|
||||
parsed.history = template.Identity.History;
|
||||
|
||||
parsed.production = this.TemplateLoader.deriveProductionQueue(template, civCode);
|
||||
if (template.Builder)
|
||||
parsed.builder = this.TemplateLoader.deriveBuildQueue(template, civCode);
|
||||
|
||||
// Set the minimum phase that this entity is available.
|
||||
// For gaia objects, this is meaningless.
|
||||
if (!parsed.requiredTechnology)
|
||||
parsed.phase = this.phaseList[0];
|
||||
else if (this.TemplateLoader.isPhaseTech(parsed.requiredTechnology))
|
||||
parsed.phase = this.getActualPhase(parsed.requiredTechnology);
|
||||
else
|
||||
parsed.phase = this.getPhaseOfTechnology(parsed.requiredTechnology, civCode);
|
||||
|
||||
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("."),
|
||||
"amount": template.ResourceSupply.Amount,
|
||||
};
|
||||
|
||||
if (parsed.upgrades)
|
||||
parsed.upgrades = this.getActualUpgradeData(parsed.upgrades, civCode);
|
||||
|
||||
if (parsed.wallSet)
|
||||
{
|
||||
parsed.wallset = {};
|
||||
|
||||
if (!parsed.upgrades)
|
||||
parsed.upgrades = [];
|
||||
|
||||
// Note: An assumption is made here that wall segments all have the same armor and auras
|
||||
let struct = this.getEntity(parsed.wallSet.templates.long, civCode);
|
||||
parsed.armour = struct.armour;
|
||||
parsed.auras = struct.auras;
|
||||
|
||||
// For technology cost multiplier, we need to use the tower
|
||||
struct = this.getEntity(parsed.wallSet.templates.tower, civCode);
|
||||
parsed.techCostMultiplier = struct.techCostMultiplier;
|
||||
|
||||
let health;
|
||||
|
||||
for (let wSegm in parsed.wallSet.templates)
|
||||
{
|
||||
if (wSegm == "fort" || wSegm == "curves")
|
||||
continue;
|
||||
|
||||
let wPart = this.getEntity(parsed.wallSet.templates[wSegm], civCode);
|
||||
parsed.wallset[wSegm] = wPart;
|
||||
|
||||
for (let research of wPart.production.techs)
|
||||
parsed.production.techs.push(research);
|
||||
|
||||
if (wPart.upgrades)
|
||||
Array.prototype.push.apply(parsed.upgrades, wPart.upgrades);
|
||||
|
||||
if (["gate", "tower"].indexOf(wSegm) != -1)
|
||||
continue;
|
||||
|
||||
if (!health)
|
||||
{
|
||||
health = { "min": wPart.health, "max": wPart.health };
|
||||
continue;
|
||||
}
|
||||
|
||||
health.min = Math.min(health.min, wPart.health);
|
||||
health.max = Math.max(health.max, wPart.health);
|
||||
}
|
||||
|
||||
if (parsed.wallSet.templates.curves)
|
||||
for (let curve of parsed.wallSet.templates.curves)
|
||||
{
|
||||
let wPart = this.getEntity(curve, civCode);
|
||||
health.min = Math.min(health.min, wPart.health);
|
||||
health.max = Math.max(health.max, wPart.health);
|
||||
}
|
||||
|
||||
if (health.min == health.max)
|
||||
parsed.health = health.min;
|
||||
else
|
||||
parsed.health = sprintf(translate("%(health_min)s to %(health_max)s"), {
|
||||
"health_min": health.min,
|
||||
"health_max": health.max
|
||||
});
|
||||
}
|
||||
|
||||
this.entities[templateName] = parsed;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse technology from json template.
|
||||
*
|
||||
* @param {string} technologyName
|
||||
* @param {string} civCode
|
||||
* @return {object} Sanitized data about the requested technology.
|
||||
*/
|
||||
getTechnology(technologyName, civCode)
|
||||
{
|
||||
if (!TechnologyTemplateExists(technologyName))
|
||||
return null;
|
||||
|
||||
if (this.TemplateLoader.isPhaseTech(technologyName) && technologyName in this.phases)
|
||||
return this.phases[technologyName];
|
||||
|
||||
if (!(civCode in this.techs))
|
||||
this.techs[civCode] = {};
|
||||
else if (technologyName in this.techs[civCode])
|
||||
return this.techs[civCode][technologyName];
|
||||
|
||||
let template = this.TemplateLoader.loadTechnologyTemplate(technologyName);
|
||||
let tech = GetTechnologyDataHelper(template, civCode, g_ResourceData);
|
||||
tech.name.internal = technologyName;
|
||||
|
||||
if (template.pair !== undefined)
|
||||
{
|
||||
tech.pair = template.pair;
|
||||
tech.reqs = this.mergeRequirements(tech.reqs, this.TemplateLoader.loadTechnologyPairTemplate(template.pair).reqs);
|
||||
}
|
||||
|
||||
if (this.TemplateLoader.isPhaseTech(technologyName))
|
||||
{
|
||||
tech.actualPhase = technologyName;
|
||||
if (tech.replaces !== undefined)
|
||||
tech.actualPhase = tech.replaces[0];
|
||||
this.phases[technologyName] = tech;
|
||||
}
|
||||
else
|
||||
this.techs[civCode][technologyName] = tech;
|
||||
return tech;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} phaseCode
|
||||
* @param {string} civCode
|
||||
* @return {object} Sanitized object containing phase data
|
||||
*/
|
||||
getPhase(phaseCode, civCode)
|
||||
{
|
||||
return this.getTechnology(phaseCode, civCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided with an array containing basic information about possible
|
||||
* upgrades, such as that generated by globalscript's GetTemplateDataHelper,
|
||||
* this function loads the actual template data of the upgrades, overwrites
|
||||
* certain values within, then passes an array containing the template data
|
||||
* back to caller.
|
||||
*/
|
||||
getActualUpgradeData(upgradesInfo, civCode)
|
||||
{
|
||||
let newUpgrades = [];
|
||||
for (let upgrade of upgradesInfo)
|
||||
{
|
||||
upgrade.entity = upgrade.entity.replace(/\{(civ|native)\}/g, civCode);
|
||||
|
||||
let data = GetTemplateDataHelper(this.TemplateLoader.loadEntityTemplate(upgrade.entity, civCode), null, this.TemplateLoader.auraData);
|
||||
data.name.internal = upgrade.entity;
|
||||
data.cost = upgrade.cost;
|
||||
data.icon = upgrade.icon || data.icon;
|
||||
data.tooltip = upgrade.tooltip || data.tooltip;
|
||||
data.requiredTechnology = upgrade.requiredTechnology || data.requiredTechnology;
|
||||
|
||||
if (!data.requiredTechnology)
|
||||
data.phase = this.phaseList[0];
|
||||
else if (this.TemplateLoader.isPhaseTech(data.requiredTechnology))
|
||||
data.phase = this.getActualPhase(data.requiredTechnology);
|
||||
else
|
||||
data.phase = this.getPhaseOfTechnology(data.requiredTechnology, civCode);
|
||||
|
||||
newUpgrades.push(data);
|
||||
}
|
||||
return newUpgrades;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines and returns the phase in which a given technology can be
|
||||
* first researched. Works recursively through the given tech's
|
||||
* pre-requisite and superseded techs if necessary.
|
||||
*
|
||||
* @param {string} techName - The Technology's name
|
||||
* @param {string} civCode
|
||||
* @return The name of the phase the technology belongs to, or false if
|
||||
* the current civ can't research this tech
|
||||
*/
|
||||
getPhaseOfTechnology(techName, civCode)
|
||||
{
|
||||
let phaseIdx = -1;
|
||||
|
||||
if (basename(techName).startsWith("phase"))
|
||||
{
|
||||
if (!this.phases[techName].reqs)
|
||||
return false;
|
||||
|
||||
phaseIdx = this.phaseList.indexOf(this.getActualPhase(techName));
|
||||
if (phaseIdx > 0)
|
||||
return this.phaseList[phaseIdx - 1];
|
||||
}
|
||||
|
||||
let techReqs = this.getTechnology(techName, civCode).reqs;
|
||||
if (!techReqs)
|
||||
return false;
|
||||
|
||||
for (let option of techReqs)
|
||||
if (option.techs)
|
||||
for (let tech of option.techs)
|
||||
{
|
||||
if (basename(tech).startsWith("phase"))
|
||||
return tech;
|
||||
if (basename(tech).startsWith("pair"))
|
||||
continue;
|
||||
phaseIdx = Math.max(phaseIdx, this.phaseList.indexOf(this.getPhaseOfTechnology(tech, civCode)));
|
||||
}
|
||||
return this.phaseList[phaseIdx] || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual phase a certain phase tech represents or stands in for.
|
||||
*
|
||||
* For example, passing `phase_city_athen` would result in `phase_city`.
|
||||
*
|
||||
* @param {string} phaseName
|
||||
* @return {string}
|
||||
*/
|
||||
getActualPhase(phaseName)
|
||||
{
|
||||
if (this.phases[phaseName])
|
||||
return this.phases[phaseName].actualPhase;
|
||||
|
||||
warn("Unrecognized phase (" + phaseName + ")");
|
||||
return this.phaseList[0];
|
||||
}
|
||||
|
||||
getModifiers(civCode)
|
||||
{
|
||||
return this.modifiers[civCode];
|
||||
}
|
||||
|
||||
deriveModifications(civCode)
|
||||
{
|
||||
this.modifiers[civCode] = this.TemplateLoader.deriveModifications(civCode);
|
||||
}
|
||||
|
||||
derivePhaseList(technologyList, civCode)
|
||||
{
|
||||
// Load all of a civ's specific phase technologies
|
||||
for (let techcode of technologyList)
|
||||
if (this.TemplateLoader.isPhaseTech(techcode))
|
||||
this.getTechnology(techcode, civCode);
|
||||
|
||||
this.phaseList = UnravelPhases(this.phases);
|
||||
|
||||
// Make sure all required generic phases are loaded and parsed
|
||||
for (let phasecode of this.phaseList)
|
||||
this.getTechnology(phasecode, civCode);
|
||||
}
|
||||
|
||||
mergeRequirements(reqsA, reqsB)
|
||||
{
|
||||
if (!reqsA || !reqsB)
|
||||
return false;
|
||||
|
||||
let finalReqs = clone(reqsA);
|
||||
|
||||
for (let option of reqsB)
|
||||
for (let type in option)
|
||||
for (let opt in finalReqs)
|
||||
{
|
||||
if (!finalReqs[opt][type])
|
||||
finalReqs[opt][type] = [];
|
||||
Array.prototype.push.apply(finalReqs[opt][type], option[type]);
|
||||
}
|
||||
|
||||
return finalReqs;
|
||||
}
|
||||
}
|
28
binaries/data/mods/public/gui/reference/common/common.js
Normal file
28
binaries/data/mods/public/gui/reference/common/common.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This needs to stay in the global scope, as it is used by various functions
|
||||
* within gui/common/tooltip.js
|
||||
*/
|
||||
var g_ResourceData = new Resources();
|
||||
|
||||
var g_Page;
|
||||
|
||||
/**
|
||||
* This is needed because getEntityCostTooltip in tooltip.js needs to get
|
||||
* the template data of the different wallSet pieces. In the session this
|
||||
* function does some caching, but here we do that in the TemplateLoader
|
||||
* class already.
|
||||
*/
|
||||
function GetTemplateData(templateName)
|
||||
{
|
||||
let template = g_Page.TemplateLoader.loadEntityTemplate(templateName, g_Page.activeCiv);
|
||||
return GetTemplateDataHelper(template, null, g_Page.TemplateLoader.auraData, g_Page.TemplateParser.getModifiers(g_Page.activeCiv));
|
||||
}
|
||||
|
||||
/**
|
||||
* This would ideally be an Engine method.
|
||||
* Or part of globalscripts. Either would be better than here.
|
||||
*/
|
||||
function TechnologyTemplateExists(templateName)
|
||||
{
|
||||
return Engine.FileExists(g_Page.TemplateLoader.TechnologyPath + templateName + ".json");
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
var g_SelectedCiv = "gaia";
|
||||
|
||||
/**
|
||||
* Compile lists of templates buildable/trainable/researchable of a given civ.
|
||||
*
|
||||
* @param {string} civCode - Code of the civ to get template list for. Optional,
|
||||
* defaults to g_SelectedCiv.
|
||||
* @return {object} containing lists of template names, grouped by type.
|
||||
*/
|
||||
function compileTemplateLists(civCode)
|
||||
{
|
||||
if (!civCode || civCode == "gaia")
|
||||
return {};
|
||||
|
||||
let templatesToParse = [];
|
||||
for (let entity of g_CivData[civCode].StartEntities)
|
||||
templatesToParse.push(entity.Template);
|
||||
|
||||
let templateLists = {
|
||||
"units": new Map(),
|
||||
"structures": new Map(),
|
||||
"techs": new Map(),
|
||||
"wallsetPieces": new Map()
|
||||
};
|
||||
|
||||
do {
|
||||
const templatesThisIteration = templatesToParse;
|
||||
templatesToParse = [];
|
||||
|
||||
for (let templateBeingParsed of templatesThisIteration)
|
||||
{
|
||||
let list = getTemplateListsFromTemplate(templateBeingParsed);
|
||||
for (let type in list)
|
||||
for (let templateName of list[type])
|
||||
if (!templateLists[type].has(templateName))
|
||||
{
|
||||
templateLists[type].set(templateName, [templateBeingParsed]);
|
||||
if (type != "techs")
|
||||
templatesToParse.push(templateName);
|
||||
}
|
||||
else if (templateLists[type].get(templateName).indexOf(templateBeingParsed) == -1)
|
||||
templateLists[type].get(templateName).push(templateBeingParsed);
|
||||
}
|
||||
} while (templatesToParse.length);
|
||||
|
||||
// Expand/filter tech pairs
|
||||
for (let [techCode, researcherList] of templateLists.techs)
|
||||
{
|
||||
if (!isPairTech(techCode))
|
||||
continue;
|
||||
|
||||
for (let subTech of loadTechnologyPair(techCode).techs)
|
||||
if (!templateLists.techs.has(subTech))
|
||||
templateLists.techs.set(subTech, researcherList);
|
||||
else
|
||||
for (let researcher of researcherList)
|
||||
if (templateLists.techs.get(subTech).indexOf(researcher) == -1)
|
||||
templateLists.techs.get(subTech).push(researcher);
|
||||
|
||||
templateLists.techs.delete(techCode);
|
||||
}
|
||||
|
||||
// Remove wallset pieces, as they've served their purpose.
|
||||
delete templateLists.wallsetPieces;
|
||||
|
||||
return templateLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles lists of buildable, trainable, or researchable entities from
|
||||
* a named template.
|
||||
*/
|
||||
function getTemplateListsFromTemplate(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);
|
||||
templateLists.structures = loadBuildQueue(template);
|
||||
|
||||
if (template.WallSet)
|
||||
{
|
||||
templateLists.wallsetPieces = [];
|
||||
for (let segment in template.WallSet.Templates)
|
||||
{
|
||||
segment = template.WallSet.Templates[segment].replace(/\{(civ|native)\}/g, g_SelectedCiv);
|
||||
if (Engine.TemplateExists(segment))
|
||||
templateLists.wallsetPieces.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return templateLists;
|
||||
}
|
||||
|
||||
function loadProductionQueue(template)
|
||||
{
|
||||
let production = {
|
||||
"techs": [],
|
||||
"units": []
|
||||
};
|
||||
|
||||
if (!template.ProductionQueue)
|
||||
return production;
|
||||
|
||||
if (template.ProductionQueue.Entities && template.ProductionQueue.Entities._string)
|
||||
for (let templateName of template.ProductionQueue.Entities._string.split(" "))
|
||||
{
|
||||
templateName = templateName.replace(/\{(civ|native)\}/g, g_SelectedCiv);
|
||||
if (Engine.TemplateExists(templateName))
|
||||
production.units.push(getBaseTemplateName(templateName));
|
||||
}
|
||||
|
||||
if (template.ProductionQueue.Technologies && template.ProductionQueue.Technologies._string)
|
||||
for (let technologyName of template.ProductionQueue.Technologies._string.split(" "))
|
||||
{
|
||||
if (technologyName.indexOf("{civ}") != -1)
|
||||
{
|
||||
let civTechName = technologyName.replace("{civ}", g_SelectedCiv);
|
||||
technologyName = techDataExists(civTechName) ? civTechName : technologyName.replace("{civ}", "generic");
|
||||
}
|
||||
|
||||
if (isPairTech(technologyName))
|
||||
for (let pairTechnologyName of loadTechnologyPair(technologyName).techs)
|
||||
production.techs.push(pairTechnologyName);
|
||||
else
|
||||
production.techs.push(technologyName);
|
||||
}
|
||||
|
||||
return production;
|
||||
}
|
||||
|
||||
function loadBuildQueue(template)
|
||||
{
|
||||
let buildQueue = [];
|
||||
|
||||
if (!template.Builder || !template.Builder.Entities._string)
|
||||
return buildQueue;
|
||||
|
||||
for (let build of template.Builder.Entities._string.split(" "))
|
||||
{
|
||||
build = build.replace(/\{(civ|native)\}/g, g_SelectedCiv);
|
||||
if (Engine.TemplateExists(build))
|
||||
buildQueue.push(build);
|
||||
}
|
||||
|
||||
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(templateName) || 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;
|
||||
|
||||
if (parentTemplate.Upgrade)
|
||||
for (let upgrade in parentTemplate.Upgrade)
|
||||
if (parentTemplate.Upgrade[upgrade].Entity)
|
||||
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;
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/**
|
||||
* GUI limits. Populated if needed by a predraw() function.
|
||||
*/
|
||||
var g_DrawLimits = {};
|
||||
|
||||
/**
|
||||
* 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_StatsFunctions = [
|
||||
getHealthTooltip,
|
||||
getHealerTooltip,
|
||||
getAttackTooltip,
|
||||
getSplashDamageTooltip,
|
||||
getArmorTooltip,
|
||||
getGarrisonTooltip,
|
||||
getProjectilesTooltip,
|
||||
getSpeedTooltip,
|
||||
getGatherTooltip,
|
||||
getResourceSupplyTooltip,
|
||||
getPopulationBonusTooltip,
|
||||
getResourceTrickleTooltip,
|
||||
getLootTooltip
|
||||
];
|
||||
|
||||
/**
|
||||
* Concatanates the return values of the array of passed functions.
|
||||
*
|
||||
* @param {object} template
|
||||
* @param {array} textFunctions
|
||||
* @param {string} joiner
|
||||
* @return {string} The built text.
|
||||
*/
|
||||
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("∞")
|
||||
});
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
var g_CurrentModifiers = {};
|
||||
|
||||
function deriveModifications(techList)
|
||||
{
|
||||
let techData = [];
|
||||
for (let techName of techList)
|
||||
techData.push(GetTechnologyBasicDataHelper(loadTechData(techName), g_SelectedCiv));
|
||||
|
||||
return DeriveModificationsFromTechnologies(techData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided with an array containing basic information about possible
|
||||
* upgrades, such as that generated by globalscript's GetTemplateDataHelper,
|
||||
* this function loads the actual template data of the upgrades, overwrites
|
||||
* certain values within, then passes an array containing the template data
|
||||
* back to caller.
|
||||
*/
|
||||
function getActualUpgradeData(upgradesInfo)
|
||||
{
|
||||
let newUpgrades = [];
|
||||
for (let upgrade of upgradesInfo)
|
||||
{
|
||||
upgrade.entity = upgrade.entity.replace(/\{(civ|native)\}/g, g_SelectedCiv);
|
||||
|
||||
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData);
|
||||
data.name.internal = upgrade.entity;
|
||||
data.cost = upgrade.cost;
|
||||
data.icon = upgrade.icon || data.icon;
|
||||
data.tooltip = upgrade.tooltip || data.tooltip;
|
||||
data.requiredTechnology = upgrade.requiredTechnology || data.requiredTechnology;
|
||||
|
||||
newUpgrades.push(data);
|
||||
}
|
||||
return newUpgrades;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines and returns the phase in which a given technology can be
|
||||
* first researched. Works recursively through the given tech's
|
||||
* pre-requisite and superseded techs if necessary.
|
||||
*
|
||||
* @param {string} techName - The Technology's name
|
||||
* @return The name of the phase the technology belongs to, or false if
|
||||
* the current civ can't research this tech
|
||||
*/
|
||||
function getPhaseOfTechnology(techName)
|
||||
{
|
||||
let phaseIdx = -1;
|
||||
|
||||
if (basename(techName).startsWith("phase"))
|
||||
{
|
||||
if (!g_ParsedData.phases[techName].reqs)
|
||||
return false;
|
||||
|
||||
phaseIdx = g_ParsedData.phaseList.indexOf(getActualPhase(techName));
|
||||
if (phaseIdx > 0)
|
||||
return g_ParsedData.phaseList[phaseIdx - 1];
|
||||
}
|
||||
|
||||
if (!g_ParsedData.techs[g_SelectedCiv][techName])
|
||||
{
|
||||
let techData = loadTechnology(techName);
|
||||
g_ParsedData.techs[g_SelectedCiv][techName] = techData;
|
||||
warn("The \"" + techName + "\" technology is not researchable in any structure buildable by the " +
|
||||
g_SelectedCiv + " civilisation, but is required by something that this civ can research, train or build!");
|
||||
}
|
||||
|
||||
let techReqs = g_ParsedData.techs[g_SelectedCiv][techName].reqs;
|
||||
if (!techReqs)
|
||||
return false;
|
||||
|
||||
for (let option of techReqs)
|
||||
if (option.techs)
|
||||
for (let tech of option.techs)
|
||||
{
|
||||
if (basename(tech).startsWith("phase"))
|
||||
return tech;
|
||||
if (basename(tech).startsWith("pair"))
|
||||
continue;
|
||||
phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(getPhaseOfTechnology(tech)));
|
||||
}
|
||||
return g_ParsedData.phaseList[phaseIdx] || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual phase a certain phase tech represents or stands in for.
|
||||
*
|
||||
* For example, passing `phase_city_athen` would result in `phase_city`.
|
||||
*
|
||||
* @param {string} phaseName
|
||||
* @return {string}
|
||||
*/
|
||||
function getActualPhase(phaseName)
|
||||
{
|
||||
if (g_ParsedData.phases[phaseName])
|
||||
return g_ParsedData.phases[phaseName].actualPhase;
|
||||
|
||||
warn("Unrecognised phase (" + phaseName + ")");
|
||||
return g_ParsedData.phaseList[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required phase of a given unit or structure.
|
||||
*
|
||||
* @param {object} template
|
||||
* @return {string}
|
||||
*/
|
||||
function getPhaseOfTemplate(template)
|
||||
{
|
||||
if (!template.requiredTechnology)
|
||||
return g_ParsedData.phaseList[0];
|
||||
|
||||
if (basename(template.requiredTechnology).startsWith("phase"))
|
||||
return getActualPhase(template.requiredTechnology);
|
||||
|
||||
return getPhaseOfTechnology(template.requiredTechnology);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is needed because getEntityCostTooltip in tooltip.js needs to get
|
||||
* the template data of the different wallSet pieces. In the session this
|
||||
* function does some caching, but here we do that in loadTemplate already.
|
||||
*/
|
||||
function GetTemplateData(templateName)
|
||||
{
|
||||
let template = loadTemplate(templateName);
|
||||
return GetTemplateDataHelper(template, null, g_AuraData, g_CurrentModifiers);
|
||||
}
|
||||
|
||||
function isPairTech(technologyCode)
|
||||
{
|
||||
return !!loadTechData(technologyCode).top;
|
||||
}
|
||||
|
||||
function mergeRequirements(reqsA, reqsB)
|
||||
{
|
||||
if (reqsA === false || reqsB === false)
|
||||
return false;
|
||||
|
||||
let finalReqs = clone(reqsA);
|
||||
|
||||
for (let option of reqsB)
|
||||
for (let type in option)
|
||||
for (let opt in finalReqs)
|
||||
{
|
||||
if (!finalReqs[opt][type])
|
||||
finalReqs[opt][type] = [];
|
||||
finalReqs[opt][type] = finalReqs[opt][type].concat(option[type]);
|
||||
}
|
||||
|
||||
return finalReqs;
|
||||
}
|
@ -1,283 +0,0 @@
|
||||
/**
|
||||
* Paths to certain files.
|
||||
*/
|
||||
const g_TechnologyPath = "simulation/data/technologies/";
|
||||
const g_AuraPath = "simulation/data/auras/";
|
||||
|
||||
/**
|
||||
* Raw Data Caches.
|
||||
*/
|
||||
var g_AuraData = {};
|
||||
var g_TemplateData = {};
|
||||
var g_TechnologyData = {};
|
||||
var g_CivData = loadCivData(true, false);
|
||||
|
||||
/**
|
||||
* Parsed Data Stores.
|
||||
*/
|
||||
var g_ParsedData = {};
|
||||
var g_ResourceData = new Resources();
|
||||
|
||||
// This must be defined after the g_TechnologyData cache object is declared.
|
||||
var g_AutoResearchTechList = findAllAutoResearchedTechs();
|
||||
|
||||
/**
|
||||
* Loads raw entity template.
|
||||
*
|
||||
* Loads from local cache if data present, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
function loadTemplate(templateName)
|
||||
{
|
||||
if (!(templateName in g_TemplateData))
|
||||
{
|
||||
// We need to clone the template because we want to perform some translations.
|
||||
let data = clone(Engine.GetTemplate(templateName));
|
||||
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;
|
||||
}
|
||||
|
||||
return g_TemplateData[templateName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads raw technology template.
|
||||
*
|
||||
* Loads from local cache if available, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
function loadTechData(templateName)
|
||||
{
|
||||
if (!(templateName in g_TechnologyData))
|
||||
{
|
||||
let data = Engine.ReadJSONFile(g_TechnologyPath + templateName + ".json");
|
||||
translateObjectKeys(data, ["genericName", "tooltip", "description"]);
|
||||
|
||||
g_TechnologyData[templateName] = data;
|
||||
}
|
||||
|
||||
return g_TechnologyData[templateName];
|
||||
}
|
||||
|
||||
function techDataExists(templateName)
|
||||
{
|
||||
return Engine.FileExists("simulation/data/technologies/" + templateName + ".json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads raw aura template.
|
||||
*
|
||||
* Loads from local cache if available, else from file system.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Object containing raw template data.
|
||||
*/
|
||||
function loadAuraData(templateName)
|
||||
{
|
||||
if (!(templateName in g_AuraData))
|
||||
{
|
||||
let data = Engine.ReadJSONFile(g_AuraPath + templateName + ".json");
|
||||
translateObjectKeys(data, ["auraName", "auraDescription"]);
|
||||
|
||||
g_AuraData[templateName] = data;
|
||||
}
|
||||
|
||||
return g_AuraData[templateName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse a structure, unit, resource, etc from its entity template file.
|
||||
*
|
||||
* @return {(object|null)} Sanitized object about the requested template or null if entity template doesn't exist.
|
||||
*/
|
||||
function loadEntityTemplate(templateName)
|
||||
{
|
||||
if (!Engine.TemplateExists(templateName))
|
||||
return null;
|
||||
|
||||
let template = loadTemplate(templateName);
|
||||
let parsed = GetTemplateDataHelper(template, null, g_AuraData, 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("."),
|
||||
"amount": template.ResourceSupply.Amount,
|
||||
};
|
||||
|
||||
if (parsed.upgrades)
|
||||
parsed.upgrades = getActualUpgradeData(parsed.upgrades);
|
||||
|
||||
if (parsed.wallSet)
|
||||
{
|
||||
parsed.wallset = {};
|
||||
|
||||
if (!parsed.upgrades)
|
||||
parsed.upgrades = [];
|
||||
|
||||
// Note: An assumption is made here that wall segments all have the same armor and auras
|
||||
let struct = loadEntityTemplate(parsed.wallSet.templates.long);
|
||||
parsed.armour = struct.armour;
|
||||
parsed.auras = struct.auras;
|
||||
|
||||
// For technology cost multiplier, we need to use the tower
|
||||
struct = loadEntityTemplate(parsed.wallSet.templates.tower);
|
||||
parsed.techCostMultiplier = struct.techCostMultiplier;
|
||||
|
||||
let health;
|
||||
|
||||
for (let wSegm in parsed.wallSet.templates)
|
||||
{
|
||||
if (wSegm == "fort" || wSegm == "curves")
|
||||
continue;
|
||||
|
||||
let wPart = loadEntityTemplate(parsed.wallSet.templates[wSegm]);
|
||||
parsed.wallset[wSegm] = wPart;
|
||||
|
||||
for (let research of wPart.production.techs)
|
||||
parsed.production.techs.push(research);
|
||||
|
||||
if (wPart.upgrades)
|
||||
parsed.upgrades = parsed.upgrades.concat(wPart.upgrades);
|
||||
|
||||
if (["gate", "tower"].indexOf(wSegm) != -1)
|
||||
continue;
|
||||
|
||||
if (!health)
|
||||
{
|
||||
health = { "min": wPart.health, "max": wPart.health };
|
||||
continue;
|
||||
}
|
||||
|
||||
health.min = Math.min(health.min, wPart.health);
|
||||
health.max = Math.max(health.max, wPart.health);
|
||||
}
|
||||
|
||||
if (parsed.wallSet.templates.curves)
|
||||
for (let curve of parsed.wallSet.templates.curves)
|
||||
{
|
||||
let wPart = loadEntityTemplate(curve);
|
||||
health.min = Math.min(health.min, wPart.health);
|
||||
health.max = Math.max(health.max, wPart.health);
|
||||
}
|
||||
|
||||
if (health.min == health.max)
|
||||
parsed.health = health.min;
|
||||
else
|
||||
parsed.health = sprintf(translate("%(health_min)s to %(health_max)s"), {
|
||||
"health_min": health.min,
|
||||
"health_max": health.max
|
||||
});
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse technology from json template.
|
||||
*
|
||||
* @param {string} templateName
|
||||
* @return {object} Sanitized data about the requested technology.
|
||||
*/
|
||||
function loadTechnology(techName)
|
||||
{
|
||||
let template = loadTechData(techName);
|
||||
let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
|
||||
tech.name.internal = techName;
|
||||
|
||||
if (template.pair !== undefined)
|
||||
{
|
||||
tech.pair = template.pair;
|
||||
tech.reqs = mergeRequirements(tech.reqs, loadTechnologyPair(template.pair).reqs);
|
||||
}
|
||||
|
||||
return tech;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crudely iterates through every tech JSON file and identifies those
|
||||
* that are auto-researched.
|
||||
*
|
||||
* @return {array} List of techs that are researched automatically
|
||||
*/
|
||||
function findAllAutoResearchedTechs()
|
||||
{
|
||||
let techList = [];
|
||||
|
||||
for (let filename of Engine.ListDirectoryFiles(g_TechnologyPath, "*.json", true))
|
||||
{
|
||||
// -5 to strip off the file extension
|
||||
let templateName = filename.slice(g_TechnologyPath.length, -5);
|
||||
let data = loadTechData(templateName);
|
||||
|
||||
if (data && data.autoResearch)
|
||||
techList.push(templateName);
|
||||
}
|
||||
|
||||
return techList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} phaseCode
|
||||
* @return {object} Sanitized object containing phase data
|
||||
*/
|
||||
function loadPhase(phaseCode)
|
||||
{
|
||||
let phase = loadTechnology(phaseCode);
|
||||
|
||||
phase.actualPhase = phaseCode;
|
||||
if (phase.replaces !== undefined)
|
||||
phase.actualPhase = phase.replaces[0];
|
||||
|
||||
return phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} pairCode
|
||||
* @return {object} Contains a list and the requirements of the techs in the pair
|
||||
*/
|
||||
function loadTechnologyPair(pairCode)
|
||||
{
|
||||
var pairInfo = loadTechData(pairCode);
|
||||
|
||||
return {
|
||||
"techs": [ pairInfo.top, pairInfo.bottom ],
|
||||
"reqs": DeriveTechnologyRequirements(pairInfo, g_SelectedCiv)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} modCode
|
||||
* @return {object} Sanitized object containing modifier tech data
|
||||
*/
|
||||
function loadModifierTech(modCode)
|
||||
{
|
||||
if (!Engine.FileExists("simulation/data/technologies/"+modCode+".json"))
|
||||
return {};
|
||||
return loadTechData(modCode);
|
||||
}
|
76
binaries/data/mods/public/gui/reference/common/tooltips.js
Normal file
76
binaries/data/mods/public/gui/reference/common/tooltips.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Creates text in the following format:
|
||||
* Header: value1, value2, ..., valueN
|
||||
*
|
||||
* This function is only used below, nowhere else.
|
||||
*/
|
||||
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(", ")))
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The following functions in this file work on the same basis as those in gui/common/tooltips.js
|
||||
*
|
||||
* Note: Due to quirks in loading order, this file might not be loaded before ReferencePage.js.
|
||||
* Do not put anything in here that you wish to access static'ly there.
|
||||
*/
|
||||
|
||||
function getBuiltByText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of units that build the structure selected.
|
||||
return template.builtByListOfNames ? buildListText(translate("Built by:"), template.builtByListOfNames) : "";
|
||||
}
|
||||
|
||||
function getTrainedByText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of structures or units that train the unit selected.
|
||||
return template.trainedByListOfNames ? buildListText(translate("Trained by:"), template.trainedByListOfNames) : "";
|
||||
}
|
||||
|
||||
function getResearchedByText(template)
|
||||
{
|
||||
// Translation: Label before a list of names of structures or units that research the technology selected.
|
||||
return template.researchedByListOfNames ? buildListText(translate("Researched at:"), template.researchedByListOfNames) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} List of the names of the buildings the selected unit can build.
|
||||
*/
|
||||
function getBuildText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of structures the selected unit can construct or build.
|
||||
return template.buildListOfNames ? buildListText(translate("Builds:"), template.buildListOfNames) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} List of the names of the technologies the selected structure/unit can research.
|
||||
*/
|
||||
function getResearchText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of technologies the selected unit or structure can research.
|
||||
return template.researchListOfNames ? buildListText(translate("Researches:"), template.researchListOfNames) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} List of the names of the units the selected unit can train.
|
||||
*/
|
||||
function getTrainText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of units the selected unit or structure can train.
|
||||
return template.trainListOfNames ? buildListText(translate("Trains:"), template.trainListOfNames) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} List of the names of the buildings/units the selected structure/unit can upgrade to.
|
||||
*/
|
||||
function getUpgradeText(template)
|
||||
{
|
||||
// Translation: Label before a list of the names of units or structures the selected unit or structure can be upgradable to.
|
||||
return template.upgradeListOfNames ? buildListText(translate("Upgradable to:"), template.upgradeListOfNames) : "";
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Class inherited by StructureBox and TrainerBox classes.
|
||||
*/
|
||||
class EntityBox
|
||||
{
|
||||
constructor(page)
|
||||
{
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
static setViewerOnPress(guiObject, templateName, civCode)
|
||||
{
|
||||
let viewerFunc = () => {
|
||||
Engine.PushGuiPage("page_viewer.xml", {
|
||||
"templateName": templateName,
|
||||
"civ": civCode
|
||||
});
|
||||
};
|
||||
guiObject.onPress = viewerFunc;
|
||||
guiObject.onPressRight = viewerFunc;
|
||||
}
|
||||
|
||||
draw(templateName, civCode)
|
||||
{
|
||||
this.template = this.page.TemplateParser.getEntity(templateName, civCode);
|
||||
this.gui.hidden = false;
|
||||
|
||||
let caption = this.gui.children[0];
|
||||
caption.caption = translate(this.template.name.specific);
|
||||
|
||||
let icon = this.gui.children[1];
|
||||
icon.sprite = "stretched:" + this.page.IconPath + this.template.icon;
|
||||
icon.tooltip = this.constructor.compileTooltip(this.template);
|
||||
this.constructor.setViewerOnPress(icon, this.template.name.internal, civCode);
|
||||
}
|
||||
|
||||
captionWidth()
|
||||
{
|
||||
// We make the assumption that the caption's padding is equal on both sides
|
||||
let caption = this.gui.children[0];
|
||||
return Engine.GetTextWidth(caption.font, caption.caption) + (caption.size.left + caption.buffer_zone) * 2;
|
||||
}
|
||||
|
||||
static compileTooltip(template)
|
||||
{
|
||||
return ReferencePage.buildText(template, this.prototype.TooltipFunctions) + "\n" + showTemplateViewerOnClickTooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height between the top of the EntityBox, and the top of the production rows.
|
||||
*
|
||||
* Used within the TreeSection class to position the production rows,
|
||||
* and used with the PhaseIdent class to position grey bars under them.
|
||||
*/
|
||||
static IconAndCaptionHeight()
|
||||
{
|
||||
let height = Engine.GetGUIObjectByName("structure[0]_icon").size.bottom + this.prototype.IconPadding;
|
||||
|
||||
// Replace function so the above is only run once.
|
||||
this.IconAndCaptionHeight = () => height;
|
||||
return height;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum width of the boxes, the margins between them (Horizontally and Vertically),
|
||||
* and the padding between the main icon and the production row(s) beneath it.
|
||||
*/
|
||||
EntityBox.prototype.MinWidth = 96;
|
||||
EntityBox.prototype.HMargin = 8;
|
||||
EntityBox.prototype.VMargin = 12;
|
||||
EntityBox.prototype.IconPadding = 8;
|
||||
|
||||
/**
|
||||
* Functions used to collate the contents of a tooltip.
|
||||
*/
|
||||
EntityBox.prototype.TooltipFunctions = [
|
||||
getEntityNamesFormatted,
|
||||
getEntityCostTooltip,
|
||||
getEntityTooltip,
|
||||
getAurasTooltip
|
||||
].concat(ReferencePage.prototype.StatsFunctions);
|
@ -0,0 +1,52 @@
|
||||
class ProductionIcon
|
||||
{
|
||||
constructor(page, guiObject)
|
||||
{
|
||||
this.page = page;
|
||||
this.productionIcon = guiObject;
|
||||
}
|
||||
|
||||
/* Returns the dimensions of a single icon, including some "helper" attributes.
|
||||
*
|
||||
* Two assumptions are made: (1) that all production icons are the same size,
|
||||
* and (2) that the size will never change for the duration of the life of the
|
||||
* containing page.
|
||||
*
|
||||
* As such, the method replaces itself after being run once, so the calculations
|
||||
* within are only performed once.
|
||||
*/
|
||||
static Size()
|
||||
{
|
||||
let baseObject = Engine.GetGUIObjectByName("phase[0]_bar[0]_icon").size;
|
||||
let size = {};
|
||||
|
||||
// Icon dimensions
|
||||
size.width = baseObject.right - baseObject.left;
|
||||
size.height = baseObject.bottom - baseObject.top;
|
||||
|
||||
// Horizontal and Vertical Margins.
|
||||
size.hMargin = baseObject.left;
|
||||
size.vMargin = baseObject.top;
|
||||
|
||||
// Width and Height padded with margins on all sides.
|
||||
size.paddedWidth = size.width + size.hMargin * 2;
|
||||
size.paddedHeight = size.height + size.vMargin * 2;
|
||||
|
||||
// Padded dimensions to use when in production rows.
|
||||
size.rowWidth = size.width + size.hMargin;
|
||||
size.rowHeight = size.paddedHeight + size.vMargin * 2;
|
||||
size.rowGap = size.rowHeight - size.paddedHeight;
|
||||
|
||||
// Replace static method and return
|
||||
this.Size = () => size;
|
||||
return size;
|
||||
}
|
||||
|
||||
draw(template, civCode)
|
||||
{
|
||||
this.productionIcon.sprite = "stretched:" + this.page.IconPath + template.icon;
|
||||
this.productionIcon.tooltip = EntityBox.compileTooltip(template);
|
||||
this.productionIcon.hidden = false;
|
||||
EntityBox.setViewerOnPress(this.productionIcon, template.name.internal, civCode);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
class ProductionRow
|
||||
{
|
||||
constructor(page, guiObject, rowIndex)
|
||||
{
|
||||
this.page = page;
|
||||
this.productionRow = guiObject;
|
||||
this.productionIconsDrawn = 0;
|
||||
this.rowIndex = rowIndex;
|
||||
this.phaseOffset = 0;
|
||||
|
||||
horizontallySpaceObjects(this.productionRow.name, ProductionIcon.Size().hMargin);
|
||||
|
||||
this.productionIcons = [];
|
||||
for (let icon of guiObject.children)
|
||||
this.productionIcons.push(new ProductionIcon(this.page, icon));
|
||||
}
|
||||
|
||||
startDraw(phaseOffset)
|
||||
{
|
||||
this.productionIconsDrawn = 0;
|
||||
this.phaseOffset = phaseOffset;
|
||||
}
|
||||
|
||||
drawIcon(template, civCode)
|
||||
{
|
||||
if (this.productionIconsDrawn == this.productionIcons.length)
|
||||
{
|
||||
error("The currently displayed civ has more production options " +
|
||||
"than can be supported by the current GUI layout");
|
||||
return;
|
||||
}
|
||||
|
||||
this.productionIcons[this.productionIconsDrawn].draw(template, civCode);
|
||||
++this.productionIconsDrawn;
|
||||
}
|
||||
|
||||
finishDraw()
|
||||
{
|
||||
hideRemaining(this.productionRow.name, this.productionIconsDrawn);
|
||||
|
||||
const IconSize = ProductionIcon.Size();
|
||||
let rowOffset = IconSize.rowHeight * (this.phaseOffset - this.rowIndex);
|
||||
let rowWidth = this.productionIconsDrawn * IconSize.rowWidth + IconSize.hMargin;
|
||||
|
||||
let size = this.productionRow.size;
|
||||
size.left = -rowWidth / 2;
|
||||
size.top = -rowOffset;
|
||||
this.productionRow.size = size;
|
||||
|
||||
this.productionRow.hidden = false;
|
||||
return rowWidth;
|
||||
}
|
||||
|
||||
hide()
|
||||
{
|
||||
this.productionRow.hidden = true
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
class ProductionRowManager
|
||||
{
|
||||
constructor(page, guiName, sortByPhase)
|
||||
{
|
||||
this.page = page;
|
||||
this.width = 0;
|
||||
this.sortProductionsByPhase = sortByPhase;
|
||||
|
||||
this.productionRows = [];
|
||||
for (let row of Engine.GetGUIObjectByName(guiName).children)
|
||||
this.productionRows.push(new ProductionRow(this.page, row, this.productionRows.length));
|
||||
}
|
||||
|
||||
draw(template, civCode, phaseIdx=0)
|
||||
{
|
||||
this.width = 0;
|
||||
|
||||
if (this.sortProductionsByPhase)
|
||||
for (let r = 0; r < this.page.TemplateParser.phaseList.length; ++r)
|
||||
this.productionRows[r].startDraw(this.page.TemplateParser.phaseList.length - phaseIdx);
|
||||
else
|
||||
this.productionRows[0].startDraw(1);
|
||||
|
||||
// (Want to draw Units before Techs)
|
||||
for (let prodType of Object.keys(template.production).reverse())
|
||||
for (let prod of template.production[prodType])
|
||||
{
|
||||
let pIdx = 0;
|
||||
switch (prodType)
|
||||
{
|
||||
|
||||
case "units":
|
||||
prod = this.page.TemplateParser.getEntity(prod, civCode);
|
||||
pIdx = this.page.TemplateParser.phaseList.indexOf(prod.phase);
|
||||
break;
|
||||
|
||||
case "techs":
|
||||
pIdx = this.page.TemplateParser.phaseList.indexOf(this.page.TemplateParser.getPhaseOfTechnology(prod, civCode));
|
||||
prod = clone(this.page.TemplateParser.getTechnology(prod, civCode));
|
||||
for (let res in template.techCostMultiplier)
|
||||
if (prod.cost[res])
|
||||
prod.cost[res] *= template.techCostMultiplier[res];
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
let rowIdx = this.sortProductionsByPhase ? Math.max(0, pIdx - phaseIdx) : 0;
|
||||
this.productionRows[rowIdx].drawIcon(prod, civCode)
|
||||
}
|
||||
|
||||
if (template.upgrades)
|
||||
for (let upgrade of template.upgrades)
|
||||
{
|
||||
let pIdx = 0;
|
||||
if (this.phaseSort)
|
||||
pIdx = this.page.TemplateParser.phaseList.indexOf(upgrade.phase);
|
||||
let rowIdx = Math.max(0, pIdx - phaseIdx);
|
||||
this.productionRows[rowIdx].drawIcon(upgrade, civCode);
|
||||
}
|
||||
|
||||
if (template.wallset)
|
||||
this.productionRows[0].drawIcon(template.wallset.tower, civCode);
|
||||
|
||||
let r = 0;
|
||||
|
||||
// Tell the production rows used we've finished
|
||||
if (this.sortProductionsByPhase)
|
||||
for (; r < this.page.TemplateParser.phaseList.length; ++r)
|
||||
this.width = Math.max(this.width, this.productionRows[r].finishDraw());
|
||||
else
|
||||
this.width = this.productionRows[r++].finishDraw();
|
||||
|
||||
// Hide any remaining phase rows
|
||||
for (; r < this.productionRows.length; ++r)
|
||||
this.productionRows[r].hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* This code wraps the gui representing buildable structures within the structree.
|
||||
*
|
||||
* An instance of this class is created for each child of the gui element named "structures".
|
||||
*/
|
||||
class StructureBox extends EntityBox
|
||||
{
|
||||
constructor(page, structureIdx)
|
||||
{
|
||||
super(page);
|
||||
this.gui = Engine.GetGUIObjectByName("structure[" + structureIdx + "]");
|
||||
this.ProductionRows = new ProductionRowManager(this.page, "structure[" + structureIdx + "]_productionRows", true);
|
||||
}
|
||||
|
||||
draw(templateName, civCode, runningWidths)
|
||||
{
|
||||
super.draw(templateName, civCode);
|
||||
|
||||
this.phaseIdx = this.page.TemplateParser.phaseList.indexOf(this.template.phase);
|
||||
|
||||
// Draw the production rows
|
||||
this.ProductionRows.draw(this.template, civCode, this.phaseIdx);
|
||||
|
||||
let boxWidth = Math.max(this.MinWidth, this.captionWidth(), this.ProductionRows.width);
|
||||
|
||||
// Set position of the Structure Box
|
||||
let size = this.gui.size;
|
||||
size.left = this.HMargin + runningWidths[this.phaseIdx];
|
||||
size.right = this.HMargin + runningWidths[this.phaseIdx] + boxWidth;
|
||||
size.top = TreeSection.getPositionOffset(this.phaseIdx, this.page.TemplateParser);
|
||||
size.bottom = TreeSection.getPositionOffset(this.phaseIdx + 1, this.page.TemplateParser) - this.VMargin;
|
||||
this.gui.size = size;
|
||||
|
||||
// Update new right-side-edge dimension
|
||||
runningWidths[this.phaseIdx] += boxWidth + this.HMargin / 2;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* This code wraps the gui representing "trainer units" (a unit that can train other units) within the structree.
|
||||
*
|
||||
* An instance of this class is created for each child of the gui element named "trainers".
|
||||
*/
|
||||
class TrainerBox extends EntityBox
|
||||
{
|
||||
constructor(page, trainerIdx)
|
||||
{
|
||||
super(page);
|
||||
|
||||
this.gui = Engine.GetGUIObjectByName("trainer[" + trainerIdx + "]");
|
||||
this.ProductionRows = new ProductionRowManager(this.page, "trainer[" + trainerIdx + "]_productionRows", false);
|
||||
|
||||
let rowHeight = ProductionIcon.Size().rowHeight;
|
||||
let size = this.gui.size;
|
||||
|
||||
// Adjust height to accommodate production row
|
||||
size.bottom += rowHeight;
|
||||
|
||||
// We make the assumuption that all trainer boxes have the same height
|
||||
let boxHeight = this.VMargin / 2 + (size.bottom - size.top + this.VMargin) * trainerIdx;
|
||||
size.top += boxHeight;
|
||||
size.bottom += boxHeight;
|
||||
|
||||
// Make the box adjust automatically to column width
|
||||
size.rright = 100;
|
||||
size.right = -size.left;
|
||||
|
||||
this.gui.size = size;
|
||||
}
|
||||
|
||||
draw(templateName, civCode)
|
||||
{
|
||||
super.draw(templateName, civCode);
|
||||
|
||||
this.ProductionRows.draw(this.template, civCode);
|
||||
|
||||
// Return the box width
|
||||
return Math.max(this.MinWidth, this.captionWidth(), this.ProductionRows.width);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
class TrainerSection
|
||||
{
|
||||
constructor(page)
|
||||
{
|
||||
this.page = page;
|
||||
this.width = 0;
|
||||
this.widthChangedHandlers = new Set();
|
||||
|
||||
this.TrainerSection = Engine.GetGUIObjectByName("trainerSection");
|
||||
this.Trainers = Engine.GetGUIObjectByName("trainers");
|
||||
|
||||
this.TrainerSectionHeading = Engine.GetGUIObjectByName("trainerSectionHeading");
|
||||
this.TrainerSectionHeading.caption = this.Caption;
|
||||
|
||||
this.trainerBoxes = [];
|
||||
for (let boxIdx in this.Trainers.children)
|
||||
this.trainerBoxes.push(new TrainerBox(this.page, boxIdx));
|
||||
}
|
||||
|
||||
registerWidthChangedHandler(handler)
|
||||
{
|
||||
this.widthChangedHandlers.add(handler);
|
||||
}
|
||||
|
||||
draw(units, civCode)
|
||||
{
|
||||
let caption = this.TrainerSectionHeading;
|
||||
this.width = Engine.GetTextWidth(caption.font, caption.caption) + (caption.size.left + caption.buffer_zone) * 2;
|
||||
let count = 0;
|
||||
|
||||
for (let unitCode of units.keys())
|
||||
{
|
||||
let unitTemplate = this.page.TemplateParser.getEntity(unitCode, civCode);
|
||||
if (!unitTemplate.production.units.length && !unitTemplate.production.techs.length && !unitTemplate.upgrades)
|
||||
continue;
|
||||
|
||||
if (count > this.trainerBoxes.length)
|
||||
{
|
||||
error("\"" + this.activeCiv + "\" has more unit trainers than can be supported by the current GUI layout");
|
||||
break;
|
||||
}
|
||||
|
||||
this.width = Math.max(
|
||||
this.width,
|
||||
this.trainerBoxes[count].draw(unitCode, civCode)
|
||||
);
|
||||
|
||||
++count;
|
||||
}
|
||||
hideRemaining(this.Trainers.name, count);
|
||||
|
||||
// Update width and visibility of section
|
||||
let size = this.TrainerSection.size;
|
||||
this.width += EntityBox.prototype.HMargin;
|
||||
size.left = -this.width + size.right;
|
||||
this.TrainerSection.size = size;
|
||||
this.TrainerSection.hidden = count == 0;
|
||||
|
||||
for (let handler of this.widthChangedHandlers)
|
||||
handler(this.width, !this.TrainerSection.hidden);
|
||||
}
|
||||
}
|
||||
|
||||
TrainerSection.prototype.Caption =
|
||||
translate("Trainer Units");
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object size="100%-124 54+64 100%-16 100%-54" name="trainerSection">
|
||||
<object
|
||||
name="trainerSectionHeading"
|
||||
type="text"
|
||||
font="sans-bold-14"
|
||||
textcolor="white"
|
||||
text_align="center"
|
||||
text_valign="top"
|
||||
size="0 0 100% 16"
|
||||
/>
|
||||
|
||||
<object type="image" style="TreeDisplay" size="0 24 100% 100%" name="trainers">
|
||||
<repeat count="6" var="t">
|
||||
<object type="image" style="StructBox" name="trainer[t]">
|
||||
<object type="text" style="StructNameSpecific" name="trainer[t]_name"/>
|
||||
<object type="button" style="StructIcon" name="trainer[t]_icon"/>
|
||||
<object name="trainer[t]_productionRows">
|
||||
<object style="ProdBoxRow">
|
||||
<repeat count="4">
|
||||
<object type="button" style="ProdBox"/>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
@ -0,0 +1,58 @@
|
||||
class PhaseIdent
|
||||
{
|
||||
constructor(page, phaseIdx)
|
||||
{
|
||||
this.page = page;
|
||||
this.phaseIdx = +phaseIdx;
|
||||
|
||||
this.Ident = Engine.GetGUIObjectByName("phase[" + this.phaseIdx + "]_ident");
|
||||
this.Icon = Engine.GetGUIObjectByName("phase[" + this.phaseIdx + "]_icon");
|
||||
this.Bars = Engine.GetGUIObjectByName("phase[" + this.phaseIdx + "]_bars");
|
||||
|
||||
let prodIconSize = ProductionIcon.Size();
|
||||
let entityBoxHeight = EntityBox.IconAndCaptionHeight();
|
||||
for (let i = 0; i < this.Bars.children.length; ++i)
|
||||
{
|
||||
let size = this.Bars.children[i].size;
|
||||
size.top = entityBoxHeight + prodIconSize.rowHeight * (i + 1);
|
||||
size.bottom = entityBoxHeight + prodIconSize.rowHeight * (i + 2) - prodIconSize.rowGap;
|
||||
this.Bars.children[i].size = size;
|
||||
}
|
||||
}
|
||||
|
||||
draw(phaseList, barLength, civCode)
|
||||
{
|
||||
// Position ident
|
||||
let identSize = this.Ident.size;
|
||||
identSize.top = TreeSection.getPositionOffset(this.phaseIdx, this.page.TemplateParser);
|
||||
identSize.bottom = TreeSection.getPositionOffset(this.phaseIdx + 1, this.page.TemplateParser);
|
||||
this.Ident.size = identSize;
|
||||
|
||||
// Draw main icon
|
||||
this.drawPhaseIcon(this.Icon, this.phaseIdx, civCode);
|
||||
|
||||
// Draw the phase bars
|
||||
let i = 1;
|
||||
for (; i < phaseList.length - this.phaseIdx; ++i)
|
||||
{
|
||||
let prodBar = this.Bars.children[(i - 1)];
|
||||
let prodBarSize = prodBar.size;
|
||||
prodBarSize.right = barLength;
|
||||
prodBar.size = prodBarSize;
|
||||
prodBar.hidden = false;
|
||||
|
||||
this.drawPhaseIcon(prodBar.children[0], this.phaseIdx + i, civCode);
|
||||
}
|
||||
hideRemaining(this.Bars.name, i - 1);
|
||||
}
|
||||
|
||||
drawPhaseIcon(phaseIcon, phaseIndex, civCode)
|
||||
{
|
||||
let phaseName = this.page.TemplateParser.phaseList[phaseIndex];
|
||||
let prodPhaseTemplate = this.page.TemplateParser.getTechnology(phaseName + "_" + civCode, civCode) || this.page.TemplateParser.getTechnology(phaseName, civCode);
|
||||
|
||||
phaseIcon.sprite = "stretched:" + this.page.IconPath + prodPhaseTemplate.icon;
|
||||
phaseIcon.tooltip = getEntityNamesFormatted(prodPhaseTemplate);
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
class PhaseIdentManager
|
||||
{
|
||||
constructor(page)
|
||||
{
|
||||
this.page = page;
|
||||
this.idents = [];
|
||||
|
||||
this.PhaseIdents = Engine.GetGUIObjectByName("phaseIdents");
|
||||
this.Idents = [];
|
||||
for (let identIdx in this.PhaseIdents.children)
|
||||
this.Idents.push(new PhaseIdent(this.page, identIdx));
|
||||
}
|
||||
|
||||
draw(phaseList, civCode, runningWidths, leftMargin)
|
||||
{
|
||||
for (let i = 0; i < phaseList.length; ++i)
|
||||
{
|
||||
let barLength = leftMargin + runningWidths[i] + EntityBox.prototype.HMargin * 0.75;
|
||||
this.Idents[i].draw(phaseList, barLength, civCode);
|
||||
}
|
||||
hideRemaining(this.PhaseIdents.name, phaseList.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
class TreeSection
|
||||
{
|
||||
constructor(page)
|
||||
{
|
||||
this.page = page;
|
||||
|
||||
this.TreeSection = Engine.GetGUIObjectByName("treeSection");
|
||||
this.Structures = Engine.GetGUIObjectByName("structures");
|
||||
|
||||
this.PhaseIdents = new PhaseIdentManager(this.page);
|
||||
|
||||
this.rightMargin = this.TreeSection.size.right;
|
||||
|
||||
this.structureBoxes = [];
|
||||
for (let boxIdx in this.Structures.children)
|
||||
this.structureBoxes.push(new StructureBox(this.page, boxIdx));
|
||||
|
||||
page.TrainerSection.registerWidthChangedHandler(this.onTrainerSectionWidthChange.bind(this));
|
||||
}
|
||||
|
||||
draw(structures, civCode)
|
||||
{
|
||||
if (structures.size > this.structureBoxes.length)
|
||||
error("\"" + this.activeCiv + "\" has more structures than can be supported by the current GUI layout");
|
||||
|
||||
// Draw structures
|
||||
let phaseList = this.page.TemplateParser.phaseList;
|
||||
let count = Math.min(structures.size, this.structureBoxes.length);
|
||||
let runningWidths = Array(phaseList.length).fill(0);
|
||||
let structureIterator = structures.keys();
|
||||
for (let idx = 0; idx < count; ++idx)
|
||||
this.structureBoxes[idx].draw(structureIterator.next().value, civCode, runningWidths);
|
||||
hideRemaining(this.Structures.name, count);
|
||||
|
||||
// Position phase idents
|
||||
this.PhaseIdents.draw(phaseList, civCode, runningWidths, this.Structures.size.left);
|
||||
}
|
||||
|
||||
drawPhaseIcon(phaseIcon, phaseIndex, civCode)
|
||||
{
|
||||
let phaseName = this.page.TemplateParser.phaseList[phaseIndex];
|
||||
let prodPhaseTemplate = this.page.TemplateParser.getTechnology(phaseName + "_" + civCode, civCode) || this.page.TemplateParser.getTechnology(phaseName, civCode);
|
||||
|
||||
phaseIcon.sprite = "stretched:" + this.page.IconPath + prodPhaseTemplate.icon;
|
||||
phaseIcon.tooltip = getEntityNamesFormatted(prodPhaseTemplate);
|
||||
};
|
||||
|
||||
onTrainerSectionWidthChange(trainerSectionWidth, trainerSectionVisible)
|
||||
{
|
||||
let size = this.TreeSection.size;
|
||||
size.right = this.rightMargin;
|
||||
if (trainerSectionVisible)
|
||||
size.right -= trainerSectionWidth + this.page.SectionGap;
|
||||
this.TreeSection.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate row position offset (accounting for different number of prod rows per phase).
|
||||
*
|
||||
* This is a static method as it is also used from within the StructureBox and PhaseIdent classes.
|
||||
*
|
||||
* @param {number} idx
|
||||
* @return {number}
|
||||
*/
|
||||
static getPositionOffset(idx, TemplateParser)
|
||||
{
|
||||
let phases = TemplateParser.phaseList.length;
|
||||
let rowHeight = ProductionIcon.Size().rowHeight;
|
||||
|
||||
let size = EntityBox.IconAndCaptionHeight() * idx; // text, image and offset
|
||||
size += EntityBox.prototype.VMargin * (idx + 1); // Margin above StructureBoxes
|
||||
size += rowHeight * (phases * idx - (idx - 1) * idx / 2); // phase rows (phase-currphase+1 per row)
|
||||
|
||||
return size;
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object size="0 54+64 100%-16 100%-54" name="treeSection">
|
||||
<object name="phaseIdents">
|
||||
<repeat count="4" var="n">
|
||||
<object name="phase[n]_ident">
|
||||
<object name="phase[n]_icon" type="image" size="16 32 16+48 32+48"/>
|
||||
<object name="phase[n]_bars">
|
||||
<repeat count="4" var="k">
|
||||
<object name="phase[n]_bar[k]" type="image" style="ProdBar" z="-10">
|
||||
<object name="phase[n]_bar[k]_icon" type="image" style="ProdBox"/>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
|
||||
<object type="image" name="structures" style="TreeDisplay" size="48+16+8 0 100% 100%">
|
||||
<repeat count="40" var="s">
|
||||
<object type="image" style="StructBox" name="structure[s]">
|
||||
<object type="text" style="StructNameSpecific" name="structure[s]_name"/>
|
||||
<object type="button" style="StructIcon" name="structure[s]_icon"/>
|
||||
<object name="structure[s]_productionRows">
|
||||
<repeat count="4">
|
||||
<object style="ProdBoxRow">
|
||||
<repeat count="24">
|
||||
<object type="button" style="ProdBox"/>
|
||||
</repeat>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* This class represents the Structure Tree GUI page.
|
||||
*
|
||||
* Further methods are described within draw.js
|
||||
*/
|
||||
class StructreePage extends ReferencePage
|
||||
{
|
||||
constructor(data)
|
||||
{
|
||||
super();
|
||||
|
||||
this.structureBoxes = [];
|
||||
this.trainerBoxes = [];
|
||||
|
||||
this.CivEmblem = Engine.GetGUIObjectByName("civEmblem");
|
||||
this.CivName = Engine.GetGUIObjectByName("civName");
|
||||
this.CivHistory = Engine.GetGUIObjectByName("civHistory");
|
||||
|
||||
this.TrainerSection = new TrainerSection(this);
|
||||
this.TreeSection = new TreeSection(this);
|
||||
|
||||
this.civSelection = new CivSelectDropdown(this.civData);
|
||||
if (!this.civSelection.hasCivs())
|
||||
{
|
||||
this.closePage();
|
||||
return;
|
||||
}
|
||||
this.civSelection.registerHandler(this.selectCiv.bind(this));
|
||||
|
||||
let civInfoButton = new CivInfoButton(this);
|
||||
let closeButton = new CloseButton(this);
|
||||
Engine.SetGlobalHotkey("structree", "Press", this.closePage.bind(this));
|
||||
}
|
||||
|
||||
closePage()
|
||||
{
|
||||
Engine.PopGuiPage({ "civ": this.activeCiv, "page": "page_structree.xml" });
|
||||
}
|
||||
|
||||
selectCiv(civCode)
|
||||
{
|
||||
this.setActiveCiv(civCode);
|
||||
|
||||
this.CivEmblem.sprite = "stretched:" + this.civData[this.activeCiv].Emblem;
|
||||
this.CivName.caption = this.civData[this.activeCiv].Name;
|
||||
this.CivHistory.caption = this.civData[this.activeCiv].History;
|
||||
|
||||
let templateLists = this.TemplateLister.getTemplateLists(this.activeCiv);
|
||||
this.TreeSection.draw(templateLists.structures, this.activeCiv);
|
||||
this.TrainerSection.draw(templateLists.units, this.activeCiv);
|
||||
}
|
||||
}
|
||||
|
||||
StructreePage.prototype.CloseButtonTooltip =
|
||||
translate("%(hotkey)s: Close Structure Tree.");
|
||||
|
||||
// Gap between the `TreeSection` and `TrainerSection` gui objects (when the latter is visible)
|
||||
StructreePage.prototype.SectionGap = 12;
|
@ -1,432 +0,0 @@
|
||||
/**
|
||||
* Functions used to collate the contents of a tooltip.
|
||||
*/
|
||||
var g_StructreeTooltipFunctions = [
|
||||
getEntityNamesFormatted,
|
||||
getEntityCostTooltip,
|
||||
getEntityTooltip,
|
||||
getAurasTooltip
|
||||
].concat(g_StatsFunctions);
|
||||
|
||||
/**
|
||||
* 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 leftMargin = Engine.GetGUIObjectByName("tree_display").size.left;
|
||||
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 prodBarWidth = 0;
|
||||
let s = 0;
|
||||
let y = 0;
|
||||
|
||||
for (let stru of g_BuildList[g_SelectedCiv][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 =
|
||||
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)
|
||||
{
|
||||
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.upgrades[prod_pha])
|
||||
for (let upgrade of stru.upgrades[prod_pha])
|
||||
{
|
||||
if (!drawProdIcon(i, s, r, p, upgrade))
|
||||
break;
|
||||
++p;
|
||||
}
|
||||
|
||||
if (stru.wallset && prod_pha == pha)
|
||||
{
|
||||
if (!drawProdIcon(i, s, r, p, stru.wallset.tower))
|
||||
break;
|
||||
++p;
|
||||
}
|
||||
|
||||
if (stru.production.techs[prod_pha])
|
||||
for (let prod of stru.production.techs[prod_pha])
|
||||
{
|
||||
prod = clone(basename(prod).startsWith("phase") ?
|
||||
g_ParsedData.phases[prod] :
|
||||
g_ParsedData.techs[g_SelectedCiv][prod]);
|
||||
|
||||
for (let res in stru.techCostMultiplier)
|
||||
if (prod.cost[res])
|
||||
prod.cost[res] *= stru.techCostMultiplier[res];
|
||||
|
||||
if (!drawProdIcon(i, s, r, p, prod))
|
||||
break;
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
rowCounts[r] = p;
|
||||
|
||||
if (p>c)
|
||||
c = p;
|
||||
|
||||
hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]", 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+"]_rows", r);
|
||||
++s;
|
||||
prodBarWidth += eleWidth + defMargin;
|
||||
}
|
||||
|
||||
hideRemaining("phase["+i+"]", s);
|
||||
|
||||
// Resize phase bars
|
||||
for (let j = 1; j < phaseList.length - i; ++j)
|
||||
{
|
||||
let prodBar = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]");
|
||||
let prodBarSize = prodBar.size;
|
||||
prodBarSize.right = leftMargin + prodBarWidth;
|
||||
prodBar.size = prodBarSize;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
let t = 0;
|
||||
for (let trainer of g_TrainList[g_SelectedCiv])
|
||||
{
|
||||
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 = 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;
|
||||
if (trainer.production)
|
||||
for (let prodType in trainer.production)
|
||||
for (let prod of trainer.production[prodType])
|
||||
{
|
||||
switch (prodType)
|
||||
{
|
||||
case "units":
|
||||
prod = g_ParsedData.units[prod];
|
||||
break;
|
||||
case "techs":
|
||||
prod = clone(g_ParsedData.techs[g_SelectedCiv][prod]);
|
||||
for (let res in trainer.techCostMultiplier)
|
||||
if (prod.cost[res])
|
||||
prod.cost[res] *= trainer.techCostMultiplier[res];
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (!drawProdIcon(null, t, null, p, prod))
|
||||
break;
|
||||
++p;
|
||||
}
|
||||
|
||||
if (trainer.upgrades)
|
||||
for (let upgrade of trainer.upgrades)
|
||||
{
|
||||
if (!drawProdIcon(null, t, null, p, upgrade))
|
||||
break;
|
||||
++p;
|
||||
}
|
||||
|
||||
hideRemaining("trainer["+t+"]_row", p);
|
||||
|
||||
let size = thisEle.size;
|
||||
size.right = size.left + Math.max(p*24, defWidth) + 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("trainers", 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws production icons.
|
||||
*
|
||||
* These are the small icons on the gray bars.
|
||||
*
|
||||
* @param {string} phase - The phase that the parent entity (the entity that
|
||||
* builds/trains/researches this entity) belongs to.
|
||||
* @param {number} parentID - Which parent entity it belongs to on the main phase rows.
|
||||
* @param {number} rowID - Which production row of the parent entity the production
|
||||
* icon sits on, if applicable.
|
||||
* @param {number} iconID - Which production icon to affect.
|
||||
* @param {object} template - The entity being produced.
|
||||
* @return {boolean} True is successfully drawn, False if no space left to draw.
|
||||
*/
|
||||
function drawProdIcon(phase, parentID, rowID, iconID, template)
|
||||
{
|
||||
let prodEle = Engine.GetGUIObjectByName("phase["+phase+"]_struct["+parentID+"]_row["+rowID+"]_prod["+iconID+"]");
|
||||
|
||||
if (phase === null)
|
||||
prodEle = Engine.GetGUIObjectByName("trainer["+parentID+"]_prod["+iconID+"]");
|
||||
|
||||
if (prodEle === undefined)
|
||||
{
|
||||
error("The " + (phase === 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/"+template.icon;
|
||||
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).
|
||||
*
|
||||
* @param {number} idx
|
||||
* @return {number}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions certain elements that only need to be positioned once
|
||||
* (as <repeat> does not position 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.size = "16 32+"+offset+" 48+16 48+32+"+offset;
|
||||
|
||||
// Set initial prod bar size
|
||||
let j = 1;
|
||||
for (; j < phaseList.length - i; ++j)
|
||||
{
|
||||
let prodBar = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]");
|
||||
prodBar.size = "40 1+"+(24*j)+"+98+"+offset+" 0 1+"+(24*j)+"+98+"+offset+"+22";
|
||||
}
|
||||
// Hide remaining prod bars
|
||||
hideRemaining("phase["+i+"]_bars", 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 r = phaseCount - i; r < phaseCount; ++r)
|
||||
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]").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_rows", i);
|
||||
hideRemaining("phase_ident", i);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function drawPhaseIcons()
|
||||
{
|
||||
for (let i = 0; i < g_ParsedData.phaseList.length; ++i)
|
||||
{
|
||||
drawPhaseIcon("phase["+i+"]_phase", i);
|
||||
|
||||
for (let j = 1; j < g_ParsedData.phaseList.length - i; ++j)
|
||||
drawPhaseIcon("phase["+i+"]_bar["+(j-1)+"]_icon", j+i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} guiObjectName
|
||||
* @param {number} phaseIndex
|
||||
*/
|
||||
function drawPhaseIcon(guiObjectName, phaseIndex)
|
||||
{
|
||||
let phaseName = g_ParsedData.phaseList[phaseIndex];
|
||||
let prodPhaseTemplate = g_ParsedData.phases[phaseName + "_" + g_SelectedCiv] || g_ParsedData.phases[phaseName];
|
||||
|
||||
let phaseIcon = Engine.GetGUIObjectByName(guiObjectName);
|
||||
phaseIcon.sprite = "stretched:session/portraits/" + prodPhaseTemplate.icon;
|
||||
phaseIcon.tooltip = getEntityNamesFormatted(prodPhaseTemplate);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<object name="phase_rows">
|
||||
<repeat count="4" var="k">
|
||||
<object name="phase[k]">
|
||||
<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="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="button" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
|
||||
</repeat>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
@ -1,13 +1,3 @@
|
||||
/**
|
||||
* Array of structure template names when given a civ and a phase name.
|
||||
*/
|
||||
var g_BuildList = {};
|
||||
|
||||
/**
|
||||
* Array of template names that can be trained from a unit, given a civ and unit template name.
|
||||
*/
|
||||
var g_TrainList = {};
|
||||
|
||||
/**
|
||||
* Initialize the page
|
||||
*
|
||||
@ -15,180 +5,10 @@ var g_TrainList = {};
|
||||
*/
|
||||
function init(data = {})
|
||||
{
|
||||
let civList = Object.keys(g_CivData).map(civ => ({
|
||||
"name": g_CivData[civ].Name,
|
||||
"code": civ,
|
||||
})).sort(sortNameIgnoreCase);
|
||||
g_Page = new StructreePage(data);
|
||||
|
||||
if (!civList.length)
|
||||
{
|
||||
closePage();
|
||||
return;
|
||||
}
|
||||
|
||||
g_ParsedData = {
|
||||
"units": {},
|
||||
"structures": {},
|
||||
"techs": {},
|
||||
"phases": {}
|
||||
};
|
||||
|
||||
let civSelection = Engine.GetGUIObjectByName("civSelection");
|
||||
civSelection.list = civList.map(c => c.name);
|
||||
civSelection.list_data = civList.map(c => c.code);
|
||||
civSelection.selected = data.civ ? civSelection.list_data.indexOf(data.civ) : 0;
|
||||
|
||||
Engine.GetGUIObjectByName("civinfo").tooltip = colorizeHotkey(translate("%(hotkey)s: Switch to Civilization Overview."), "civinfo");
|
||||
Engine.GetGUIObjectByName("close").tooltip = colorizeHotkey(translate("%(hotkey)s: Close Structure Tree."), "cancel");
|
||||
}
|
||||
|
||||
function switchToCivInfoPage()
|
||||
{
|
||||
Engine.PopGuiPage({ "civ": g_SelectedCiv, "nextPage": "page_civinfo.xml" });
|
||||
}
|
||||
|
||||
function closePage()
|
||||
{
|
||||
Engine.PopGuiPage({ "civ": g_SelectedCiv, "page": "page_structree.xml" });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} civCode
|
||||
*/
|
||||
function selectCiv(civCode)
|
||||
{
|
||||
if (civCode === g_SelectedCiv || !g_CivData[civCode])
|
||||
return;
|
||||
|
||||
g_SelectedCiv = civCode;
|
||||
|
||||
g_CurrentModifiers = deriveModifications(g_AutoResearchTechList);
|
||||
|
||||
// If a buildList already exists, then this civ has already been parsed
|
||||
if (g_BuildList[g_SelectedCiv])
|
||||
{
|
||||
draw();
|
||||
drawPhaseIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
let templateLists = compileTemplateLists(civCode);
|
||||
|
||||
for (let u of templateLists.units.keys())
|
||||
if (!g_ParsedData.units[u])
|
||||
g_ParsedData.units[u] = loadEntityTemplate(u);
|
||||
|
||||
for (let s of templateLists.structures.keys())
|
||||
if (!g_ParsedData.structures[s])
|
||||
g_ParsedData.structures[s] = loadEntityTemplate(s);
|
||||
|
||||
// Load technologies
|
||||
g_ParsedData.techs[civCode] = {};
|
||||
for (let techcode of templateLists.techs.keys())
|
||||
if (basename(techcode).startsWith("phase"))
|
||||
g_ParsedData.phases[techcode] = loadPhase(techcode);
|
||||
else
|
||||
g_ParsedData.techs[civCode][techcode] = loadTechnology(techcode);
|
||||
|
||||
// Establish phase order
|
||||
g_ParsedData.phaseList = UnravelPhases(g_ParsedData.phases);
|
||||
|
||||
// Load any required generic phases that aren't already loaded
|
||||
for (let phasecode of g_ParsedData.phaseList)
|
||||
if (!g_ParsedData.phases[phasecode])
|
||||
g_ParsedData.phases[phasecode] = loadPhase(phasecode);
|
||||
|
||||
// Group production and upgrade lists of structures by phase
|
||||
for (let structCode of templateLists.structures.keys())
|
||||
{
|
||||
let structInfo = g_ParsedData.structures[structCode];
|
||||
structInfo.phase = getPhaseOfTemplate(structInfo);
|
||||
let structPhaseIdx = g_ParsedData.phaseList.indexOf(structInfo.phase);
|
||||
|
||||
// If this structure is shared with another civ,
|
||||
// it may have already gone through the grouping process already.
|
||||
if (!Array.isArray(structInfo.production.techs))
|
||||
continue;
|
||||
|
||||
// Sort techs by phase
|
||||
let newProdTech = {};
|
||||
for (let prod of structInfo.production.techs)
|
||||
{
|
||||
let phase = getPhaseOfTechnology(prod);
|
||||
if (phase === false)
|
||||
continue;
|
||||
|
||||
if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
|
||||
phase = structInfo.phase;
|
||||
|
||||
if (!(phase in newProdTech))
|
||||
newProdTech[phase] = [];
|
||||
|
||||
newProdTech[phase].push(prod);
|
||||
}
|
||||
|
||||
// Sort units by phase
|
||||
let newProdUnits = {};
|
||||
for (let prod of structInfo.production.units)
|
||||
{
|
||||
let phase = getPhaseOfTemplate(g_ParsedData.units[prod]);
|
||||
if (phase === false)
|
||||
continue;
|
||||
|
||||
if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
|
||||
phase = structInfo.phase;
|
||||
|
||||
if (!(phase in newProdUnits))
|
||||
newProdUnits[phase] = [];
|
||||
|
||||
newProdUnits[phase].push(prod);
|
||||
}
|
||||
|
||||
g_ParsedData.structures[structCode].production = {
|
||||
"techs": newProdTech,
|
||||
"units": newProdUnits
|
||||
};
|
||||
|
||||
// Sort upgrades by phase
|
||||
let newUpgrades = {};
|
||||
if (structInfo.upgrades)
|
||||
for (let upgrade of structInfo.upgrades)
|
||||
{
|
||||
let phase = getPhaseOfTemplate(upgrade);
|
||||
|
||||
if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
|
||||
phase = structInfo.phase;
|
||||
|
||||
if (!newUpgrades[phase])
|
||||
newUpgrades[phase] = [];
|
||||
newUpgrades[phase].push(upgrade);
|
||||
}
|
||||
g_ParsedData.structures[structCode].upgrades = newUpgrades;
|
||||
}
|
||||
|
||||
// Determine the buildList for the civ (grouped by phase)
|
||||
let buildList = {};
|
||||
let trainerList = [];
|
||||
for (let pha of g_ParsedData.phaseList)
|
||||
buildList[pha] = [];
|
||||
for (let structCode of templateLists.structures.keys())
|
||||
{
|
||||
let phase = g_ParsedData.structures[structCode].phase;
|
||||
buildList[phase].push(structCode);
|
||||
}
|
||||
|
||||
for (let unitCode of templateLists.units.keys())
|
||||
{
|
||||
let unitTemplate = g_ParsedData.units[unitCode];
|
||||
if (!unitTemplate.production.units.length && !unitTemplate.production.techs.length && !unitTemplate.upgrades)
|
||||
continue;
|
||||
|
||||
trainerList.push(unitCode);
|
||||
}
|
||||
|
||||
g_BuildList[g_SelectedCiv] = buildList;
|
||||
g_TrainList[g_SelectedCiv] = trainerList;
|
||||
|
||||
draw();
|
||||
drawPhaseIcons();
|
||||
if (data.civ)
|
||||
g_Page.civSelection.selectCiv(data.civ);
|
||||
else
|
||||
g_Page.civSelection.selectFirstCiv();
|
||||
}
|
||||
|
@ -2,14 +2,15 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/reference/common/"/>
|
||||
<script directory="gui/reference/structree/"/>
|
||||
|
||||
<object hotkey="structree">
|
||||
<action on="Press">
|
||||
closePage();
|
||||
</action>
|
||||
</object>
|
||||
<script directory="gui/reference/common/"/>
|
||||
<script directory="gui/reference/common/Buttons/"/>
|
||||
<script directory="gui/reference/common/Dropdowns/"/>
|
||||
|
||||
<script directory="gui/reference/structree/"/>
|
||||
<script directory="gui/reference/structree/Boxes/"/>
|
||||
<script directory="gui/reference/structree/Sections/Trainer/"/>
|
||||
<script directory="gui/reference/structree/Sections/Tree/"/>
|
||||
|
||||
<!-- Add a translucent black background to fade out the menu page -->
|
||||
<object type="image" sprite="BackgroundTranslucent"/>
|
||||
@ -20,21 +21,7 @@
|
||||
</object>
|
||||
|
||||
<!-- Civ selection -->
|
||||
<object size="16 10 100%-16 30">
|
||||
<object
|
||||
name="civSelectionHeading"
|
||||
type="text"
|
||||
font="sans-bold-16"
|
||||
textcolor="white"
|
||||
text_align="left"
|
||||
size="100%-290 10 100%-180 48">
|
||||
<translatableAttribute id="caption">Civilization:</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="civSelection" type="dropdown" style="ModernDropDown" size="100%-180 8 100% 34" dropdown_size="424">
|
||||
<action on="SelectionChange">selectCiv(this.list_data[this.selected]);</action>
|
||||
</object>
|
||||
</object>
|
||||
<include file="gui/reference/common/Dropdowns/CivSelectDropdown.xml"/>
|
||||
|
||||
<object
|
||||
name="civEmblem"
|
||||
@ -63,79 +50,15 @@
|
||||
size="104 52 100%-8 100%"
|
||||
/>
|
||||
|
||||
<!-- Structure Tree display -->
|
||||
<object size="0 54+64 100%-124 100%-54" name="display_tree">
|
||||
<object name="phase_ident">
|
||||
<repeat count="4" var="n">
|
||||
<object>
|
||||
<object name="phase[n]_phase" type="image"/>
|
||||
<object name="phase[n]_bars">
|
||||
<repeat count="4" var="k">
|
||||
<object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
|
||||
<object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
<!-- Structure Tree display section -->
|
||||
<include file="gui/reference/structree/Sections/Tree/TreeSection.xml"/>
|
||||
|
||||
<object type="image" name="tree_display" style="TreeDisplay" size="48+16+8 0 100%-12 100%">
|
||||
<include file="gui/reference/structree/rows.xml"/>
|
||||
</object>
|
||||
</object>
|
||||
<!-- Trainer Units display section -->
|
||||
<include file="gui/reference/structree/Sections/Trainer/TrainerSection.xml"/>
|
||||
|
||||
<!-- Trainer Units display -->
|
||||
<object size="100%-124 54+64 100%-16 100%-54" name="display_trainers">
|
||||
<object
|
||||
type="text"
|
||||
font="sans-bold-14"
|
||||
textcolor="white"
|
||||
text_align="center"
|
||||
text_valign="top"
|
||||
size="0 0 100% 16"
|
||||
>
|
||||
<translatableAttribute id="caption">Trainer Units</translatableAttribute>
|
||||
</object>
|
||||
<!-- Buttons -->
|
||||
<include file="gui/reference/common/Buttons/CivInfoButton.xml"/>
|
||||
<include file="gui/reference/common/Buttons/CloseButton.xml"/>
|
||||
|
||||
<object type="image" style="TreeDisplay" size="0 24 100% 100%" name="trainers">
|
||||
<repeat count="6" var="t">
|
||||
<object type="image" style="StructBox" name="trainer[t]">
|
||||
<object type="text" style="StructNameSpecific" name="trainer[t]_name"/>
|
||||
<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="button" style="ProdBox" name="trainer[t]_prod[p]"/>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<!-- Civilization Overview page -->
|
||||
<object
|
||||
type="button"
|
||||
style="StoneButton"
|
||||
size="100%-421 100%-44 100%-221 100%-16"
|
||||
name="civinfo"
|
||||
hotkey="civinfo"
|
||||
>
|
||||
<translatableAttribute id="caption">Civilization Overview</translatableAttribute>
|
||||
<action on="Press">switchToCivInfoPage();</action>
|
||||
</object>
|
||||
<!-- Close dialog -->
|
||||
<object
|
||||
type="button"
|
||||
style="StoneButton"
|
||||
size="100%-216 100%-44 100%-16 100%-16"
|
||||
name="close"
|
||||
hotkey="cancel"
|
||||
>
|
||||
<translatableAttribute id="caption">Close</translatableAttribute>
|
||||
<action on="Press">closePage();</action>
|
||||
</object>
|
||||
</object>
|
||||
</objects>
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<style name="StructNameSpecific"
|
||||
font="sans-12"
|
||||
size="8 0 100%-8 20"
|
||||
size="0 0 100% 20"
|
||||
text_align="center"
|
||||
textcolor="white"
|
||||
/>
|
||||
@ -18,12 +18,22 @@
|
||||
|
||||
<style name="StructIcon"
|
||||
size="50%-24 8+16 50%+24 8+16+48"
|
||||
tooltip_style="referenceTooltip"
|
||||
/>
|
||||
|
||||
<style name="ProdBox"
|
||||
size="0 0 20 20"
|
||||
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
|
||||
tooltip_style="referenceTooltip"
|
||||
/>
|
||||
|
||||
<style name="ProdBar"
|
||||
sprite="ProdBar"
|
||||
size="40 0 0 0"
|
||||
/>
|
||||
|
||||
<style name="ProdBox"
|
||||
size="2 2 22 22"
|
||||
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
|
||||
tooltip_style="referenceTooltip"
|
||||
/>
|
||||
|
||||
<style name="ProdBoxRow"
|
||||
size="50% 100%-24 100% 100%"
|
||||
/>
|
||||
</styles>
|
||||
|
169
binaries/data/mods/public/gui/reference/viewer/ViewerPage.js
Normal file
169
binaries/data/mods/public/gui/reference/viewer/ViewerPage.js
Normal file
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* This class represents the Template Viewer GUI page.
|
||||
*/
|
||||
class ViewerPage extends ReferencePage
|
||||
{
|
||||
constructor(data)
|
||||
{
|
||||
super();
|
||||
|
||||
this.currentTemplate = undefined;
|
||||
|
||||
this.guiElements = {
|
||||
"entityName": Engine.GetGUIObjectByName("entityName"),
|
||||
"entityIcon": Engine.GetGUIObjectByName("entityIcon"),
|
||||
"entityStats": Engine.GetGUIObjectByName("entityStats"),
|
||||
"entityInfo": Engine.GetGUIObjectByName("entityInfo"),
|
||||
"entityRankGlyph": Engine.GetGUIObjectByName("entityRankGlyph"),
|
||||
};
|
||||
|
||||
let closeButton = new CloseButton(this);
|
||||
}
|
||||
|
||||
selectTemplate(data)
|
||||
{
|
||||
if (!data || !data.templateName)
|
||||
{
|
||||
error("Viewer: No template provided");
|
||||
this.closePage();
|
||||
return;
|
||||
}
|
||||
|
||||
let templateName = removeFiltersFromTemplateName(data.templateName);
|
||||
let isTech = TechnologyTemplateExists(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 = this.TemplateLoader.getBaseTemplateName(templateName, this.TemplateLoader.DefaultCiv);
|
||||
|
||||
this.setActiveCiv(this.TemplateLoader.loadEntityTemplate(templateName, this.TemplateLoader.DefaultCiv).Identity.Civ);
|
||||
}
|
||||
|
||||
if (this.activeCiv == this.TemplateLoader.DefaultCiv && data.civ)
|
||||
this.setActiveCiv(data.civ);
|
||||
|
||||
this.currentTemplate = isTech ? this.TemplateParser.getTechnology(templateName, this.activeCiv) : this.TemplateParser.getEntity(templateName, this.activeCiv);
|
||||
if (!this.currentTemplate)
|
||||
{
|
||||
error("Viewer: unable to recognize or load template (" + templateName + ")");
|
||||
this.closePage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Here we compile lists of the names of all the entities that can build/train/research this template,
|
||||
// and the entities and technologies that this template can build/train/research.
|
||||
// We do that here, so we don't do it later in the tooltip callback functions, as that would be messier.
|
||||
if (this.activeCiv != this.TemplateLoader.DefaultCiv)
|
||||
{
|
||||
let templateLists = this.TemplateLister.getTemplateLists(this.activeCiv);
|
||||
|
||||
let builders = templateLists.structures.get(this.currentTemplate.name.internal);
|
||||
if (builders && builders.length)
|
||||
this.currentTemplate.builtByListOfNames = builders.map(builder => getEntityNames(this.TemplateParser.getEntity(builder, this.activeCiv)));
|
||||
|
||||
let trainers = templateLists.units.get(this.currentTemplate.name.internal);
|
||||
if (trainers && trainers.length)
|
||||
this.currentTemplate.trainedByListOfNames = trainers.map(trainer => getEntityNames(this.TemplateParser.getEntity(trainer, this.activeCiv)));
|
||||
|
||||
let researchers = templateLists.techs.get(this.currentTemplate.name.internal);
|
||||
if (researchers && researchers.length)
|
||||
this.currentTemplate.researchedByListOfNames = researchers.map(researcher => getEntityNames(this.TemplateParser.getEntity(researcher, this.activeCiv)));
|
||||
}
|
||||
|
||||
if (this.currentTemplate.builder && this.currentTemplate.builder.length)
|
||||
this.currentTemplate.buildListOfNames = this.currentTemplate.builder.map(prod => getEntityNames(this.TemplateParser.getEntity(prod, this.activeCiv)));
|
||||
|
||||
if (this.currentTemplate.production)
|
||||
{
|
||||
if (this.currentTemplate.production.units && this.currentTemplate.production.units.length)
|
||||
this.currentTemplate.trainListOfNames = this.currentTemplate.production.units.map(prod => getEntityNames(this.TemplateParser.getEntity(prod, this.activeCiv)));
|
||||
|
||||
if (this.currentTemplate.production.techs && this.currentTemplate.production.techs.length)
|
||||
{
|
||||
this.currentTemplate.researchListOfNames = [];
|
||||
for (let tech of this.currentTemplate.production.techs)
|
||||
{
|
||||
let techTemplate = this.TemplateParser.getTechnology(tech, this.activeCiv);
|
||||
if (techTemplate.reqs)
|
||||
this.currentTemplate.researchListOfNames.push(getEntityNames(techTemplate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentTemplate.upgrades)
|
||||
this.currentTemplate.upgradeListOfNames = this.currentTemplate.upgrades.map(upgrade =>
|
||||
getEntityNames(upgrade.name ? upgrade : this.TemplateParser.getEntity(upgrade.entity, this.activeCiv))
|
||||
);
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the UI elements.
|
||||
*/
|
||||
draw()
|
||||
{
|
||||
this.guiElements.entityName.caption = getEntityNamesFormatted(this.currentTemplate);
|
||||
|
||||
let entityIcon = this.guiElements.entityIcon;
|
||||
entityIcon.sprite = "stretched:" + this.IconPath + this.currentTemplate.icon;
|
||||
|
||||
let entityStats = this.guiElements.entityStats;
|
||||
entityStats.caption = this.constructor.buildText(this.currentTemplate, this.StatsFunctions);
|
||||
|
||||
let infoSize = this.guiElements.entityInfo.size;
|
||||
// The magic '8' below provides a gap between the bottom of the icon, and the start of the info text.
|
||||
infoSize.top = Math.max(entityIcon.size.bottom + 8, entityStats.size.top + entityStats.getTextSize().height);
|
||||
this.guiElements.entityInfo.size = infoSize;
|
||||
|
||||
this.guiElements.entityInfo.caption = this.constructor.buildText(this.currentTemplate, this.InfoFunctions, "\n\n");
|
||||
|
||||
if (this.currentTemplate.promotion)
|
||||
this.guiElements.entityRankGlyph.sprite = "stretched:" + this.RankIconPath + this.currentTemplate.promotion.current_rank + ".png";
|
||||
this.guiElements.entityRankGlyph.hidden = !this.currentTemplate.promotion;
|
||||
}
|
||||
|
||||
closePage()
|
||||
{
|
||||
Engine.PopGuiPage({ "civ": this.activeCiv, "page": "page_viewer.xml" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of these functions appear to the right of the entity icon
|
||||
* on the page.
|
||||
*/
|
||||
ViewerPage.prototype.StatsFunctions = [getEntityCostTooltip].concat(ReferencePage.prototype.StatsFunctions);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* The functions listed can be found in gui/common/tooltips.js or
|
||||
* gui/reference/common/tooltips.js
|
||||
*/
|
||||
ViewerPage.prototype.InfoFunctions = [
|
||||
getEntityTooltip,
|
||||
getHistoryTooltip,
|
||||
getDescriptionTooltip,
|
||||
getAurasTooltip,
|
||||
getVisibleEntityClassesFormatted,
|
||||
getBuiltByText,
|
||||
getTrainedByText,
|
||||
getResearchedByText,
|
||||
getBuildText,
|
||||
getTrainText,
|
||||
getResearchText,
|
||||
getUpgradeText
|
||||
];
|
||||
|
||||
ViewerPage.prototype.CloseButtonTooltip =
|
||||
translate("%(hotkey)s: Close Template Viewer");
|
||||
|
||||
ViewerPage.prototype.RankIconPath = "session/icons/ranks/";
|
@ -1,34 +1,3 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@ -36,11 +5,6 @@ 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.
|
||||
*
|
||||
@ -50,166 +14,6 @@ var g_RankIconPath = "session/icons/ranks/";
|
||||
*/
|
||||
function init(data)
|
||||
{
|
||||
if (!data || !data.templateName)
|
||||
{
|
||||
error("Viewer: No template provided");
|
||||
closePage();
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
let entityInfo = Engine.GetGUIObjectByName("entityInfo");
|
||||
let infoSize = entityInfo.size;
|
||||
// The magic '8' below provides a gap between the bottom of the icon, and the start of the info text.
|
||||
infoSize.top = Math.max(entityIcon.size.bottom + 8, entityStats.size.top + entityStats.getTextSize().height);
|
||||
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 structures 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 structures/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))));
|
||||
g_Page = new ViewerPage();
|
||||
g_Page.selectTemplate(data);
|
||||
}
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
<objects>
|
||||
<script directory="gui/common/"/>
|
||||
|
||||
<script directory="gui/reference/common/"/>
|
||||
<script directory="gui/reference/common/Buttons/"/>
|
||||
|
||||
<script directory="gui/reference/viewer/"/>
|
||||
|
||||
<!-- Add a translucent black background to fade out whatever's behind this -->
|
||||
@ -27,16 +30,9 @@
|
||||
|
||||
<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">Engine.PopGuiPage();</action>
|
||||
</object>
|
||||
<!-- Close button -->
|
||||
<include file="gui/reference/common/Buttons/CloseButton.xml"/>
|
||||
|
||||
</object>
|
||||
|
||||
</objects>
|
||||
|
Loading…
Reference in New Issue
Block a user