2020-07-07 12:11:36 -07:00
|
|
|
/**
|
|
|
|
|
* This class parses and stores parsed template data.
|
|
|
|
|
*/
|
|
|
|
|
class TemplateParser
|
|
|
|
|
{
|
|
|
|
|
constructor(TemplateLoader)
|
|
|
|
|
{
|
|
|
|
|
this.TemplateLoader = TemplateLoader;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parsed Data Stores
|
|
|
|
|
*/
|
2020-12-31 17:55:13 -08:00
|
|
|
this.auras = {};
|
2020-07-07 12:11:36 -07:00
|
|
|
this.entities = {};
|
|
|
|
|
this.techs = {};
|
|
|
|
|
this.phases = {};
|
|
|
|
|
this.modifiers = {};
|
2021-06-05 09:24:25 -07:00
|
|
|
this.players = {};
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
this.phaseList = [];
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 17:55:13 -08:00
|
|
|
getAura(auraName)
|
|
|
|
|
{
|
|
|
|
|
if (auraName in this.auras)
|
|
|
|
|
return this.auras[auraName];
|
|
|
|
|
|
|
|
|
|
if (!AuraTemplateExists(auraName))
|
|
|
|
|
return null;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const template = this.TemplateLoader.loadAuraTemplate(auraName);
|
|
|
|
|
const parsed = GetAuraDataHelper(template);
|
2020-12-31 17:55:13 -08:00
|
|
|
|
|
|
|
|
if (template.civ)
|
|
|
|
|
parsed.civ = template.civ;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const affectedPlayers = template.affectedPlayers || this.AuraAffectedPlayerDefault;
|
2021-06-05 09:24:25 -07:00
|
|
|
parsed.affectsTeam = this.AuraTeamIndicators.some(indicator => affectedPlayers.includes(indicator));
|
2021-06-08 16:35:44 -07:00
|
|
|
parsed.affectsSelf = this.AuraSelfIndicators.some(indicator => affectedPlayers.includes(indicator));
|
2021-06-05 09:24:25 -07:00
|
|
|
|
2020-12-31 17:55:13 -08:00
|
|
|
this.auras[auraName] = parsed;
|
|
|
|
|
return this.auras[auraName];
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-07 12:11:36 -07:00
|
|
|
/**
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
Resolve issue with palisade health in Structure Tree
The "health" of the palisade wallset was not being displayed correctly
in the
Structure Tree for any civ that didn't have the same resultant civbonus
effect
as the first civ shown when the 'Tree was opened.
The issue was caused by caching the parsed form of an entity template
when it is
first requested, then using that for *every* civ.
This wouldn't matter if every civ had completely different entities from
every
other civ, and no civ had civbonus technologies that affect shared
entities.
However, in "vanilla" 0 A.D., the templates of the palisade wallset, and
those
of the sheep, pig, and goat trainables, are shared between multiple
civs. And
the athen, brit, gaul, mace, and spart civs all have civbonus
technologies that
affect structure health.
(And as for mods, who knows...)
The solution provided here is to cache parsed entities by civ, like we
already
do with technologies.
Issue raised by @Nescio in b2842e8021
This was SVN commit r23913.
2020-07-30 17:14:31 -07:00
|
|
|
if (!(civCode in this.entities))
|
|
|
|
|
this.entities[civCode] = {};
|
|
|
|
|
else if (templateName in this.entities[civCode])
|
|
|
|
|
return this.entities[civCode][templateName];
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
if (!Engine.TemplateExists(templateName))
|
|
|
|
|
return null;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);
|
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
|
|
|
const parsed = g_TemplateHelper.computeDataFromModifiers(template, this.TemplateLoader.auraData, g_ResourceData,
|
|
|
|
|
this.modifiers[civCode] || {}, civCode);
|
2020-07-07 12:11:36 -07:00
|
|
|
parsed.name.internal = templateName;
|
|
|
|
|
|
|
|
|
|
parsed.history = template.Identity.History;
|
|
|
|
|
|
2021-11-15 23:08:39 -08:00
|
|
|
parsed.production = this.TemplateLoader.deriveProduction(template, civCode);
|
2020-07-07 12:11:36 -07:00
|
|
|
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.
|
2023-01-05 00:07:57 -08:00
|
|
|
// Complex requirements are too difficult to process for now, so assume the first phase.
|
|
|
|
|
if (!parsed.requirements?.Techs)
|
2020-07-07 12:11:36 -07:00
|
|
|
parsed.phase = this.phaseList[0];
|
|
|
|
|
else
|
2023-01-05 00:07:57 -08:00
|
|
|
{
|
|
|
|
|
let highestPhaseIndex = 0;
|
2023-01-30 00:05:34 -08:00
|
|
|
for (const tech of parsed.requirements.Techs._string.split(" "))
|
2023-01-05 00:07:57 -08:00
|
|
|
{
|
|
|
|
|
if (tech[0] === "!")
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const phaseIndex = this.phaseList.indexOf(
|
|
|
|
|
this.TemplateLoader.isPhaseTech(tech) ? this.getActualPhase(tech) :
|
|
|
|
|
this.getPhaseOfTechnology(tech, civCode));
|
|
|
|
|
if (phaseIndex > highestPhaseIndex)
|
|
|
|
|
highestPhaseIndex = phaseIndex;
|
|
|
|
|
}
|
|
|
|
|
parsed.phase = this.phaseList[highestPhaseIndex];
|
|
|
|
|
}
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
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("."),
|
Decay/Regenerate option for resources.
Allows entities to change their resource amount over time, possibly with
some constraint.
This is a not-so-bare minimum, but can certainly be improved and/or
extended later.
Part of: #1973
Original patch by: @smiley
Redone by: @Stan
Standing on the shoulders of giants: @Freagarach
(Revisions: 59; Inlines: 209)
Differential revision: D1718
Comments by: @Angen, @elexis, @Imarok, @Langbart, @nani, @Nescio,
@smiley, @Stan, @wraitii
This was SVN commit r24963.
2021-02-28 12:14:53 -08:00
|
|
|
"amount": template.ResourceSupply.Max,
|
2020-07-07 12:11:36 -07:00
|
|
|
};
|
|
|
|
|
|
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 (parsed.upgrade)
|
|
|
|
|
parsed.upgrade.options = this.getActualUpgradeData(parsed.upgrade.options, civCode);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
if (parsed.wallSet)
|
|
|
|
|
{
|
|
|
|
|
parsed.wallset = {};
|
|
|
|
|
|
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 (!parsed.upgrade)
|
|
|
|
|
parsed.upgrade = { "options": [] };
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2020-08-27 03:24:59 -07:00
|
|
|
// Note: An assumption is made here that wall segments all have the same resistance and auras
|
2020-07-07 12:11:36 -07:00
|
|
|
let struct = this.getEntity(parsed.wallSet.templates.long, civCode);
|
2020-08-27 03:24:59 -07:00
|
|
|
parsed.resistance = struct.resistance;
|
2020-07-07 12:11:36 -07:00
|
|
|
parsed.auras = struct.auras;
|
|
|
|
|
|
2026-02-12 06:56:03 -08:00
|
|
|
// For technology cost multiplier (in the researcher component), we need to use the tower
|
2020-07-07 12:11:36 -07:00
|
|
|
struct = this.getEntity(parsed.wallSet.templates.tower, civCode);
|
2026-02-12 06:56:03 -08:00
|
|
|
parsed.researcher = struct.researcher;
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2026-02-12 06:56:03 -08:00
|
|
|
let hitpoints;
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const wSegm in parsed.wallSet.templates)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
|
|
|
|
if (wSegm == "fort" || wSegm == "curves")
|
|
|
|
|
continue;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const wPart = this.getEntity(parsed.wallSet.templates[wSegm], civCode);
|
2020-07-07 12:11:36 -07:00
|
|
|
parsed.wallset[wSegm] = wPart;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const research of wPart.production.techs)
|
2020-07-07 12:11:36 -07:00
|
|
|
parsed.production.techs.push(research);
|
|
|
|
|
|
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 (wPart.upgrade)
|
|
|
|
|
Array.prototype.push.apply(parsed.upgrade.options, wPart.upgrade.options);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
if (["gate", "tower"].indexOf(wSegm) != -1)
|
|
|
|
|
continue;
|
|
|
|
|
|
2026-02-12 06:56:03 -08:00
|
|
|
if (!hitpoints)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
2026-02-12 06:56:03 -08:00
|
|
|
hitpoints = { "min": wPart.maxHitpoints, "max": wPart.maxHitpoints };
|
2020-07-07 12:11:36 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 06:56:03 -08:00
|
|
|
hitpoints.min = Math.min(hitpoints.min, wPart.maxHitpoints);
|
|
|
|
|
hitpoints.max = Math.max(hitpoints.max, wPart.maxHitpoints);
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parsed.wallSet.templates.curves)
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const curve of parsed.wallSet.templates.curves)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
2025-05-10 09:38:38 -07:00
|
|
|
const wPart = this.getEntity(curve, civCode);
|
2026-02-12 06:56:03 -08:00
|
|
|
hitpoints.min = Math.min(hitpoints.min, wPart.maxHitpoints);
|
|
|
|
|
hitpoints.max = Math.max(hitpoints.max, wPart.maxHitpoints);
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 06:56:03 -08:00
|
|
|
if (hitpoints.min === hitpoints.max)
|
|
|
|
|
parsed.maxHitpoints = hitpoints.min;
|
2020-07-07 12:11:36 -07:00
|
|
|
else
|
2026-02-12 06:56:03 -08:00
|
|
|
parsed.maxHitpoints = sprintf(translate("%(health_min)s to %(health_max)s"), {
|
|
|
|
|
"health_min": hitpoints.min,
|
|
|
|
|
"health_max": hitpoints.max
|
2020-07-07 12:11:36 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
Resolve issue with palisade health in Structure Tree
The "health" of the palisade wallset was not being displayed correctly
in the
Structure Tree for any civ that didn't have the same resultant civbonus
effect
as the first civ shown when the 'Tree was opened.
The issue was caused by caching the parsed form of an entity template
when it is
first requested, then using that for *every* civ.
This wouldn't matter if every civ had completely different entities from
every
other civ, and no civ had civbonus technologies that affect shared
entities.
However, in "vanilla" 0 A.D., the templates of the palisade wallset, and
those
of the sheep, pig, and goat trainables, are shared between multiple
civs. And
the athen, brit, gaul, mace, and spart civs all have civbonus
technologies that
affect structure health.
(And as for mods, who knows...)
The solution provided here is to cache parsed entities by civ, like we
already
do with technologies.
Issue raised by @Nescio in b2842e8021
This was SVN commit r23913.
2020-07-30 17:14:31 -07:00
|
|
|
this.entities[civCode][templateName] = parsed;
|
2020-07-07 12:11:36 -07:00
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load and parse technology from json template.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} technologyName
|
|
|
|
|
* @param {string} civCode
|
2020-08-24 04:01:25 -07:00
|
|
|
* @return {Object} Sanitized data about the requested technology.
|
2020-07-07 12:11:36 -07:00
|
|
|
*/
|
|
|
|
|
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];
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const template = this.TemplateLoader.loadTechnologyTemplate(technologyName);
|
2022-08-10 14:49:15 -07:00
|
|
|
const tech = GetTechnologyDataHelper(template, civCode, g_ResourceData, this.modifiers[civCode] || {});
|
2020-07-07 12:11:36 -07:00
|
|
|
tech.name.internal = technologyName;
|
|
|
|
|
|
2026-05-23 14:27:31 -07:00
|
|
|
if (template.partOfPair !== undefined)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
2026-05-23 14:27:31 -07:00
|
|
|
tech.partOfPair = template.partOfPair;
|
|
|
|
|
tech.reqs = this.mergeRequirements(tech.reqs, this.TemplateLoader.loadTechnologyPairTemplate(template.partOfPair).reqs);
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2020-08-24 04:01:25 -07:00
|
|
|
* @return {Object} Sanitized object containing phase data
|
2020-07-07 12:11:36 -07:00
|
|
|
*/
|
|
|
|
|
getPhase(phaseCode, civCode)
|
|
|
|
|
{
|
|
|
|
|
return this.getTechnology(phaseCode, civCode);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-05 09:24:25 -07:00
|
|
|
/**
|
|
|
|
|
* Load and parse the relevant player_{civ}.xml template.
|
|
|
|
|
*/
|
|
|
|
|
getPlayer(civCode)
|
|
|
|
|
{
|
|
|
|
|
if (civCode in this.players)
|
|
|
|
|
return this.players[civCode];
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const template = this.TemplateLoader.loadPlayerTemplate(civCode);
|
|
|
|
|
const parsed = {
|
2021-06-05 09:39:59 -07:00
|
|
|
"civbonuses": [],
|
2021-06-05 09:24:25 -07:00
|
|
|
"teambonuses": [],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (template.Auras)
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const auraTemplateName of template.Auras._string.split(/\s+/))
|
2021-06-05 09:39:59 -07:00
|
|
|
if (AuraTemplateExists(auraTemplateName))
|
|
|
|
|
if (this.getAura(auraTemplateName).affectsTeam)
|
|
|
|
|
parsed.teambonuses.push(auraTemplateName);
|
|
|
|
|
else
|
|
|
|
|
parsed.civbonuses.push(auraTemplateName);
|
2021-06-05 09:24:25 -07:00
|
|
|
|
|
|
|
|
this.players[civCode] = parsed;
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-07 12:11:36 -07:00
|
|
|
/**
|
|
|
|
|
* Provided with an array containing basic information about possible
|
2026-02-12 06:56:03 -08:00
|
|
|
* upgrades, such as that generated by globalscript's g_TemplateHelper,
|
2020-07-07 12:11:36 -07:00
|
|
|
* 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)
|
|
|
|
|
{
|
2025-05-10 09:38:38 -07:00
|
|
|
const newUpgrades = [];
|
|
|
|
|
for (const upgrade of upgradesInfo)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
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
|
|
|
const data = g_TemplateHelper.computeDataFromModifiers(this.TemplateLoader.loadEntityTemplate(upgrade.entity, civCode),
|
|
|
|
|
this.TemplateLoader.auraData, g_ResourceData, this.modifiers[civCode] || {}, civCode);
|
2020-07-07 12:11:36 -07:00
|
|
|
data.name.internal = upgrade.entity;
|
|
|
|
|
data.cost = upgrade.cost;
|
|
|
|
|
data.icon = upgrade.icon || data.icon;
|
|
|
|
|
data.tooltip = upgrade.tooltip || data.tooltip;
|
2022-11-24 03:20:11 -08:00
|
|
|
data.requirements = upgrade.requirements || data.requirements;
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2023-01-30 00:05:34 -08:00
|
|
|
if (!data.requirements?.Techs)
|
2020-07-07 12:11:36 -07:00
|
|
|
data.phase = this.phaseList[0];
|
|
|
|
|
else
|
2023-01-30 00:05:34 -08:00
|
|
|
{
|
|
|
|
|
let highestPhaseIndex = 0;
|
|
|
|
|
for (const tech of data.requirements.Techs._string.split(" "))
|
|
|
|
|
{
|
|
|
|
|
if (tech[0] === "!")
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const phaseIndex = this.phaseList.indexOf(
|
|
|
|
|
this.TemplateLoader.isPhaseTech(tech) ? this.getActualPhase(tech) :
|
|
|
|
|
this.getPhaseOfTechnology(tech, civCode));
|
|
|
|
|
if (phaseIndex > highestPhaseIndex)
|
|
|
|
|
highestPhaseIndex = phaseIndex;
|
|
|
|
|
}
|
|
|
|
|
data.phase = this.phaseList[highestPhaseIndex];
|
|
|
|
|
}
|
2020-07-07 12:11:36 -07:00
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const techReqs = this.getTechnology(techName, civCode).reqs;
|
2020-07-07 12:11:36 -07:00
|
|
|
if (!techReqs)
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const option of techReqs)
|
2020-07-07 12:11:36 -07:00
|
|
|
if (option.techs)
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const tech of option.techs)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2021-06-08 16:35:44 -07:00
|
|
|
const player = this.getPlayer(civCode);
|
|
|
|
|
const auraList = clone(player.civbonuses);
|
|
|
|
|
for (const bonusname of player.teambonuses)
|
|
|
|
|
if (this.getAura(bonusname).affectsSelf)
|
|
|
|
|
auraList.push(bonusname);
|
|
|
|
|
|
|
|
|
|
this.modifiers[civCode] = this.TemplateLoader.deriveModifications(civCode, auraList);
|
2020-07-07 12:11:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
derivePhaseList(technologyList, civCode)
|
|
|
|
|
{
|
2024-12-25 01:47:38 -08:00
|
|
|
// Wipe all cached phase data. It contains the previous civ's SpecificNames which wouldn't be reloaded otherwise.
|
|
|
|
|
this.phases = {};
|
|
|
|
|
|
2020-07-07 12:11:36 -07:00
|
|
|
// Load all of a civ's specific phase technologies
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const techcode of technologyList)
|
2020-07-07 12:11:36 -07:00
|
|
|
if (this.TemplateLoader.isPhaseTech(techcode))
|
|
|
|
|
this.getTechnology(techcode, civCode);
|
|
|
|
|
|
|
|
|
|
this.phaseList = UnravelPhases(this.phases);
|
|
|
|
|
|
|
|
|
|
// Make sure all required generic phases are loaded and parsed
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const phasecode of this.phaseList)
|
2020-07-07 12:11:36 -07:00
|
|
|
this.getTechnology(phasecode, civCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mergeRequirements(reqsA, reqsB)
|
|
|
|
|
{
|
|
|
|
|
if (!reqsA || !reqsB)
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
const finalReqs = clone(reqsA);
|
2020-07-07 12:11:36 -07:00
|
|
|
|
2025-05-10 09:38:38 -07:00
|
|
|
for (const option of reqsB)
|
|
|
|
|
for (const type in option)
|
|
|
|
|
for (const opt in finalReqs)
|
2020-07-07 12:11:36 -07:00
|
|
|
{
|
|
|
|
|
if (!finalReqs[opt][type])
|
|
|
|
|
finalReqs[opt][type] = [];
|
|
|
|
|
Array.prototype.push.apply(finalReqs[opt][type], option[type]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return finalReqs;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-05 09:24:25 -07:00
|
|
|
|
|
|
|
|
// Default affected player token list to use if an aura doesn't explicitly give one.
|
|
|
|
|
// Keep in sync with simulation/components/Auras.js
|
|
|
|
|
TemplateParser.prototype.AuraAffectedPlayerDefault =
|
|
|
|
|
["Player"];
|
|
|
|
|
|
|
|
|
|
// List of tokens that, if found in an aura's "affectedPlayers" attribute, indicate
|
|
|
|
|
// that the aura applies to team members.
|
|
|
|
|
TemplateParser.prototype.AuraTeamIndicators =
|
|
|
|
|
["MutualAlly", "ExclusiveMutualAlly"];
|
2021-06-08 16:35:44 -07:00
|
|
|
|
|
|
|
|
// List of tokens that, if found in an aura's "affectedPlayers" attribute, indicate
|
|
|
|
|
// that the aura applies to the aura's owning civ.
|
|
|
|
|
TemplateParser.prototype.AuraSelfIndicators =
|
|
|
|
|
["Player", "Ally", "MutualAlly"];
|