1
0
forked from 0ad/0ad

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:
s0600204 2020-07-07 19:11:36 +00:00
parent 365dbd91fc
commit b2842e8021
38 changed files with 1943 additions and 1660 deletions

View File

@ -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)

View File

@ -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.");

View File

@ -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"
/>

View File

@ -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";

View File

@ -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"
/>

View File

@ -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:");

View File

@ -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>

View File

@ -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
];

View 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;
}
}

View 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"];

View 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;
}
}

View 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");
}

View File

@ -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;
}

View File

@ -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("∞")
});
}

View File

@ -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;
}

View File

@ -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);
}

View 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) : "";
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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");

View File

@ -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>

View File

@ -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);
};
}

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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>

View File

@ -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;

View File

@ -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);
}

View File

@ -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>

View File

@ -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();
}

View File

@ -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>

View File

@ -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>

View 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/";

View File

@ -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);
}

View File

@ -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>