2020-07-07 12:11:36 -07:00
|
|
|
/**
|
|
|
|
|
* This class represents the Template Viewer GUI page.
|
|
|
|
|
*/
|
|
|
|
|
class ViewerPage extends ReferencePage
|
|
|
|
|
{
|
2024-09-21 11:24:36 -07:00
|
|
|
constructor(closePageCallback)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
2024-09-21 11:24:36 -07:00
|
|
|
super(closePageCallback);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
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"),
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const closeButton = new CloseButton(this);
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectTemplate(data)
|
|
|
|
|
{
|
|
|
|
|
if (!data || !data.templateName)
|
|
|
|
|
{
|
|
|
|
|
error("Viewer: No template provided");
|
|
|
|
|
this.closePage();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const templateName = removeFiltersFromTemplateName(data.templateName);
|
|
|
|
|
const isTech = TechnologyTemplateExists(templateName);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
// Attempt to get the civ code from the template, or, if
|
|
|
|
|
// it's a technology, from the researcher's template.
|
|
|
|
|
if (!isTech)
|
|
|
|
|
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)
|
|
|
|
|
{
|
2021-04-14 13:05:26 -07:00
|
|
|
let currentTemplateName = this.currentTemplate.name.internal;
|
|
|
|
|
if (!isTech)
|
|
|
|
|
{
|
|
|
|
|
// If template is a non-promotion, non-upgrade variant of another (e.g.
|
|
|
|
|
// units/{civ}/support_female_citizen_house), we wish to use the name of the base template,
|
|
|
|
|
// not that of the variant template, when interacting with the compiled Template Lists.
|
2025-05-10 09:38:38 -07:00
|
|
|
const templateVariance = this.TemplateLoader.getVariantBaseAndType(this.currentTemplate.name.internal, this.TemplateLoader.DefaultCiv);
|
2021-04-14 13:05:26 -07:00
|
|
|
if (templateVariance[1].passthru)
|
|
|
|
|
currentTemplateName = templateVariance[0];
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const templateLists = this.TemplateLister.getTemplateLists(this.activeCiv);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const builders = templateLists.structures.get(currentTemplateName);
|
2020-07-07 12:11:36 -07:00
|
|
|
if (builders && builders.length)
|
|
|
|
|
this.currentTemplate.builtByListOfNames = builders.map(builder => getEntityNames(this.TemplateParser.getEntity(builder, this.activeCiv)));
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const trainers = templateLists.units.get(currentTemplateName);
|
2020-07-07 12:11:36 -07:00
|
|
|
if (trainers && trainers.length)
|
|
|
|
|
this.currentTemplate.trainedByListOfNames = trainers.map(trainer => getEntityNames(this.TemplateParser.getEntity(trainer, this.activeCiv)));
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const researchers = templateLists.techs.get(currentTemplateName);
|
2020-07-07 12:11:36 -07:00
|
|
|
if (researchers && researchers.length)
|
2023-01-16 06:09:53 -08:00
|
|
|
{
|
2020-07-07 12:11:36 -07:00
|
|
|
this.currentTemplate.researchedByListOfNames = researchers.map(researcher => getEntityNames(this.TemplateParser.getEntity(researcher, this.activeCiv)));
|
2026-02-12 06:56:03 -08:00
|
|
|
const techCostMultiplier = this.TemplateParser.getEntity(researchers[0], this.activeCiv).researcher.techCostMultiplier;
|
|
|
|
|
for (const res in techCostMultiplier)
|
2023-01-16 06:09:53 -08:00
|
|
|
if (this.currentTemplate.cost[res])
|
|
|
|
|
this.currentTemplate.cost[res] *= techCostMultiplier[res];
|
|
|
|
|
}
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = [];
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const tech of this.currentTemplate.production.techs)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
2025-05-10 09:38:38 -07:00
|
|
|
const techTemplate = this.TemplateParser.getTechnology(tech, this.activeCiv);
|
2020-07-07 12:11:36 -07:00
|
|
|
if (techTemplate.reqs)
|
|
|
|
|
this.currentTemplate.researchListOfNames.push(getEntityNames(techTemplate));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
|
|
|
if (this.currentTemplate.upgrade)
|
|
|
|
|
this.currentTemplate.upgradeListOfNames = this.currentTemplate.upgrade.options.map(upgrade =>
|
2020-07-07 12:11:36 -07:00
|
|
|
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);
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const entityIcon = this.guiElements.entityIcon;
|
2020-07-07 12:11:36 -07:00
|
|
|
entityIcon.sprite = "stretched:" + this.IconPath + this.currentTemplate.icon;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const entityStats = this.guiElements.entityStats;
|
2020-07-07 12:11:36 -07:00
|
|
|
entityStats.caption = this.constructor.buildText(this.currentTemplate, this.StatsFunctions);
|
|
|
|
|
|
2024-12-26 07:52:09 -08:00
|
|
|
const infoTopMargin = 8;
|
2025-05-29 15:23:46 -07:00
|
|
|
this.guiElements.entityInfo.size.top = Math.max(entityIcon.size.bottom + infoTopMargin, entityStats.size.top + entityStats.getTextSize().height + infoTopMargin);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
2024-09-21 11:24:36 -07:00
|
|
|
this.closePageCallback({ "civ": this.activeCiv, "page": "page_viewer.xml" });
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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/";
|