Revamp GetTemplateDataHelper

This patch is primarily meant as preparation in order to be able to
reuse this logic for GetEntityState calls in the future.

Key changes:
- Clearer API by providing different functions for different purposes.
- Slight performance optimisation by storing parameters in a context
object and pulling the info from there instead of redeclaring a lambda
each time, which it seems Spidermonkey struggled to optimise well.
- Allow computing only partial template data, specified by passing a
list of desired components. (to be used more in the future)
- Make it so the naming and structure of template data and matches 1:1
with the data in entity states. This makes accessing values from them
more consistent and would also allow e.g. adopting data between the two
in the future.
This commit is contained in:
Vantha 2026-02-12 15:56:03 +01:00 committed by Vantha
parent f90804c78f
commit a89ce596a4
No known key found for this signature in database
GPG key ID: 3F5D02FA4D3E8E74
10 changed files with 587 additions and 495 deletions

File diff suppressed because it is too large Load diff

View file

@ -157,12 +157,12 @@ function getHistoryTooltip(template)
function getHealthTooltip(template)
{
if (!template.health)
if (!template.maxHitpoints)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Health:")),
"details": template.health
"details": template.maxHitpoints
});
}

View file

@ -60,7 +60,7 @@ class TemplateParser
return null;
const template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);
const parsed = GetTemplateDataHelper(template, null, this.TemplateLoader.auraData, g_ResourceData, this.modifiers[civCode] || {});
const parsed = g_TemplateHelper.computeDataFromModifiers(template, this.TemplateLoader.auraData, g_ResourceData, this.modifiers[civCode] || {});
parsed.name.internal = templateName;
parsed.history = template.Identity.History;
@ -118,11 +118,11 @@ class TemplateParser
parsed.resistance = struct.resistance;
parsed.auras = struct.auras;
// For technology cost multiplier, we need to use the tower
// For technology cost multiplier (in the researcher component), we need to use the tower
struct = this.getEntity(parsed.wallSet.templates.tower, civCode);
parsed.techCostMultiplier = struct.techCostMultiplier;
parsed.researcher = struct.researcher;
let health;
let hitpoints;
for (const wSegm in parsed.wallSet.templates)
{
@ -141,30 +141,30 @@ class TemplateParser
if (["gate", "tower"].indexOf(wSegm) != -1)
continue;
if (!health)
if (!hitpoints)
{
health = { "min": wPart.health, "max": wPart.health };
hitpoints = { "min": wPart.maxHitpoints, "max": wPart.maxHitpoints };
continue;
}
health.min = Math.min(health.min, wPart.health);
health.max = Math.max(health.max, wPart.health);
hitpoints.min = Math.min(hitpoints.min, wPart.maxHitpoints);
hitpoints.max = Math.max(hitpoints.max, wPart.maxHitpoints);
}
if (parsed.wallSet.templates.curves)
for (const curve of parsed.wallSet.templates.curves)
{
const wPart = this.getEntity(curve, civCode);
health.min = Math.min(health.min, wPart.health);
health.max = Math.max(health.max, wPart.health);
hitpoints.min = Math.min(hitpoints.min, wPart.maxHitpoints);
hitpoints.max = Math.max(hitpoints.max, wPart.maxHitpoints);
}
if (health.min == health.max)
parsed.health = health.min;
if (hitpoints.min === hitpoints.max)
parsed.maxHitpoints = hitpoints.min;
else
parsed.health = sprintf(translate("%(health_min)s to %(health_max)s"), {
"health_min": health.min,
"health_max": health.max
parsed.maxHitpoints = sprintf(translate("%(health_min)s to %(health_max)s"), {
"health_min": hitpoints.min,
"health_max": hitpoints.max
});
}
@ -252,7 +252,7 @@ class TemplateParser
/**
* Provided with an array containing basic information about possible
* upgrades, such as that generated by globalscript's GetTemplateDataHelper,
* upgrades, such as that generated by globalscript's g_TemplateHelper,
* 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.
@ -264,7 +264,7 @@ class TemplateParser
{
upgrade.entity = upgrade.entity.replace(/\{(civ|native)\}/g, civCode);
const data = GetTemplateDataHelper(this.TemplateLoader.loadEntityTemplate(upgrade.entity, civCode), null, this.TemplateLoader.auraData, g_ResourceData, this.modifiers[civCode] || {});
const data = g_TemplateHelper.computeDataFromModifiers(this.TemplateLoader.loadEntityTemplate(upgrade.entity, civCode), this.TemplateLoader.auraData, g_ResourceData, this.modifiers[civCode] || {});
data.name.internal = upgrade.entity;
data.cost = upgrade.cost;
data.icon = upgrade.icon || data.icon;

View file

@ -15,7 +15,7 @@ var g_Page;
function GetTemplateData(templateName)
{
const template = g_Page.TemplateLoader.loadEntityTemplate(templateName, g_Page.activeCiv);
return GetTemplateDataHelper(template, null, g_Page.TemplateLoader.auraData, g_ResourceData, g_Page.TemplateParser.getModifiers(g_Page.activeCiv));
return g_TemplateHelper.computeDataFromModifiers(template, g_Page.TemplateLoader.auraData, g_ResourceData, g_Page.TemplateParser.getModifiers(g_Page.activeCiv));
}
/**

View file

@ -37,9 +37,9 @@ class ProductionRowManager
case "techs":
pIdx = this.page.TemplateParser.phaseList.indexOf(this.page.TemplateParser.getPhaseOfTechnology(prod, civCode));
prod = clone(this.page.TemplateParser.getTechnology(prod, civCode));
for (const res in template.techCostMultiplier)
for (const res in template.researcher.techCostMultiplier)
if (prod.cost[res])
prod.cost[res] *= template.techCostMultiplier[res];
prod.cost[res] *= template.researcher.techCostMultiplier[res];
break;
default:

View file

@ -78,8 +78,8 @@ class ViewerPage extends ReferencePage
if (researchers && researchers.length)
{
this.currentTemplate.researchedByListOfNames = researchers.map(researcher => getEntityNames(this.TemplateParser.getEntity(researcher, this.activeCiv)));
const { techCostMultiplier } = this.TemplateParser.getEntity(researchers[0], this.activeCiv);
for (const res in this.currentTemplate.cost)
const techCostMultiplier = this.TemplateParser.getEntity(researchers[0], this.activeCiv).researcher.techCostMultiplier;
for (const res in techCostMultiplier)
if (this.currentTemplate.cost[res])
this.currentTemplate.cost[res] *= techCostMultiplier[res];
}

View file

@ -48,7 +48,7 @@ function loadWallsetsFromCivData()
function loadWallset(wallsetPath, civ)
{
const newWallset = { "curves": [] };
const wallsetData = GetTemplateDataHelper(wallsetPath, null, null, g_Resources).wallSet;
const wallsetData = g_TemplateHelper.getBasicData(wallsetPath, {}, g_Resources, ["WallSet"]).wallSet;
for (const element in wallsetData.templates)
if (element == "curves")
@ -271,7 +271,7 @@ function getWallElement(element, style)
function readyWallElement(path, civCode)
{
path = path.replace(/\{civ\}/g, civCode);
const template = GetTemplateDataHelper(Engine.GetTemplate(path), null, null, g_Resources);
const template = g_TemplateHelper.getBasicData(Engine.GetTemplate(path), null, g_Resources, ["WallPiece", "Obstruction"]);
const length = template.wallPiece ? template.wallPiece.length : template.obstruction.shape.width;
return deepfreeze({

View file

@ -654,7 +654,7 @@ GuiInterface.prototype.GetTemplateData = function(player, data)
const aurasTemplate = {};
if (!template.Auras)
return GetTemplateDataHelper(template, owner, aurasTemplate, Resources);
return g_TemplateHelper.computeDataFromPlayer(template, aurasTemplate, Resources, owner);
const auraNames = template.Auras._string.split(/\s+/);
@ -667,7 +667,7 @@ GuiInterface.prototype.GetTemplateData = function(player, data)
aurasTemplate[name] = auraTemplate;
}
return GetTemplateDataHelper(template, owner, aurasTemplate, Resources);
return g_TemplateHelper.computeDataFromPlayer(template, aurasTemplate, Resources, owner);
};
GuiInterface.prototype.AreRequirementsMet = function(player, data)

View file

@ -139,8 +139,7 @@ AddMock(100, IID_Diplomacy, {
AddMock(100, IID_Identity, {
"GetName": function() { return "Player 1"; },
"GetCiv": function() { return "gaia"; },
"GetRankTechName": function() { return undefined; }
"GetCiv": function() { return "gaia"; }
});
AddMock(100, IID_EntityLimits, {
@ -236,8 +235,7 @@ AddMock(101, IID_Diplomacy, {
AddMock(101, IID_Identity, {
"GetName": function() { return "Player 2"; },
"GetCiv": function() { return "mace"; },
"GetRankTechName": function() { return undefined; }
"GetCiv": function() { return "mace"; }
});
AddMock(101, IID_EntityLimits, {
@ -593,8 +591,7 @@ AddMock(10, IID_Identity, {
"GetSelectionGroupName": function() { return "Selection Group Name"; },
"HasClass": function() { return true; },
"IsUndeletable": function() { return false; },
"IsControllable": function() { return true; },
"GetRankTechName": function() { return undefined; }
"IsControllable": function() { return true; }
});
AddMock(10, IID_Position, {
@ -623,7 +620,6 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), {
"template": "example",
"identity": {
"rank": "foo",
"rankTechName": undefined,
"classes": ["class1", "class2"],
"selectionGroupName": "Selection Group Name",
"canDelete": true,

View file

@ -140,12 +140,12 @@ cmpUpgrade.OnOwnershipChanged({ "to": playerID });
* Now to start the test proper
* To start with, no techs are researched...
*/
// T1: Check the cost of the upgrade without a player value being passed (as it would be in the structree).
let parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
// T1: Check the cost of the upgrade without accounting for any player modifications (as it would be in the structree).
let parsed_template = g_TemplateHelper.getBasicData(template, {}, Resources, ["Upgrade"]);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T2: Check the value, with a player ID (as it would be in-session).
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
// T2: Check the value, this time accounting for player modifiers (as it would be in-session).
parsed_template = g_TemplateHelper.computeDataFromPlayer(template, {}, Resources, playerID, ["Upgrade"]);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T3: Check that the value is correct within the Update Component.
@ -159,11 +159,11 @@ cmpUpgrade.Upgrade("structures/" + civCode + "/defense_tower");
isResearched = true;
// T4: Check that the player-less value hasn't increased...
parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
parsed_template = g_TemplateHelper.getBasicData(template, {}, Resources, ["Upgrade"]);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T5: ...but the player-backed value has.
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
parsed_template = g_TemplateHelper.computeDataFromPlayer(template, {}, Resources, playerID, ["Upgrade"]);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 160, "wood": 25, "time": 90 });
// T6: The upgrade component should still be using the old resource cost (but new time cost) for the upgrade in progress...