Define technology pairs in an array

This patch renames the "pair" property of paired technologies to
"partOfPair" for clarity and in the pair parent packs the two techs
(previously "top" and "bottom") into an array called "pair".
This makes the hierarchy more clear in the code and pairs might not be
always placed vertically in the future.
This commit is contained in:
Vantha 2026-05-23 23:27:31 +02:00
parent 3922a7ee0a
commit 107a49caf1
23 changed files with 51 additions and 83 deletions

View file

@ -132,7 +132,7 @@ class TemplateLoader
{
const template = this.loadTechnologyTemplate(templateName);
return {
"techs": [template.top, template.bottom],
"techs": template.pair,
"reqs": DeriveTechnologyRequirements(template, civCode)
};
}
@ -308,7 +308,7 @@ class TemplateLoader
isPairTech(technologyCode)
{
return !!this.loadTechnologyTemplate(technologyCode).top;
return !!this.loadTechnologyTemplate(technologyCode).pair;
}
isPhaseTech(technologyCode)

View file

@ -196,10 +196,10 @@ class TemplateParser
const tech = GetTechnologyDataHelper(template, civCode, g_ResourceData, this.modifiers[civCode] || {});
tech.name.internal = technologyName;
if (template.pair !== undefined)
if (template.partOfPair !== undefined)
{
tech.pair = template.pair;
tech.reqs = this.mergeRequirements(tech.reqs, this.TemplateLoader.loadTechnologyPairTemplate(template.pair).reqs);
tech.partOfPair = template.partOfPair;
tech.reqs = this.mergeRequirements(tech.reqs, this.TemplateLoader.loadTechnologyPairTemplate(template.partOfPair).reqs);
}
if (this.TemplateLoader.isPhaseTech(technologyName))

View file

@ -684,8 +684,8 @@ g_SelectionPanels.Research = {
(item.tech == tech ||
item.tech.pair &&
tech.pair &&
item.tech.bottom == tech.bottom &&
item.tech.top == tech.top) &&
item.tech.pair?.[0] == tech.pair?.[0] &&
item.tech.pair?.[1] == tech.pair?.[1]) &&
Object.keys(item.techCostMultiplier).every(
k => item.techCostMultiplier[k] == state.researcher.techCostMultiplier[k])
));
@ -731,7 +731,7 @@ g_SelectionPanels.Research = {
// Handle one or two techs (tech pair)
const player = data.player;
const playerState = GetSimState().players[player];
for (const tech of data.item.tech.pair ? [data.item.tech.bottom, data.item.tech.top] : [data.item.tech])
for (const tech of (data.item.tech.pair || [data.item.tech]))
{
// Don't change the object returned by GetTechnologyData
const template = clone(GetTechnologyData(tech, playerState.civ));

View file

@ -48,8 +48,8 @@ GameState.prototype.init = function(SharedScript, state, player)
{
// Cannot call pickrandom because this function is called on rejoin and that causes oos.
// (reverting rP20750)
techName = this.playerData.disabledTechnologies[techData._template.bottom] ?
techData._template.top : techData._template.bottom;
techName = this.playerData.disabledTechnologies[techData._template.pair[0]] ?
techData._template.pair[1] : techData._template.pair[0];
const supersedes = techData._template.supersedes;
techData = clone(this.getTemplate(techName));

View file

@ -7,15 +7,14 @@ export function Technology(templateName)
const template = TechnologyTemplates.Get(templateName);
// check if this is one of two paired technologies.
this._isPair = template.pair !== undefined;
if (this._isPair)
if (template.partOfPair)
{
const pairTech = TechnologyTemplates.Get(template.pair);
this._pairedWith = pairTech.top == templateName ? pairTech.bottom : pairTech.top;
const parentTech = TechnologyTemplates.Get(template.partOfPair);
this._pairedWith = parentTech.pair[0] == templateName ? parentTech.pair[1] : parentTech.pair[0];
}
// check if it only defines a pair:
this._definesPair = template.top !== undefined;
this._definesPair = !!template.pair;
this._template = template;
}
@ -41,22 +40,17 @@ Technology.prototype.getPairedTechs = function()
if (!this._definesPair)
return undefined;
return [
new Technology(this._template.top),
new Technology(this._template.bottom)
];
return this._template.pair.map(name => new Technology(name));
};
Technology.prototype.pair = function()
{
if (!this._isPair)
return undefined;
return this._template.pair;
return this._template.partOfPair;
};
Technology.prototype.pairedWith = function()
{
if (!this._isPair)
if (!this._template.partOfPair)
return undefined;
return this._pairedWith;
};

View file

@ -234,36 +234,22 @@ Researcher.prototype.GetTechnologiesList = function()
superseded[template.supersedes] = tech;
}
// Now make researched/in progress techs invisible.
for (const i in techList)
return techList.map(tech =>
{
let tech = techList[i];
while (this.IsTechnologyResearchedOrInProgress(tech))
tech = superseded[tech];
// Make researched/in progress techs invisible.
while (tech && this.IsTechnologyResearchedOrInProgress(tech))
tech = superseded[tech]; // might be undefined
techList[i] = tech;
}
const ret = [];
// This inserts the techs into the correct positions to line up the technology pairs.
for (let i = 0; i < techList.length; ++i)
{
const tech = techList[i];
if (!tech)
{
ret[i] = undefined;
continue;
}
return undefined;
// "Unwrap" tech pairs
const template = TechnologyTemplates.Get(tech);
if (template.top)
ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
else
ret[i] = tech;
}
if (template?.pair)
tech = { "pair": template.pair };
return ret;
return tech;
});
};
/**
@ -294,11 +280,9 @@ Researcher.prototype.IsTechnologyResearchedOrInProgress = function(tech)
return false;
const template = TechnologyTemplates.Get(tech);
if (template.top)
return cmpTechnologyManager.IsTechnologyResearched(template.top) ||
cmpTechnologyManager.IsInProgress(template.top) ||
cmpTechnologyManager.IsTechnologyResearched(template.bottom) ||
cmpTechnologyManager.IsInProgress(template.bottom);
if (template.pair)
return template.pair.some(t => cmpTechnologyManager.IsTechnologyResearched(t) ||
cmpTechnologyManager.IsInProgress(t));
return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech);
};
@ -310,9 +294,7 @@ Researcher.prototype.IsTechnologyResearchedOrInProgress = function(tech)
*/
Researcher.prototype.QueueTechnology = function(templateName, metadata)
{
if (!this.GetTechnologiesList().some(tech =>
tech && (tech == templateName ||
tech.pair && (tech.top == templateName || tech.bottom == templateName))))
if (!this.GetTechnologiesList().some(tech => tech === templateName || tech?.pair?.includes(templateName)))
{
error("This entity cannot research " + templateName + ".");
return -1;

View file

@ -212,7 +212,7 @@ TechnologyManager.prototype.Init = function()
this.unresearchedAutoResearchTechs = new Set();
const allTechs = TechnologyTemplates.GetAll();
for (const key in allTechs)
if (allTechs[key].autoResearch || allTechs[key].top)
if (allTechs[key].autoResearch || allTechs[key].pair)
this.unresearchedAutoResearchTechs.add(key);
};
@ -263,8 +263,7 @@ TechnologyManager.prototype.UpdateAutoResearch = function()
for (const key of this.unresearchedAutoResearchTechs)
{
const tech = TechnologyTemplates.Get(key);
if ((tech.autoResearch && this.CanResearch(key)) ||
(tech.top && (this.IsTechnologyResearched(tech.top) || this.IsTechnologyResearched(tech.bottom))))
if ((tech.autoResearch && this.CanResearch(key)) || (tech.pair?.some(t => this.IsTechnologyResearched(t))))
{
this.unresearchedAutoResearchTechs.delete(key);
this.ResearchTechnology(key);
@ -306,11 +305,10 @@ TechnologyManager.prototype.CanResearch = function(tech)
return false;
}
if (template.top && this.IsInProgress(template.top) ||
template.bottom && this.IsInProgress(template.bottom))
if (template.pair?.some(t => this.IsInProgress(t)))
return false;
if (template.pair && !this.CanResearch(template.pair))
if (template.partOfPair && !this.CanResearch(template.partOfPair))
return false;
if (this.IsInProgress(tech))

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_civil_engineering_han",
"partOfPair": "pair_unlock_civil_engineering_han",
"genericName": "Improved Construction",
"specificName": { "han": "Gōngchéng" },
"description": "",

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_civil_engineering_han",
"partOfPair": "pair_unlock_civil_engineering_han",
"genericName": "Robust Architecture",
"specificName": { "han": "Gōngchéng" },
"description": "",

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_civil_service_han",
"partOfPair": "pair_unlock_civil_service_han",
"genericName": "Efficient Bureaucracy",
"specificName": { "han": "Guānliáo" },
"description": "",

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_civil_service_han",
"partOfPair": "pair_unlock_civil_service_han",
"genericName": "Intensive Training",
"specificName": { "han": "Guānliáo" },
"description": "",

View file

@ -1,5 +1,5 @@
{
"pair": "pair_gather_food_maur",
"partOfPair": "pair_gather_food_maur",
"genericName": "Ahimsa",
"description": "Ahimsa is the ancient Indian principle of nonviolence which applies to actions towards all living beings. It is a key virtue in Indian religions like Jainism, Buddhism, Hinduism, and Sikhism.",
"cost": {

View file

@ -1,5 +1,5 @@
{
"pair": "pair_gather_food_maur",
"partOfPair": "pair_gather_food_maur",
"genericName": "Wicker Baskets",
"description": "Equip your foragers with wicker baskets for foraging.",
"cost": {

View file

@ -1,6 +1,5 @@
{
"genericName": "Wicker Basket vs Ahimsa",
"top": "gather_wicker_baskets_maur",
"bottom": "gather_ahimsa",
"pair": ["gather_wicker_baskets_maur", "gather_ahimsa"],
"requirements": { "civ": "maur" }
}

View file

@ -1,6 +1,5 @@
{
"genericName": "Traditional Army vs Reform Army",
"top": "traditional_army_sele",
"bottom": "reformed_army_sele",
"pair": ["traditional_army_sele", "reformed_army_sele"],
"requirements": { "civ": "sele" }
}

View file

@ -1,6 +1,5 @@
{
"genericName": "Civil Engineering",
"top": "civil_engineering_01",
"bottom": "civil_engineering_02",
"pair": ["civil_engineering_01", "civil_engineering_02"],
"requirements": { "civ": "han" }
}

View file

@ -1,6 +1,5 @@
{
"genericName": "Civil Service",
"top": "civil_service_01",
"bottom": "civil_service_02",
"pair": ["civil_service_01", "civil_service_02"],
"requirements": { "civ": "han" }
}

View file

@ -1,6 +1,5 @@
{
"genericName": "Cult",
"top": "pharaonic_cult",
"bottom": "serapis_cult",
"pair": ["pharaonic_cult", "serapis_cult"],
"requirements": { "civ": "ptol" }
}

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_cult_ptol",
"partOfPair": "pair_unlock_cult_ptol",
"genericName": "Pharaonic Cult",
"description": "The Pharaohs were worshipped as living gods. Their word was sacrosanct and beyond reproach, at least among the common people. The Ptolemaic dynasts certainly took advantage of this ancient custom to the fullest, to varying degrees of success.",
"cost": {

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_champions_sele",
"partOfPair": "pair_unlock_champions_sele",
"genericName": "Reform Army",
"description": "The Roman-style core of the Seleucid army.",
"requirements": {

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_cult_ptol",
"partOfPair": "pair_unlock_cult_ptol",
"genericName": "Serapis Cult",
"description": "The cult of Serapis was introduced during the 3rd century BC on the orders of Ptolemy I of Egypt as a means to unify the Greeks and Egyptians in his realm. The god was depicted as Greek in appearance, but with Egyptian trappings, and combined iconography from a great many cults, signifying both abundance and resurrection. A serapeion was any temple or religious precinct devoted to Serapis. The cult of Serapis was spread as a matter of deliberate policy by the Ptolemaic kings, who also built an immense serapeum in Alexandria.",
"cost": {

View file

@ -1,5 +1,5 @@
{
"pair": "pair_unlock_champions_sele",
"partOfPair": "pair_unlock_champions_sele",
"genericName": "Traditional Army",
"description": "The Macedonian-style core of the Seleucid army.",
"requirements": {

View file

@ -151,7 +151,6 @@ function Cheat(input)
// try to spilt the input
const tmp = input.parameter.split(/\s+/);
const number = +tmp[0];
const pair = tmp.length > 1 && (tmp[1] == "top" || tmp[1] == "bottom") ? tmp[1] : "top"; // use top as default value
// check, if valid number was parsed.
if (!isNaN(number))
@ -166,7 +165,7 @@ function Cheat(input)
// get name of tech
if (tech.pair)
techname = tech[pair];
techname = tech.pair[tmp[1] === "1" ? 1 : 0];
else
techname = tech;
}