1
0
forked from mirrors/0ad

Merge different logics of technology requirements and fix the related bugs. Create a global script which derive the technology requirements objects in a form usable for structure tree, gui, simulation and AI. Reviewed by mimo (for AI) and fatherbushido. Fixes #3993, #1646, #4263, #4217. Refs #4108.

This was SVN commit r19120.
This commit is contained in:
fatherbushido
2017-01-08 14:00:20 +00:00
parent b5991e188b
commit 6c97d8118b
23 changed files with 1069 additions and 421 deletions
@@ -50,3 +50,238 @@ function DoesModificationApply(modification, classes)
{
return MatchesClassList(classes, modification.affects);
}
/**
* Derives the technology requirements from a given technology template.
* Takes into account the `supersedes` attribute.
*
* @param {object} template - The template object. Loading of the template must have already occured.
*
* @return Derived technology requirements. See `InterpretTechRequirements` for object's syntax.
*/
function DeriveTechnologyRequirements(template, civ)
{
let requirements = [];
if (template.requirements)
{
let op = Object.keys(template.requirements)[0];
let val = template.requirements[op];
requirements = InterpretTechRequirements(civ, op, val);
}
if (template.supersedes && requirements)
{
if (!requirements.length)
requirements.push({});
for (let req of requirements)
{
if (!req.techs)
req.techs = [];
req.techs.push(template.supersedes);
}
}
return requirements;
}
/**
* Interprets the prerequisite requirements of a technology.
*
* Takes the initial { key: value } from the short-form requirements object in entity templates,
* and parses it into an object that can be more easily checked by simulation and gui.
*
* Works recursively if needed.
*
* The returned object is in the form:
* ```
* { "techs": ["tech1", "tech2"] },
* { "techs": ["tech3"] }
* ```
* or
* ```
* { "entities": [[{
* "class": "human",
* "number": 2,
* "check": "count"
* }
* or
* ```
* false;
* ```
* (Or, to translate:
* 1. need either both `tech1` and `tech2`, or `tech3`
* 2. need 2 entities with the `human` class
* 3. cannot research this tech at all)
*
* @param {string} civ - The civ code
* @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
* @param {mixed} value - The value associated with the above operation.
*
* @return Object containing the requirements for the given civ, or false if the civ cannot research the tech.
*/
function InterpretTechRequirements(civ, operator, value)
{
let requirements = [];
switch (operator)
{
case "civ":
return !civ || civ == value ? [] : false;
case "notciv":
return civ == value ? false : [];
case "entity":
{
let number = value.number || value.numberOfTypes || 0;
if (number > 0)
requirements.push({
"entities": [{
"class": value.class,
"number": number,
"check": value.number ? "count" : "variants"
}]
});
break;
}
case "tech":
requirements.push({
"techs": [value]
});
break;
case "all":
{
let civPermitted = undefined; // tri-state (undefined, false, or true)
for (let subvalue of value)
{
let newOper = Object.keys(subvalue)[0];
let newValue = subvalue[newOper];
let result = InterpretTechRequirements(civ, newOper, newValue);
switch (newOper)
{
case "civ":
if (result)
civPermitted = true;
else if (civPermitted !== true)
civPermitted = false;
break;
case "notciv":
if (!result)
return false;
break;
case "any":
if (!result)
return false;
// else, fall through
case "all":
if (!result)
{
let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
if (!nullcivreqs || !nullcivreqs.length)
civPermitted = false;
continue;
}
// else, fall through
case "tech":
case "entity":
{
if (result.length)
{
if (!requirements.length)
requirements.push({});
let newRequirements = [];
for (let currReq of requirements)
for (let res of result)
{
let newReq = {}
for (let subtype in currReq)
newReq[subtype] = currReq[subtype];
for (let subtype in res)
{
if (!newReq[subtype])
newReq[subtype] = [];
newReq[subtype] = newReq[subtype].concat(res[subtype]);
}
newRequirements.push(newReq);
}
requirements = newRequirements;
}
break;
}
}
}
if (civPermitted === false) // if and only if false
return false;
break;
}
case "any":
{
let civPermitted = false;
for (let subvalue of value)
{
let newOper = Object.keys(subvalue)[0];
let newValue = subvalue[newOper];
let result = InterpretTechRequirements(civ, newOper, newValue);
switch (newOper)
{
case "civ":
if (result)
return [];
break;
case "notciv":
if (!result)
return false;
civPermitted = true;
break;
case "any":
if (!result)
{
let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
if (!nullcivreqs || !nullcivreqs.length)
continue;
return false;
}
// else, fall through
case "all":
if (!result)
continue;
civPermitted = true;
// else, fall through
case "tech":
case "entity":
for (let res of result)
requirements.push(res);
break;
}
}
if (!civPermitted && !requirements.length)
return false;
break;
}
default:
warn("Unknown requirement operator: "+operator);
}
return requirements;
}
@@ -351,17 +351,12 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources)
*/
function GetTechnologyDataHelper(template, civ, resources)
{
var ret = {};
let ret = {};
// Get specific name for this civ or else the generic specific name
var specific;
let specific;
if (template.specificName)
{
if (template.specificName[civ])
specific = template.specificName[civ];
else
specific = template.specificName['generic'];
}
specific = template.specificName[civ] || template.specificName.generic;
ret.name = {
"specific": specific,
@@ -377,19 +372,7 @@ function GetTechnologyDataHelper(template, civ, resources)
ret.tooltip = template.tooltip;
ret.requirementsTooltip = template.requirementsTooltip || "";
// TODO: This doesn't handle all types of requirements
if (template.requirements && template.requirements.class)
ret.classRequirements = {
"class": template.requirements.class,
"number": template.requirements.number
};
else if (template.requirements && template.requirements.all)
for (let req of template.requirements.all)
if (req.class)
ret.classRequirements = {
"class": req.class,
"number": req.number
};
ret.reqs = DeriveTechnologyRequirements(template, civ);
ret.description = template.description;
@@ -34,3 +34,13 @@ function shuffleArray(source)
}
return result;
}
/**
* Removes prefixing path from a path or filename, leaving just the file's name (with extension)
*
* ie. a/b/c/file.ext -> file.ext
*/
function basename(path)
{
return path.slice(path.lastIndexOf("/") + 1);
}
@@ -459,13 +459,13 @@ function getEntityCostTooltip(template, trainNum, entity)
return "";
}
function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology)
function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology, civ)
{
if (technologyEnabled)
return "";
return sprintf(translate("Requires %(technology)s"), {
"technology": getEntityNames(GetTechnologyData(requiredTechnology))
"technology": getEntityNames(GetTechnologyData(requiredTechnology, civ))
});
}
@@ -350,7 +350,7 @@ g_SelectionPanels.Construction = {
let limits = getEntityLimitAndCount(data.playerState, data.item);
tooltips.push(
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
@@ -794,12 +794,14 @@ g_SelectionPanels.Research = {
setPanelObjectPosition(pair, data.i, data.rowLength);
// Handle one or two techs (tech pair)
let player = data.player;
for (let i in techs)
{
let tech = techs[i];
let playerState = GetSimState().players[player];
// Don't change the object returned by GetTechnologyData
let template = clone(GetTechnologyData(tech));
let template = clone(GetTechnologyData(tech, playerState.civ));
if (!template)
return false;
@@ -808,12 +810,12 @@ g_SelectionPanels.Research = {
let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": template.cost,
"player": data.player
"player": player
});
let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
"tech": tech,
"player": data.player
"player": player
});
let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
@@ -828,13 +830,40 @@ g_SelectionPanels.Research = {
if (!requirementsPassed)
{
let tip = template.requirementsTooltip;
if (template.classRequirements)
let reqs = template.reqs;
for (let req of reqs)
{
let player = data.player;
let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0;
let remaining = template.classRequirements.number - current;
tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), {
"number": remaining
if (!req.entities)
continue;
let entityCounts = [];
for (let entity of req.entities)
{
let current = 0;
switch (entity.check)
{
case "count":
current = playerState.classCounts[entity.class] || 0;
break;
case "variants":
current = playerState.typeCountsByClass[entity.class] ?
Object.keys(playerState.typeCountsByClass[entity.class]).length : 0;
break;
}
let remaining = entity.number - current;
if (remaining < 1)
continue;
entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
"number": remaining,
"class": entity.class
}));
}
tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
"entityCounts": entityCounts.join(translate(", "))
});
}
tooltips.push(tip);
@@ -1064,7 +1093,7 @@ g_SelectionPanels.Training = {
"[color=\"" + g_HotkeyColor + "\"]" +
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) +
"[/color]",
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
@@ -1154,7 +1183,7 @@ g_SelectionPanels.Upgrade = {
tooltips.push(
getEntityCostTooltip(data.item),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
tooltip = tooltips.filter(tip => tip).join("\n");
@@ -222,16 +222,19 @@ function GetTemplateDataWithoutLocalization(templateName)
return g_TemplateDataWithoutLocalization[templateName];
}
function GetTechnologyData(technologyName)
function GetTechnologyData(technologyName, civ)
{
if (!(technologyName in g_TechnologyData))
if (!g_TechnologyData[civ])
g_TechnologyData[civ] = {};
if (!(technologyName in g_TechnologyData[civ]))
{
let template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
let template = Engine.GuiInterfaceCall("GetTechnologyData", { "name": technologyName, "civ": civ });
translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
g_TechnologyData[technologyName] = template;
g_TechnologyData[civ][technologyName] = template;
}
return g_TechnologyData[technologyName];
return g_TechnologyData[civ][technologyName];
}
function init(initData, hotloadData)
@@ -97,9 +97,9 @@ function draw()
if (stru.production.technology[prod_pha])
for (let prod of stru.production.technology[prod_pha])
{
prod = clone(depath(prod).slice(0,5) == "phase" ?
prod = clone(basename(prod).slice(0,5) == "phase" ?
g_ParsedData.phases[prod] :
g_ParsedData.techs[prod]);
g_ParsedData.techs[g_SelectedCiv][prod]);
for (let res in stru.techCostMultiplier)
if (prod.cost[res])
@@ -183,7 +183,7 @@ function draw()
prod = g_ParsedData.units[prod];
break;
case "techs":
prod = clone(g_ParsedData.techs[prod]);
prod = clone(g_ParsedData.techs[civCode][prod]);
for (let res in trainer.techCostMultiplier)
if (prod.cost[res])
prod.cost[res] *= trainer.techCostMultiplier[res];
@@ -48,11 +48,6 @@ function loadAuraData(templateName)
return g_AuraData[templateName];
}
function depath(path)
{
return path.slice(path.lastIndexOf("/") + 1);
}
/**
* This is needed because getEntityCostTooltip in tooltip.js needs to get
* the template data of the different wallSet pieces. In the session this
@@ -63,3 +58,60 @@ function GetTemplateData(templateName)
var template = loadTemplate(templateName);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
}
/**
* 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).slice(0, 5) === "phase")
{
phaseIdx = g_ParsedData.phaseList.indexOf(GetActualPhase(techName));
if (phaseIdx > 0)
return g_ParsedData.phaseList[phaseIdx - 1];
}
if (!g_ParsedData.techs[g_SelectedCiv][techName])
warn(g_SelectedCiv + " : " + techName);
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).slice(0, 5) === "phase")
return tech;
phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(tech)));
}
return g_ParsedData.phaseList[phaseIdx] || false;
}
function GetActualPhase(phaseName)
{
if (g_ParsedData.phases[phaseName])
return g_ParsedData.phases[phaseName].actualPhase;
warn("Unrecognised phase (" + techName + ")");
return g_ParsedData.phaseList[0];
}
function GetPhaseOfTemplate(template)
{
if (!template.requiredTechnology)
return g_ParsedData.phaseList[0];
if (basename(template.requiredTechnology).slice(0, 5) == "phase")
return GetActualPhase(template.requiredTechnology);
return GetPhaseOfTechnology(template.requiredTechnology);
}
+14 -154
View File
@@ -2,18 +2,9 @@ function loadUnit(templateName)
{
if (!Engine.TemplateExists(templateName))
return null;
var template = loadTemplate(templateName);
var unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
unit.phase = false;
if (unit.requiredTechnology)
{
if (depath(unit.requiredTechnology).slice(0, 5) == "phase")
unit.phase = unit.requiredTechnology;
else if (unit.requiredTechnology.length)
unit.required = unit.requiredTechnology;
}
let template = loadTemplate(templateName);
let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
if (template.ProductionQueue)
{
@@ -54,17 +45,8 @@ function loadUnit(templateName)
function loadStructure(templateName)
{
var template = loadTemplate(templateName);
var structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
structure.phase = false;
if (structure.requiredTechnology)
{
if (depath(structure.requiredTechnology).slice(0, 5) == "phase")
structure.phase = structure.requiredTechnology;
else if (structure.requiredTechnology.length)
structure.required = structure.requiredTechnology;
}
let template = loadTemplate(templateName);
let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
structure.production = {
"technology": [],
@@ -140,64 +122,12 @@ function loadStructure(templateName)
function loadTechnology(techName)
{
var template = loadTechData(techName);
var tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
tech.reqs = {};
let template = loadTechData(techName);
let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
if (template.pair !== undefined)
tech.pair = template.pair;
if (template.requirements !== undefined)
{
for (let op in template.requirements)
{
let val = template.requirements[op];
let req = calcReqs(op, val);
switch (op)
{
case "tech":
tech.reqs.generic = req;
break;
case "civ":
tech.reqs[req] = [];
break;
case "any":
if (req[0].length > 0)
for (let r of req[0])
{
let v = req[0][r];
if (typeof r == "number")
tech.reqs[v] = [];
else
tech.reqs[r] = v;
}
if (req[1].length > 0)
tech.reqs.generic = req[1];
break;
case "all":
if (!req[0].length)
tech.reqs.generic = req[1];
else
for (let r of req[0])
tech.reqs[r] = req[1];
break;
}
}
}
if (template.supersedes !== undefined)
{
if (tech.reqs.generic !== undefined)
tech.reqs.generic.push(template.supersedes);
else
for (let ck of Object.keys(tech.reqs))
tech.reqs[ck].push(template.supersedes);
}
return tech;
}
@@ -219,76 +149,10 @@ function loadTechnologyPair(pairCode)
return {
"techs": [ pairInfo.top, pairInfo.bottom ],
"req": pairInfo.supersedes || ""
"reqs": DeriveTechnologyRequirements(pairInfo, g_SelectedCiv)
};
}
/**
* Calculate the prerequisite requirements of a technology.
* Works recursively if needed.
*
* @param op The base operation. Can be "civ", "tech", "all" or "any".
* @param val The value associated with the above operation.
*
* @return Sorted requirments.
*/
function calcReqs(op, val)
{
switch (op)
{
case "civ":
case "class":
case "notciv":
case "number":
// nothing needs doing
break;
case "tech":
if (depath(val).slice(0,4) === "pair")
return loadTechnologyPair(val).techs;
return [ val ];
case "all":
case "any":
let t = [];
let c = [];
for (let nv of val)
{
for (let o in nv)
{
let v = nv[o];
let r = calcReqs(o, v);
switch (o)
{
case "civ":
case "notciv":
c.push(r);
break;
case "tech":
t = t.concat(r);
break;
case "any":
c = c.concat(r[0]);
t = t.concat(r[1]);
break;
case "all":
for (let ci in r[0])
c[ci] = r[1];
t = t;
}
}
}
return [ c, t ];
default:
warn("Unknown reqs operator: "+op);
}
return val;
}
/**
* Unravel phases
*
@@ -304,28 +168,24 @@ function unravelPhases(techs)
{
let techdata = techs[techcode];
if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2)
if (!techdata.reqs || !techdata.reqs.length || !techdata.reqs[0].techs || techdata.reqs[0].techs.length < 2)
continue;
let reqTech = techs[techcode].reqs.generic[1];
let reqTech = techs[techcode].reqs[0].techs[1];
// Tech that can't be researched anywhere
if (!(reqTech in techs))
if (!techs[reqTech] || !techs[reqTech].reqs.length)
continue;
if (!("generic" in techs[reqTech].reqs))
continue;
let reqPhase = techs[reqTech].reqs[0].techs[0];
let myPhase = techs[techcode].reqs[0].techs[0];
let reqPhase = techs[reqTech].reqs.generic[0];
let myPhase = techs[techcode].reqs.generic[0];
if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase")
if (reqPhase == myPhase || basename(reqPhase).slice(0,5) !== "phase" || basename(myPhase).slice(0,5) !== "phase")
continue;
let reqPhasePos = phaseList.indexOf(reqPhase);
let myPhasePos = phaseList.indexOf(myPhase);
if (phaseList.length === 0)
if (!phaseList.length)
phaseList = [reqPhase, myPhase];
else if (reqPhasePos < 0 && myPhasePos > -1)
phaseList.splice(myPhasePos, 0, reqPhase);
@@ -61,18 +61,15 @@ function selectCiv(civCode)
"structures": [],
"techs": []
};
g_ParsedData.techs[civCode] = {};
// get initial units
var startStructs = [];
for (let entity of g_CivData[civCode].StartEntities)
{
if (entity.Template.slice(0, 5) == "units")
g_Lists.units.push(entity.Template);
else if (entity.Template.slice(0, 6) == "struct")
{
g_Lists.structures.push(entity.Template);
startStructs.push(entity.Template);
}
}
// Load units and structures
@@ -95,44 +92,52 @@ function selectCiv(civCode)
var techPairs = {};
for (let techcode of g_Lists.techs)
{
let realcode = depath(techcode);
let realcode = basename(techcode);
if (realcode.slice(0,4) == "pair" || realcode.indexOf("_pair") > -1)
techPairs[techcode] = loadTechnologyPair(techcode);
else if (realcode.slice(0,5) == "phase")
g_ParsedData.phases[techcode] = loadPhase(techcode);
else
g_ParsedData.techs[techcode] = loadTechnology(techcode);
g_ParsedData.techs[civCode][techcode] = loadTechnology(techcode);
}
// Expand tech pairs
for (let paircode in techPairs)
{
let pair = techPairs[paircode];
if (pair.reqs === false)
continue;
for (let techcode of pair.techs)
{
if (depath(techcode).slice(0, 5) === "phase")
if (basename(techcode).slice(0, 5) === "phase")
g_ParsedData.phases[techcode] = loadPhase(techcode);
else
{
let newTech = loadTechnology(techcode);
if (pair.req !== "")
{
if ("generic" in newTech.reqs)
newTech.reqs.generic.concat(techPairs[pair.req].techs);
else
{
for (let civkey of Object.keys(newTech.reqs))
newTech.reqs[civkey].concat(techPairs[pair.req].techs);
}
}
g_ParsedData.techs[techcode] = newTech;
if (!newTech.reqs)
newTech.reqs = {};
else if (newTech.reqs === false)
continue;
for (let option of pair.reqs)
for (let type in option)
for (let opt in newTech.reqs)
{
if (!newTech.reqs[opt][type])
newTech.reqs[opt][type] = [];
newTech.reqs[opt][type] = newTech.reqs[opt][type].concat(option[type]);
}
g_ParsedData.techs[civCode][techcode] = newTech;
}
}
}
// Establish phase order
g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs);
g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs[civCode]);
for (let phasecode of g_ParsedData.phaseList)
{
let phaseInfo = loadTechData(phasecode);
@@ -170,6 +175,7 @@ function selectCiv(civCode)
for (let structCode of g_Lists.structures)
{
let structInfo = g_ParsedData.structures[structCode];
structInfo.phase = GetPhaseOfTemplate(structInfo);
let structPhaseIdx = g_ParsedData.phaseList.indexOf(structInfo.phase);
// If this building is shared with another civ,
@@ -181,43 +187,20 @@ function selectCiv(civCode)
for (let prod of structInfo.production.technology)
if (prod in techPairs)
structInfo.production.technology.splice(
structInfo.production.technology.indexOf(prod), 1,
techPairs[prod].techs[0], techPairs[prod].techs[1]
structInfo.production.technology.indexOf(prod),
1, ...techPairs[prod].techs
);
// Sort techs by phase
let newProdTech = {};
for (let prod of structInfo.production.technology)
{
let phase = "";
let phase = GetPhaseOfTechnology(prod);
if (phase === false)
continue;
if (depath(prod).slice(0,5) === "phase")
{
phase = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[prod].actualPhase);
if (phase > 0)
phase = g_ParsedData.phaseList[phase - 1];
}
else if (g_SelectedCiv in g_ParsedData.techs[prod].reqs)
{
for (let req of g_ParsedData.techs[prod].reqs[g_SelectedCiv])
if (depath(req).slice(0,5) === "phase")
phase = req;
}
else if ("generic" in g_ParsedData.techs[prod].reqs)
{
for (let req of g_ParsedData.techs[prod].reqs.generic)
if (depath(req).slice(0,5) === "phase")
phase = req;
}
if (depath(phase).slice(0,5) !== "phase" ||
g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
{
if (structInfo.phase !== false)
phase = structInfo.phase;
else
phase = g_ParsedData.phaseList[0];
}
if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
phase = structInfo.phase;
if (!(phase in newProdTech))
newProdTech[phase] = [];
@@ -225,46 +208,19 @@ function selectCiv(civCode)
newProdTech[phase].push(prod);
}
// Determine phase for units
// Sort units by phase
let newProdUnits = {};
for (let prod of structInfo.production.units)
{
if (!g_ParsedData.units[prod])
continue;
let unit = g_ParsedData.units[prod];
let phase = "";
let phase = GetPhaseOfTemplate(g_ParsedData.units[prod]);
if (phase === false)
continue;
if (unit.phase !== false)
{
if (g_ParsedData.phaseList.indexOf(unit.phase) < 0)
phase = g_ParsedData.phases[unit.phase].actualPhase;
else
phase = unit.phase;
}
else if (unit.required !== undefined)
{
if (g_ParsedData.phases[unit.required])
phase = g_ParsedData.phases[unit.required].actualPhase;
else if (g_ParsedData.techs[unit.required])
{
let reqs = g_ParsedData.techs[unit.required].reqs;
if (reqs[g_SelectedCiv])
phase = reqs[g_SelectedCiv][0];
else if (reqs.generic)
phase = reqs.generic[0];
else
warn("Empty requirements found on technology " + unit.required);
}
else
warn("Technology " + unit.required + " for " + prod + " not found.");
}
if (depath(phase).slice(0,5) !== "phase" || g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
if (structInfo.phase !== false)
phase = structInfo.phase;
else
phase = g_ParsedData.phaseList[0];
if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
phase = structInfo.phase;
if (!(phase in newProdUnits))
newProdUnits[phase] = [];
@@ -279,20 +235,14 @@ function selectCiv(civCode)
}
// Determine the buildList for the civ (grouped by phase)
var buildList = {};
var trainerList = [];
let buildList = {};
let trainerList = [];
for (let pha of g_ParsedData.phaseList)
buildList[pha] = [];
for (let structCode of g_Lists.structures)
{
if (!g_ParsedData.structures[structCode].phase || startStructs.indexOf(structCode) > -1)
g_ParsedData.structures[structCode].phase = g_ParsedData.phaseList[0];
let myPhase = g_ParsedData.structures[structCode].phase;
if (g_ParsedData.phaseList.indexOf(myPhase) === -1)
myPhase = g_ParsedData.phases[myPhase].actualPhase;
buildList[myPhase].push(structCode);
let phase = g_ParsedData.structures[structCode].phase;
buildList[phase].push(structCode);
}
for (let unitCode of g_Lists.units)
if (g_ParsedData.units[unitCode] && g_ParsedData.units[unitCode].production)
@@ -36,7 +36,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) {
let k = techs.indexOf(this.phases[i].name);
if (k !== -1)
{
this.phases[i].requirements = (this.getTemplate(techs[k]))._template.requirements;
this.phases[i].requirements = DeriveTechnologyRequirements(this.getTemplate(techs[k])._template, this.getPlayerCiv());
continue;
}
for (let tech of techs)
@@ -45,7 +45,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) {
if (template.replaces && template.replaces.indexOf(this.phases[i].name) != -1)
{
this.phases[i].name = tech;
this.phases[i].requirements = template.requirements;
this.phases[i].requirements = DeriveTechnologyRequirements(template, this.getPlayerCiv());
break;
}
}
@@ -169,21 +169,24 @@ m.GameState.prototype.cityPhase = function()
return this.phases[2].name;
};
m.GameState.prototype.getPhaseRequirements = function(i)
m.GameState.prototype.getPhaseEntityRequirements = function(i)
{
if (!this.phases[i-1].requirements)
return undefined;
let requirements = this.phases[i-1].requirements;
if (requirements.number)
return requirements;
else if (requirements.all)
let entityReqs = [];
for (let requirement of this.phases[i-1].requirements)
{
for (let req of requirements.all)
if (req.number)
return req;
if (!requirement.entities)
continue;
for (let entity of requirement.entities)
if (entity.check == "count")
entityReqs.push({
"class": entity.class,
"count": entity.number
});
}
return undefined;
};
return entityReqs;
}
m.GameState.prototype.isResearched = function(template)
{
@@ -213,14 +216,6 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec
if (noRequirementCheck === true)
return true;
// not already researched, check if we can.
// basically a copy of the function in technologyManager since we can't use it.
// Checks the requirements for a technology to see if it can be researched at the current time
// The technology which this technology supersedes is required
if (template.supersedes() && !this.playerData.researchedTechs[template.supersedes()])
return false;
// if this is a pair, we must check that the pair tech is not being researched
if (template.pair())
{
@@ -231,39 +226,52 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec
return false;
}
return this.checkTechRequirements(template.requirements());
return this.checkTechRequirements(template.requirements(this.playerData.civ));
};
/**
* Private function for checking a set of requirements is met
* basically copies TechnologyManager
* Private function for checking a set of requirements is met.
* Basically copies TechnologyManager, but compares against
* variables only available within the AI
*/
m.GameState.prototype.checkTechRequirements = function(reqs)
{
// If there are no requirements then all requirements are met
if (!reqs)
return false;
if (!reqs.length)
return true;
if (reqs.all)
return reqs.all.every(r => this.checkTechRequirements(r));
if (reqs.any)
return reqs.any.some(r => this.checkTechRequirements(r));
if (reqs.civ)
return this.playerData.civ == reqs.civ;
if (reqs.notciv)
return this.playerData.civ != reqs.notciv;
if (reqs.tech)
return this.playerData.researchedTechs[reqs.tech] !== undefined && this.playerData.researchedTechs[reqs.tech];
if (reqs.class && reqs.numberOfTypes)
return this.playerData.typeCountsByClass[reqs.class] &&
Object.keys(this.playerData.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes;
if (reqs.class && reqs.number)
return this.playerData.classCounts[reqs.class] &&
this.playerData.classCounts[reqs.class] >= reqs.number;
function doesEntitySpecPass(entity)
{
switch (entity.check)
{
case "count":
if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number)
return false;
break;
// The technologies requirements are not a recognised format
error("Bad requirements " + uneval(reqs));
return false;
case "variants":
if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number)
return false;
break;
}
return true;
};
return reqs.some(req => {
return Object.keys(req).every(type => {
switch (type)
{
case "techs":
return req[type].every(tech => !!this.playerData.researchedTechs[tech]);
case "entities":
return req[type].every(doesEntitySpecPass, this);
}
return false;
});
});
};
m.GameState.prototype.getMap = function()
@@ -97,11 +97,9 @@ m.Technology.prototype.researchTime = function()
return this._template.researchTime;
};
m.Technology.prototype.requirements = function()
m.Technology.prototype.requirements = function(civ)
{
if (!this._template.requirements)
return undefined;
return this._template.requirements;
return DeriveTechnologyRequirements(this._template, civ);
};
m.Technology.prototype.autoResearch = function()
@@ -1274,11 +1274,12 @@ m.HQ.prototype.buildTemple = function(gameState, queues)
{
if (gameState.currentPhase() < 2 || this.econState !== "cityPhasing")
return;
let requirements = gameState.getPhaseRequirements(3);
if (!requirements || !requirements.number)
return;
if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number)
let requirements = gameState.getPhaseEntityRequirements(3);
if (!requirements.length)
return;
for (let entityReq of requirements)
if (gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length >= entityReq.count)
return;
}
if (!this.canBuild(gameState, "structures/{civ}_temple"))
return;
@@ -1388,7 +1389,7 @@ m.HQ.prototype.manageCorral = function(gameState, queues)
* build more houses if needed.
* kinda ugly, lots of special cases to both build enough houses but not tooo many…
*/
m.HQ.prototype.buildMoreHouses = function(gameState,queues)
m.HQ.prototype.buildMoreHouses = function(gameState, queues)
{
if (gameState.getPopulationMax() <= gameState.getPopulationLimit())
return;
@@ -1418,35 +1419,45 @@ m.HQ.prototype.buildMoreHouses = function(gameState,queues)
queues.house.addPlan(plan);
}
if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseRequirements(2))
if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseEntityRequirements(2).length)
{
let requirements = gameState.getPhaseRequirements(2);
let count = gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length;
if (requirements && count < requirements.number && this.stopBuilding.has(gameState.applyCiv("structures/{civ}_house")))
let houseTemplateName = gameState.applyCiv("structures/{civ}_house");
let houseTemplate = gameState.getTemplate(houseTemplateName);
let needed = 0;
for (let entityReq of gameState.getPhaseEntityRequirements(2))
{
if (this.Config.debug > 1)
API3.warn("no room to place a house ... try to be less restrictive");
this.stopBuilding.delete(gameState.applyCiv("structures/{civ}_house"));
this.requireHouses = true;
if (!houseTemplate.hasClass(entityReq.class))
continue;
let count = gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length;
if (count < entityReq.count && this.stopBuilding.has(houseTemplateName))
{
if (this.Config.debug > 1)
API3.warn("no room to place a house ... try to be less restrictive");
this.stopBuilding.delete(houseTemplateName);
this.requireHouses = true;
}
needed = Math.max(needed, entityReq.count - count);
}
let houseQueue = queues.house.plans;
for (let i = 0; i < numPlanned; ++i)
{
if (houseQueue[i].isGo(gameState))
++count;
else if (count < requirements.number)
--needed;
else if (needed > 0)
{
houseQueue[i].isGo = function () { return true; };
++count;
--needed;
}
}
}
if (this.requireHouses)
{
let requirements = gameState.getPhaseRequirements(2);
if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number)
this.requireHouses = undefined;
let houseTemplate = gameState.getTemplate(gameState.applyCiv("structures/{civ}_house"));
if (gameState.getPhaseEntityRequirements(2).every(req =>
!houseTemplate.hasClass(req.class) || gameState.getOwnStructures().filter(API3.Filters.byClass(req.class)).length >= req.count))
this.requireHouses = undefined;
}
// When population limit too tight
@@ -658,19 +658,19 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
};
GuiInterface.prototype.GetTechnologyData = function(player, name)
GuiInterface.prototype.GetTechnologyData = function(player, data)
{
let cmpDataTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_DataTemplateManager);
let template = cmpDataTemplateManager.GetTechnologyTemplate(name);
let template = cmpDataTemplateManager.GetTechnologyTemplate(data.name);
if (!template)
{
warn("Tried to get data for invalid technology: " + name);
warn("Tried to get data for invalid technology: " + data.name);
return null;
}
let cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return GetTechnologyDataHelper(template, cmpPlayer.GetCiv(), Resources);
return GetTechnologyDataHelper(template, data.civ || cmpPlayer.GetCiv(), Resources);
};
GuiInterface.prototype.IsTechnologyResearched = function(player, data)
@@ -162,7 +162,7 @@ ProductionQueue.prototype.GetTechnologiesList = function()
// Remove any technologies that can't be researched by this civ
techs = techs.filter(tech => {
let reqs = cmpTechnologyManager.GetTechnologyTemplate(tech).requirements || null;
let reqs = DeriveTechnologyRequirements(cmpTechnologyManager.GetTechnologyTemplate(tech), cmpPlayer.GetCiv());
return cmpTechnologyManager.CheckTechnologyRequirements(reqs, true);
});
@@ -97,17 +97,14 @@ TechnologyManager.prototype.IsTechnologyResearched = function(tech)
// Checks the requirements for a technology to see if it can be researched at the current time
TechnologyManager.prototype.CanResearch = function(tech)
{
var template = this.GetTechnologyTemplate(tech);
let template = this.GetTechnologyTemplate(tech);
if (!template)
{
warn("Technology \"" + tech + "\" does not exist");
return false;
}
// The technology which this technology supersedes is required
if (template.supersedes && !this.IsTechnologyResearched(template.supersedes))
return false;
if (template.top && this.IsInProgress(template.top) ||
template.bottom && this.IsInProgress(template.bottom))
return false;
@@ -121,50 +118,56 @@ TechnologyManager.prototype.CanResearch = function(tech)
if (this.IsTechnologyResearched(tech))
return false;
return this.CheckTechnologyRequirements(template.requirements || null);
return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Player).GetCiv()));
};
/**
* Private function for checking a set of requirements is met
* @param reqs Object of technology requirements as given by the technology template
* @param civonly A boolean set to true if only the civ requirement is checked
* @param {object} reqs - Technology requirements as derived from the technology template by globalscripts
* @param {boolean} civonly - True if only the civ requirement is to be checked
*
* @return true if the requirements are checked
* false otherwise
* @return true if the requirements pass, false otherwise
*/
TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly)
TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false)
{
// If there are no requirements then all requirements are met
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (!reqs)
return false;
if (civonly || !reqs.length)
return true;
if (reqs.all)
return reqs.all.every(r => this.CheckTechnologyRequirements(r, civonly));
if (reqs.any)
return reqs.any.some(r => this.CheckTechnologyRequirements(r, civonly));
if (reqs.civ)
return reqs.some(req => {
return Object.keys(req).every(type => {
switch (type)
{
case "techs":
return req[type].every(this.IsTechnologyResearched, this);
case "entities":
return req[type].every(this.DoesEntitySpecPass, this);
}
return false;
});
});
}
TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
{
switch (entity.check)
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
return cmpPlayer && cmpPlayer.GetCiv() == reqs.civ;
case "count":
if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
return false;
break;
case "variants":
if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
return false;
break;
}
if (reqs.notciv)
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
return cmpPlayer && cmpPlayer.GetCiv() != reqs.notciv;
}
if (civonly)
return true;
if (reqs.tech)
return this.IsTechnologyResearched(reqs.tech);
if (reqs.class && reqs.numberOfTypes)
return this.typeCountsByClass[reqs.class] &&
Object.keys(this.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes;
if (reqs.class && reqs.number)
return this.classCounts[reqs.class] &&
this.classCounts[reqs.class] >= reqs.number;
// The technologies requirements are not a recognised format
error("Bad requirements " + uneval(reqs));
return false;
return true;
};
TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
@@ -0,0 +1,510 @@
// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
// No requirements set in template
let template = {};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
/**
* First, the basics:
*/
// Technology Requirement
template.requirements = { "tech": "expected_tech" };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
// Entity Requirement: Count of entities matching given class
template.requirements = { "entity": { "class": "Village", "number": 5 } };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
// Entity Requirement: Count of entities matching given class
template.requirements = { "entity": { "class": "Village", "numberOfTypes": 5 } };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
// Single `civ`
template.requirements = { "civ": "athen" };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
// Single `notciv`
template.requirements = { "notciv": "athen" };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), []);
/**
* Basic `all`s:
*/
// Multiple techs
template.requirements = { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C"] }]);
// Multiple entity definitions
template.requirements = {
"all": [
{ "entity": { "class": "class_A", "number": 5 } },
{ "entity": { "class": "class_B", "number": 5 } },
{ "entity": { "class": "class_C", "number": 5 } }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"),
[{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "count" }, { "class": "class_C", "number": 5, "check": "count" }] }]);
// A `tech` and an `entity`
template.requirements = { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
// Multiple `civ`s
template.requirements = { "all": [{ "civ": "civ_A"}, { "civ": "civ_B"}, { "civ": "civ_C"}] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false);
// Multiple `notciv`s
template.requirements = { "all": [{ "notciv": "civ_A"}, { "notciv": "civ_B"}, { "notciv": "civ_C"}] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
// A `civ` with a tech/entity
template.requirements = { "all": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
// A `notciv` with a tech/entity
template.requirements = { "all": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
/**
* Basic `any`s:
*/
// Multiple techs
template.requirements = { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "techs": ["tech_B"] }, { "techs": ["tech_C"] }]);
// Multiple entity definitions
template.requirements = {
"any": [
{ "entity": { "class": "class_A", "number": 5 } },
{ "entity": { "class": "class_B", "number": 5 } },
{ "entity": { "class": "class_C", "number": 5 } }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
{ "entities": [{ "class": "class_B", "number": 5, "check": "count" }] },
{ "entities": [{ "class": "class_C", "number": 5, "check": "count" }] }
]);
// A tech or an entity
template.requirements = { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
// Multiple `civ`s
template.requirements = { "any": [{ "civ": "civ_A" }, { "civ": "civ_B" }, { "civ": "civ_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false);
// Multiple `notciv`s
template.requirements = { "any": [{ "notciv": "civ_A" }, { "notciv": "civ_B" }, { "notciv": "civ_C" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
// A `civ` or a tech/entity
template.requirements = { "any": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
// A `notciv` or a tech
template.requirements = { "any": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
/**
* Complicated `all`s, part 1 - an `all` inside an `all`:
*/
// Techs
template.requirements = {
"all": [
{ "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
{ "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C", "tech_D"] }]);
// Techs and entities
template.requirements = {
"all": [
{ "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
{ "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{
"techs": ["tech_A", "tech_B"],
"entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }]
}]);
// Two `civ`s, without and with a tech
template.requirements = {
"all": [
{ "all": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
template.requirements = {
"all": [
{ "tech": "required_tech" },
{ "all": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
// Two `notciv`s, without and with a tech
template.requirements = {
"all": [
{ "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
template.requirements = {
"all": [
{ "tech": "required_tech" },
{ "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
// Inner `all` has a tech and a `civ`/`notciv`
template.requirements = {
"all": [
{ "all": [{ "tech": "tech_A" }, { "civ": "maur" }] },
{ "tech": "tech_B" }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_B"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_A", "tech_B"] }]);
template.requirements = {
"all": [
{ "all": [{ "tech": "tech_A" }, { "notciv": "maur" }] },
{ "tech": "tech_B" }
]
}
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_B"] }]);
/**
* Complicated `all`s, part 2 - an `any` inside an `all`:
*/
// Techs
template.requirements = {
"all": [
{ "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
{ "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A", "tech_C"] },
{ "techs": ["tech_A", "tech_D"] },
{ "techs": ["tech_B", "tech_C"] },
{ "techs": ["tech_B", "tech_D"] }
]);
// Techs and entities
template.requirements = {
"all": [
{ "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
{ "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
{ "techs": ["tech_A", "tech_B"] },
{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] },
{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }], "techs": ["tech_B"] }
]);
// Two `civ`s, without and with a tech
template.requirements = {
"all": [
{ "any": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
template.requirements = {
"all": [
{ "tech": "required_tech" },
{ "any": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
// Two `notciv`s, without and with a tech
template.requirements = {
"all": [
{ "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
template.requirements = {
"all": [
{ "tech": "required_tech" },
{ "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
/**
* Complicated `any`s, part 1 - an `all` inside an `any`:
*/
// Techs
template.requirements = {
"any": [
{ "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
{ "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A", "tech_B"] },
{ "techs": ["tech_C", "tech_D"] }
]);
// Techs and entities
template.requirements = {
"any": [
{ "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
{ "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A"], "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
{ "entities": [{ "class": "class_B", "number": 5, "check": "variants" }], "techs": ["tech_B"] }
]);
// Two `civ`s, without and with a tech
template.requirements = {
"any": [
{ "all": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
template.requirements = {
"any": [
{ "tech": "required_tech" },
{ "all": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
// Note: these requirements don't really make sense, as the `any` makes the `civ`s in the the inner `all` irrelevant.
// We test it anyway as a precursor to later tests.
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
// Two `notciv`s, without and with a tech
template.requirements = {
"any": [
{ "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
template.requirements = {
"any": [
{ "tech": "required_tech" },
{ "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
// Note: these requirements have a result that might seen unexpected at first glance.
// This is because the `notciv`s are rendered irrelevant by the `any`, and they have nothing else to operate on.
// We test it anyway as a precursor for later tests.
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
// Inner `all` has a tech and a `civ`/`notciv`
template.requirements = {
"any": [
{ "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
{ "tech": "tech2" }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
template.requirements = {
"any": [
{ "all": [{ "notciv": "civA" }, { "tech": "tech1" }] },
{ "tech": "tech2" }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech2"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
/**
* Complicated `any`s, part 2 - an `any` inside an `any`:
*/
// Techs
template.requirements = {
"any": [
{ "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
{ "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A"] },
{ "techs": ["tech_B"] },
{ "techs": ["tech_C"] },
{ "techs": ["tech_D"] }
]);
// Techs and entities
template.requirements = {
"any": [
{ "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
{ "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
{ "techs": ["tech_A"] },
{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
{ "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
{ "techs": ["tech_B"] }
]);
// Two `civ`s, without and with a tech
template.requirements = {
"any": [
{ "any": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
template.requirements = {
"any": [
{ "tech": "required_tech" },
{ "any": [{ "civ": "athen" }, { "civ": "spart" }] }
]
};
// These requirements may not make sense, as the `civ`s are unable to restrict the requirements due to the outer `any`
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
// Two `notciv`s, without and with a tech
template.requirements = {
"any": [
{ "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
template.requirements = {
"any": [
{ "tech": "required_tech" },
{ "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
]
};
// These requirements may not make sense, as the `notciv`s are made irrelevant by the outer `any`
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
/**
* Further tests
*/
template.requirements = {
"all": [
{ "tech": "tech1" },
{ "any": [{ "civ": "civA" }, { "civ": "civB" }] },
{ "notciv": "civC" }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
template.requirements = {
"any": [
{ "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
{ "all": [{ "civ": "civB" }, { "tech": "tech2" }] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
template.requirements = {
"any": [
{ "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
{ "all": [
{ "any": [{ "civ": "civB" }, { "civ": "civC" }] },
{ "tech": "tech2" }
] }
]
};
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), [{ "techs": ["tech2"] }]);
TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civD"), false);
@@ -6,7 +6,7 @@
},
"description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology.",
"cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 },
"requirements": { "all": [{ "class": "Town", "number": 4 }, { "notciv": "athen" }] },
"requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "notciv": "athen" }] },
"requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).",
"supersedes": "phase_town",
"icon": "city_phase.png",
@@ -5,7 +5,7 @@
},
"description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology. This is the Athenian city phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.",
"cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 },
"requirements": { "all": [{ "class": "Town", "number": 4 }, { "civ": "athen" }] },
"requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "civ": "athen" }] },
"requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).",
"supersedes": "phase_town_athen",
"replaces": ["phase_city"],
@@ -6,7 +6,7 @@
},
"description": "Advances from a small village to a bustling town, ready to expand rapidly.",
"cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 },
"requirements": { "all": [{ "class": "Village", "number": 5 }, { "notciv": "athen" }] },
"requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "notciv": "athen" }] },
"requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).",
"supersedes": "phase_village",
"icon": "town_phase.png",
@@ -5,7 +5,7 @@
},
"description": "Advances from a small village to a bustling town, ready to expand rapidly. This is the Athenian town phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.",
"cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 },
"requirements": { "all": [{ "class": "Village", "number": 5 }, { "civ": "athen" }] },
"requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] },
"requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).",
"supersedes": "phase_village",
"replaces": ["phase_town"],
@@ -5,16 +5,12 @@
"requirements": {
"all": [
{ "tech": "phase_city" },
{
"all": [
{ "notciv": "brit"},
{ "notciv": "gaul" },
{ "notciv": "iber" },
{ "notciv": "maur" },
{ "notciv": "pers" },
{ "notciv": "sele" }
]
}
{ "notciv": "brit"},
{ "notciv": "gaul" },
{ "notciv": "iber" },
{ "notciv": "maur" },
{ "notciv": "pers" },
{ "notciv": "sele" }
]
},
"requirementsTooltip": "Unlocked in City Phase.",
@@ -2,7 +2,7 @@
"genericName": "Diaspora",
"description": "The extension of trade leads to the permanent establishment of storekeepers and their families in foreign countries, allowing them to exploit the wealth of these countries.",
"cost": { "food": 200, "wood": 200, "stone": 100, "metal": 100 },
"requirements": { "class": "Trader", "number": 3 },
"requirements": { "entity": { "class": "Trader", "number": 3 } },
"requirementsTooltip": "Requires 3 Traders",
"supersedes": "unlock_shared_los",
"icon": "diaspora.png",