Ticket #4263: 4263_techreqs_v1-4.patch

File 4263_techreqs_v1-4.patch, 73.3 KB (added by s0600204, 7 years ago)

Updated patch.

  • binaries/data/mods/public/globalscripts/Technologies.js

    diff --git a/binaries/data/mods/public/globalscripts/Technologies.js b/binaries/data/mods/public/globalscripts/Technologies.js
    index 1af45a4dc..5340e611d 100644
    a b function DoesModificationApply(modification, classes)  
    5050{
    5151    return MatchesClassList(classes, modification.affects);
    5252}
     53
     54/**
     55 * Derives the technology requirements from a given technology template.
     56 * Takes into account the `supersedes` attribute.
     57 *
     58 * @param {object} template - The template object. Loading of the template must have already occured.
     59 *
     60 * @return Derived technology requirements. See `InterpretTechRequirements` for object's syntax.
     61 */
     62function DeriveTechnologyRequirements(template, civ)
     63{
     64    let requirements = [];
     65
     66    if (template.requirements)
     67    {
     68        let op = Object.keys(template.requirements)[0];
     69        let val = template.requirements[op];
     70        requirements = InterpretTechRequirements(civ, op, val);
     71    }
     72
     73    if (template.supersedes && requirements)
     74    {
     75        if (!requirements.length)
     76            requirements.push({});
     77
     78        for (let req of requirements)
     79        {
     80            if (!req.techs)
     81                req.techs = [];
     82            req.techs.push(template.supersedes);
     83        }
     84    }
     85
     86    return requirements;
     87}
     88
     89/**
     90 * Interprets the prerequisite requirements of a technology.
     91 *
     92 * Takes the initial { key: value } from the short-form requirements object in entity templates,
     93 * and parses it into an object that can be more easily checked by simulation and gui.
     94 *
     95 * Works recursively if needed.
     96 *
     97 * The returned object is in the form:
     98 * ```
     99 *  { "techs": ["tech1", "tech2"] },
     100 *  { "techs": ["tech3"] }
     101 * ```
     102 * or
     103 * ```
     104 *  { "entities": [[{
     105 *      "class": "human",
     106 *      "number": 2,
     107 *      "check": "count"
     108 *  }
     109 * or
     110 * ```
     111 *  false;
     112 * ```
     113 * (Or, to translate:
     114 * 1. need either both `tech1` and `tech2`, or `tech3`
     115 * 2. need 2 entities with the `human` class
     116 * 3. cannot research this tech at all)
     117 *
     118 * @param {string} civ - The civ code
     119 * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
     120 * @param {mixed} value - The value associated with the above operation.
     121 *
     122 * @return Object containing the requirements for the given civ, or false if the civ cannot research the tech.
     123 */
     124function InterpretTechRequirements(civ, operator, value)
     125{
     126    let requirements = [];
     127
     128    switch (operator)
     129    {
     130    case "civ":
     131        return !civ || civ == value ? [] : false;
     132
     133    case "notciv":
     134        return civ == value ? false : [];
     135
     136    case "entity":
     137    {
     138        let number = value.number || value.numberOfTypes || 0;
     139        if (number > 0)
     140            requirements.push({
     141                "entities": [{
     142                    "class": value.class,
     143                    "number": number,
     144                    "check": value.number ? "count" : "variants"
     145                }]
     146            });
     147    }
     148        break;
     149
     150    case "tech":
     151        requirements.push({
     152            "techs": [value]
     153        });
     154        break;
     155
     156    case "all":
     157    {
     158        let civPermitted = -1; // tri-state boolean
     159        for (let subvalue of value)
     160        {
     161            let newOper = Object.keys(subvalue)[0];
     162            let newValue = subvalue[newOper];
     163            let result = InterpretTechRequirements(civ, newOper, newValue);
     164
     165            switch (newOper)
     166            {
     167            case "civ":
     168                if (result)
     169                    civPermitted = true;
     170                else if (civPermitted < true)
     171                    civPermitted = false;
     172                break;
     173
     174            case "notciv":
     175                if (!result)
     176                    return false;
     177                break;
     178
     179            case "any":
     180                if (!result)
     181                    return false;
     182                // else, fall through
     183
     184            case "all":
     185                if (!result)
     186                {
     187                    let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
     188                    if (!nullcivreqs || !nullcivreqs.length)
     189                        civPermitted = false;
     190                    continue;
     191                }
     192                // else, fall through
     193
     194            case "tech":
     195            case "entity":
     196            {
     197                if (result.length)
     198                {
     199                    if (!requirements.length)
     200                        requirements.push({});
     201
     202                    let newRequirements = [];
     203                    for (let currReq of requirements)
     204                        for (let res of result)
     205                        {
     206                            let newReq = {}
     207                            for (let subtype in currReq)
     208                                newReq[subtype] = currReq[subtype];
     209
     210                            for (let subtype in res)
     211                            {
     212                                if (!newReq[subtype])
     213                                    newReq[subtype] = [];
     214                                newReq[subtype] = newReq[subtype].concat(res[subtype]);
     215                            }
     216                            newRequirements.push(newReq);
     217                        }
     218                    requirements = newRequirements;
     219                }
     220            }
     221                break;
     222
     223            }
     224        }
     225        if (!civPermitted)
     226            return false;
     227    }
     228    break;
     229
     230    case "any":
     231    {
     232        let civPermitted = false;
     233        for (let subvalue of value)
     234        {
     235            let newOper = Object.keys(subvalue)[0];
     236            let newValue = subvalue[newOper];
     237            let result = InterpretTechRequirements(civ, newOper, newValue);
     238
     239            switch (newOper)
     240            {
     241
     242            case "civ":
     243                if (result)
     244                    return [];
     245                break;
     246
     247            case "notciv":
     248                if (!result)
     249                    return false;
     250                civPermitted = true;
     251                break;
     252
     253            case "any":
     254                if (!result)
     255                {
     256                    let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
     257                    if (!nullcivreqs || !nullcivreqs.length)
     258                        continue;
     259                    return false;
     260                }
     261                // else, fall through
     262
     263            case "all":
     264                if (!result)
     265                    continue;
     266                civPermitted = true;
     267                // else, fall through
     268
     269            case "tech":
     270            case "entity":
     271                for (let res of result)
     272                    requirements.push(res);
     273                break;
     274
     275            }
     276        }
     277        if (!civPermitted && !requirements.length)
     278            return false;
     279    }
     280    break;
     281
     282    default:
     283        warn("Unknown requirement operator: "+operator);
     284    }
     285
     286    return requirements;
     287}
  • binaries/data/mods/public/globalscripts/Templates.js

    diff --git a/binaries/data/mods/public/globalscripts/Templates.js b/binaries/data/mods/public/globalscripts/Templates.js
    index 66d445c91..db54ca469 100644
    a b function GetTemplateDataHelper(template, player, auraTemplates, resources)  
    351351 */
    352352function GetTechnologyDataHelper(template, civ, resources)
    353353{
    354     var ret = {};
     354    let ret = {};
    355355
    356356    // Get specific name for this civ or else the generic specific name
    357     var specific;
     357    let specific;
    358358    if (template.specificName)
    359     {
    360         if (template.specificName[civ])
    361             specific = template.specificName[civ];
    362         else
    363             specific = template.specificName['generic'];
    364     }
     359        specific = template.specificName[civ] || template.specificName.generic;
    365360
    366361    ret.name = {
    367362        "specific": specific,
    function GetTechnologyDataHelper(template, civ, resources)  
    377372    ret.tooltip = template.tooltip;
    378373    ret.requirementsTooltip = template.requirementsTooltip || "";
    379374
    380     // TODO: This doesn't handle all types of requirements
    381     if (template.requirements && template.requirements.class)
    382         ret.classRequirements = {
    383             "class": template.requirements.class,
    384             "number": template.requirements.number
    385         };
    386     else if (template.requirements && template.requirements.all)
    387         for (let req of template.requirements.all)
    388             if (req.class)
    389                 ret.classRequirements = {
    390                     "class": req.class,
    391                     "number": req.number
    392                 };
     375    ret.reqs = DeriveTechnologyRequirements(template, civ);
    393376
    394377    ret.description = template.description;
    395378
  • binaries/data/mods/public/globalscripts/utility.js

    diff --git a/binaries/data/mods/public/globalscripts/utility.js b/binaries/data/mods/public/globalscripts/utility.js
    index 6146b4634..9fdc1b71d 100644
    a b function shuffleArray(source)  
    3434    }
    3535    return result;
    3636}
     37
     38/**
     39 * Removes prefixing path from a path or filename, leaving just the file's name (with extension)
     40 *
     41 * ie. a/b/c/file.ext -> file.ext
     42 */
     43function basename(path)
     44{
     45    return path.slice(path.lastIndexOf("/") + 1);
     46}
  • binaries/data/mods/public/gui/common/tooltips.js

    diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js
    index adae9432b..460c52061 100644
    a b function getEntityCostTooltip(template, trainNum, entity)  
    459459    return "";
    460460}
    461461
    462 function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology)
     462function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology, civ)
    463463{
    464464    if (technologyEnabled)
    465465        return "";
    466466
    467467    return sprintf(translate("Requires %(technology)s"), {
    468         "technology": getEntityNames(GetTechnologyData(requiredTechnology))
     468        "technology": getEntityNames(GetTechnologyData(requiredTechnology, civ))
    469469    });
    470470}
    471471
  • binaries/data/mods/public/gui/session/selection_panels.js

    diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js
    index 6bc6e12be..96ec3e0c1 100644
    a b g_SelectionPanels.Construction = {  
    350350        let limits = getEntityLimitAndCount(data.playerState, data.item);
    351351        tooltips.push(
    352352            formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
    353             getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology),
     353            getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
    354354            getNeededResourcesTooltip(neededResources));
    355355
    356356        data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    g_SelectionPanels.Research = {  
    794794        setPanelObjectPosition(pair, data.i, data.rowLength);
    795795
    796796        // Handle one or two techs (tech pair)
     797        let player = data.player;
    797798        for (let i in techs)
    798799        {
    799800            let tech = techs[i];
    800801
    801802            // Don't change the object returned by GetTechnologyData
    802             let template = clone(GetTechnologyData(tech));
     803            let template = clone(GetTechnologyData(tech, GetSimState().players[player].civ));
    803804            if (!template)
    804805                return false;
    805806
    g_SelectionPanels.Research = {  
    808809
    809810            let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    810811                "cost": template.cost,
    811                 "player": data.player
     812                "player": player
    812813            });
    813814
    814815            let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
    815816                "tech": tech,
    816                 "player": data.player
     817                "player": player
    817818            });
    818819
    819820            let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
    g_SelectionPanels.Research = {  
    828829            if (!requirementsPassed)
    829830            {
    830831                let tip = template.requirementsTooltip;
    831                 if (template.classRequirements)
     832                let reqs = template.reqs;
     833                for (let req of reqs)
    832834                {
    833                     let player = data.player;
    834                     let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0;
    835                     let remaining = template.classRequirements.number - current;
    836                     tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), {
    837                         "number": remaining
     835                    if (!req.entities)
     836                        continue;
     837
     838                    let entityCounts = [];
     839                    for (let entity of req.entities)
     840                    {
     841                        let current = 0;
     842                        switch (entity.check)
     843                        {
     844                        case "count":
     845                            current = GetSimState().players[player].classCounts[entity.class] || 0;
     846                            break;
     847
     848                        case "variants":
     849                            current = GetSimState().players[player].typeCountsByClass[entity.class] ? Object.keys(GetSimState().players[player].typeCountsByClass[entity.class]).length : 0;
     850                            break;
     851                        }
     852
     853                        let remaining = entity.number - current;
     854                        if (remaining < 1)
     855                            continue;
     856
     857                        entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
     858                            "number": remaining,
     859                            "class": entity.class
     860                        }));
     861                    }
     862
     863                    tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
     864                        "entityCounts": entityCounts.join(translate(", "))
    838865                    });
    839866                }
    840867                tooltips.push(tip);
    g_SelectionPanels.Training = {  
    10641091            "[color=\"" + g_HotkeyColor + "\"]" +
    10651092            formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) +
    10661093            "[/color]",
    1067             getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology),
     1094            getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
    10681095            getNeededResourcesTooltip(neededResources));
    10691096
    10701097        data.button.tooltip = tooltips.filter(tip => tip).join("\n");
    g_SelectionPanels.Upgrade = {  
    11541181            tooltips.push(
    11551182                getEntityCostTooltip(data.item),
    11561183                formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
    1157                 getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology),
     1184                getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
    11581185                getNeededResourcesTooltip(neededResources));
    11591186
    11601187            tooltip = tooltips.filter(tip => tip).join("\n");
  • binaries/data/mods/public/gui/session/session.js

    diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
    index e2b43aae5..d0b479581 100644
    a b function GetTemplateDataWithoutLocalization(templateName)  
    222222    return g_TemplateDataWithoutLocalization[templateName];
    223223}
    224224
    225 function GetTechnologyData(technologyName)
     225function GetTechnologyData(technologyName, civ)
    226226{
    227     if (!(technologyName in g_TechnologyData))
     227    if (!g_TechnologyData[civ])
     228        g_TechnologyData[civ] = {};
     229
     230    if (!(technologyName in g_TechnologyData[civ]))
    228231    {
    229         let template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
     232        let template = Engine.GuiInterfaceCall("GetTechnologyData", { "name": technologyName, "civ": civ });
    230233        translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
    231         g_TechnologyData[technologyName] = template;
     234        g_TechnologyData[civ][technologyName] = template;
    232235    }
    233236
    234     return g_TechnologyData[technologyName];
     237    return g_TechnologyData[civ][technologyName];
    235238}
    236239
    237240function init(initData, hotloadData)
  • binaries/data/mods/public/gui/structree/draw.js

    diff --git a/binaries/data/mods/public/gui/structree/draw.js b/binaries/data/mods/public/gui/structree/draw.js
    index bb437cc8b..44be50783 100644
    a b function draw()  
    9797                if (stru.production.technology[prod_pha])
    9898                    for (let prod of stru.production.technology[prod_pha])
    9999                    {
    100                         prod = clone(depath(prod).slice(0,5) == "phase" ?
     100                        prod = clone(basename(prod).slice(0,5) == "phase" ?
    101101                            g_ParsedData.phases[prod] :
    102                             g_ParsedData.techs[prod]);
     102                            g_ParsedData.techs[g_SelectedCiv][prod]);
    103103
    104104                        for (let res in stru.techCostMultiplier)
    105105                            if (prod.cost[res])
    function draw()  
    183183                    prod = g_ParsedData.units[prod];
    184184                    break;
    185185                case "techs":
    186                     prod = clone(g_ParsedData.techs[prod]);
     186                    prod = clone(g_ParsedData.techs[civCode][prod]);
    187187                    for (let res in trainer.techCostMultiplier)
    188188                        if (prod.cost[res])
    189189                            prod.cost[res] *= trainer.techCostMultiplier[res];
  • binaries/data/mods/public/gui/structree/helper.js

    diff --git a/binaries/data/mods/public/gui/structree/helper.js b/binaries/data/mods/public/gui/structree/helper.js
    index b0c8577ac..53e4fa036 100644
    a b function loadAuraData(templateName)  
    4848    return g_AuraData[templateName];
    4949}
    5050
    51 function depath(path)
    52 {
    53     return path.slice(path.lastIndexOf("/") + 1);
    54 }
    55 
    5651/**
    5752 * This is needed because getEntityCostTooltip in tooltip.js needs to get
    5853 * the template data of the different wallSet pieces. In the session this
    function GetTemplateData(templateName)  
    6358    var template = loadTemplate(templateName);
    6459    return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
    6560}
     61
     62/**
     63 * Determines and returns the phase in which a given technology can be
     64 * first researched. Works recursively through the given tech's
     65 * pre-requisite and superseded techs if necessary.
     66 *
     67 * @param {string} techName - The Technology's name
     68 * @return The name of the phase the technology belongs to, or false if
     69 *         the current civ can't research this tech
     70 */
     71function GetPhaseOfTechnology(techName)
     72{
     73    let phaseIdx = -1;
     74
     75    if (basename(techName).slice(0, 5) === "phase")
     76    {
     77        phaseIdx = g_ParsedData.phaseList.indexOf(GetActualPhase(techName));
     78        if (phaseIdx > 0)
     79            return g_ParsedData.phaseList[phaseIdx - 1];
     80    }
     81
     82    if (!g_ParsedData.techs[g_SelectedCiv][techName])
     83        warn(g_SelectedCiv + " : " + techName);
     84    let techReqs = g_ParsedData.techs[g_SelectedCiv][techName].reqs;
     85    if (!techReqs)
     86        return false;
     87
     88    for (let option of techReqs)
     89        if (option.techs)
     90            for (let tech of option.techs)
     91            {
     92                if (basename(tech).slice(0, 5) === "phase")
     93                    return tech;
     94                phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(tech)));
     95            }
     96    return g_ParsedData.phaseList[phaseIdx] || false;
     97}
     98
     99function GetActualPhase(phaseName)
     100{
     101    if (g_ParsedData.phases[phaseName])
     102        return g_ParsedData.phases[phaseName].actualPhase;
     103
     104    warn("Unrecognised phase (" + techName + ")");
     105    return g_ParsedData.phaseList[0];
     106}
     107
     108function GetPhaseOfTemplate(template)
     109{
     110    if (!template.requiredTechnology)
     111        return g_ParsedData.phaseList[0];
     112
     113    if (basename(template.requiredTechnology).slice(0, 5) == "phase")
     114        return GetActualPhase(template.requiredTechnology);
     115
     116    return GetPhaseOfTechnology(template.requiredTechnology);
     117}
  • binaries/data/mods/public/gui/structree/load.js

    diff --git a/binaries/data/mods/public/gui/structree/load.js b/binaries/data/mods/public/gui/structree/load.js
    index 07a55cb1c..2d6b58b77 100644
    a b function loadUnit(templateName)  
    22{
    33    if (!Engine.TemplateExists(templateName))
    44        return null;
    5     var template = loadTemplate(templateName);
    65
    7     var unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
    8     unit.phase = false;
    9 
    10     if (unit.requiredTechnology)
    11     {
    12         if (depath(unit.requiredTechnology).slice(0, 5) == "phase")
    13             unit.phase = unit.requiredTechnology;
    14         else if (unit.requiredTechnology.length)
    15             unit.required = unit.requiredTechnology;
    16     }
     6    let template = loadTemplate(templateName);
     7    let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
    178
    189    if (template.ProductionQueue)
    1910    {
    function loadUnit(templateName)  
    5445
    5546function loadStructure(templateName)
    5647{
    57     var template = loadTemplate(templateName);
    58     var structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
    59     structure.phase = false;
    60 
    61     if (structure.requiredTechnology)
    62     {
    63         if (depath(structure.requiredTechnology).slice(0, 5) == "phase")
    64             structure.phase = structure.requiredTechnology;
    65         else if (structure.requiredTechnology.length)
    66             structure.required = structure.requiredTechnology;
    67     }
     48    let template = loadTemplate(templateName);
     49    let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
    6850
    6951    structure.production = {
    7052        "technology": [],
    function loadStructure(templateName)  
    140122
    141123function loadTechnology(techName)
    142124{
    143     var template = loadTechData(techName);
    144     var tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
    145     tech.reqs = {};
     125    let template = loadTechData(techName);
     126    let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
    146127
    147128    if (template.pair !== undefined)
    148129        tech.pair = template.pair;
    149130
    150     if (template.requirements !== undefined)
    151     {
    152         for (let op in template.requirements)
    153         {
    154             let val = template.requirements[op];
    155             let req = calcReqs(op, val);
    156 
    157             switch (op)
    158             {
    159             case "tech":
    160                 tech.reqs.generic = req;
    161                 break;
    162 
    163             case "civ":
    164                 tech.reqs[req] = [];
    165                 break;
    166 
    167             case "any":
    168                 if (req[0].length > 0)
    169                     for (let r of req[0])
    170                     {
    171                         let v = req[0][r];
    172                         if (typeof r == "number")
    173                             tech.reqs[v] = [];
    174                         else
    175                             tech.reqs[r] = v;
    176                     }
    177                 if (req[1].length > 0)
    178                     tech.reqs.generic = req[1];
    179                 break;
    180 
    181             case "all":
    182                 if (!req[0].length)
    183                     tech.reqs.generic = req[1];
    184                 else
    185                     for (let r of req[0])
    186                         tech.reqs[r] = req[1];
    187                 break;
    188             }
    189         }
    190     }
    191 
    192     if (template.supersedes !== undefined)
    193     {
    194         if (tech.reqs.generic !== undefined)
    195             tech.reqs.generic.push(template.supersedes);
    196         else
    197             for (let ck of Object.keys(tech.reqs))
    198                 tech.reqs[ck].push(template.supersedes);
    199     }
    200 
    201131    return tech;
    202132}
    203133
    function loadTechnologyPair(pairCode)  
    219149
    220150    return {
    221151        "techs": [ pairInfo.top, pairInfo.bottom ],
    222         "req": pairInfo.supersedes || ""
     152        "reqs": DeriveTechnologyRequirements(pairInfo, g_SelectedCiv)
    223153    };
    224154}
    225155
    226156/**
    227  * Calculate the prerequisite requirements of a technology.
    228  * Works recursively if needed.
    229  *
    230  * @param op The base operation. Can be "civ", "tech", "all" or "any".
    231  * @param val The value associated with the above operation.
    232  *
    233  * @return Sorted requirments.
    234  */
    235 function calcReqs(op, val)
    236 {
    237     switch (op)
    238     {
    239     case "civ":
    240     case "class":
    241     case "notciv":
    242     case "number":
    243         // nothing needs doing
    244         break;
    245 
    246     case "tech":
    247         if (depath(val).slice(0,4) === "pair")
    248             return loadTechnologyPair(val).techs;
    249         return [ val ];
    250 
    251     case "all":
    252     case "any":
    253         let t = [];
    254         let c = [];
    255         for (let nv of val)
    256         {
    257             for (let o in nv)
    258             {
    259                 let v = nv[o];
    260                 let r = calcReqs(o, v);
    261                 switch (o)
    262                 {
    263                 case "civ":
    264                 case "notciv":
    265                     c.push(r);
    266                     break;
    267 
    268                 case "tech":
    269                     t = t.concat(r);
    270                     break;
    271 
    272                 case "any":
    273                     c = c.concat(r[0]);
    274                     t = t.concat(r[1]);
    275                     break;
    276 
    277                 case "all":
    278                     for (let ci in r[0])
    279                         c[ci] = r[1];
    280                     t = t;
    281                 }
    282             }
    283         }
    284         return [ c, t ];
    285 
    286     default:
    287         warn("Unknown reqs operator: "+op);
    288     }
    289     return val;
    290 }
    291 
    292 /**
    293157 * Unravel phases
    294158 *
    295159 * @param techs The current available store of techs
    function unravelPhases(techs)  
    304168    {
    305169        let techdata = techs[techcode];
    306170
    307         if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2)
     171        if (!techdata.reqs || !techdata.reqs.length || !techdata.reqs[0].techs || techdata.reqs[0].techs.length < 2)
    308172            continue;
    309173
    310         let reqTech = techs[techcode].reqs.generic[1];
    311 
    312         // Tech that can't be researched anywhere
    313         if (!(reqTech in techs))
    314             continue;
     174        let reqTech = techs[techcode].reqs[0].techs[1];
    315175
    316         if (!("generic" in techs[reqTech].reqs))
     176        if (!techs[reqTech] || !techs[reqTech].reqs.length)
    317177            continue;
    318178
    319         let reqPhase = techs[reqTech].reqs.generic[0];
    320         let myPhase = techs[techcode].reqs.generic[0];
     179        let reqPhase = techs[reqTech].reqs[0].techs[0];
     180        let myPhase = techs[techcode].reqs[0].techs[0];
    321181
    322         if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase")
     182        if (reqPhase == myPhase || basename(reqPhase).slice(0,5) !== "phase" || basename(myPhase).slice(0,5) !== "phase")
    323183            continue;
    324184
    325185        let reqPhasePos = phaseList.indexOf(reqPhase);
    326186        let myPhasePos = phaseList.indexOf(myPhase);
    327187
    328         if (phaseList.length === 0)
     188        if (!phaseList.length)
    329189            phaseList = [reqPhase, myPhase];
    330190        else if (reqPhasePos < 0 && myPhasePos > -1)
    331191            phaseList.splice(myPhasePos, 0, reqPhase);
  • binaries/data/mods/public/gui/structree/structree.js

    diff --git a/binaries/data/mods/public/gui/structree/structree.js b/binaries/data/mods/public/gui/structree/structree.js
    index fcb6a9c25..20f20fdcb 100644
    a b function selectCiv(civCode)  
    6161        "structures": [],
    6262        "techs": []
    6363    };
     64    g_ParsedData.techs[civCode] = {};
    6465
    6566    // get initial units
    66     var startStructs = [];
    6767    for (let entity of g_CivData[civCode].StartEntities)
    6868    {
    6969        if (entity.Template.slice(0, 5) == "units")
    7070            g_Lists.units.push(entity.Template);
    7171        else if (entity.Template.slice(0, 6) == "struct")
    72         {
    7372            g_Lists.structures.push(entity.Template);
    74             startStructs.push(entity.Template);
    75         }
    7673    }
    7774
    7875    // Load units and structures
    function selectCiv(civCode)  
    9592    var techPairs = {};
    9693    for (let techcode of g_Lists.techs)
    9794    {
    98         let realcode = depath(techcode);
     95        let realcode = basename(techcode);
    9996
    10097        if (realcode.slice(0,4) == "pair" || realcode.indexOf("_pair") > -1)
    10198            techPairs[techcode] = loadTechnologyPair(techcode);
    10299        else if (realcode.slice(0,5) == "phase")
    103100            g_ParsedData.phases[techcode] = loadPhase(techcode);
    104101        else
    105             g_ParsedData.techs[techcode] = loadTechnology(techcode);
     102            g_ParsedData.techs[civCode][techcode] = loadTechnology(techcode);
    106103    }
    107104
    108105    // Expand tech pairs
    109106    for (let paircode in techPairs)
    110107    {
    111108        let pair = techPairs[paircode];
     109        if (pair.reqs === false)
     110            continue;
     111
    112112        for (let techcode of pair.techs)
    113113        {
    114             if (depath(techcode).slice(0, 5) === "phase")
     114            if (basename(techcode).slice(0, 5) === "phase")
    115115                g_ParsedData.phases[techcode] = loadPhase(techcode);
    116116            else
    117117            {
    118118                let newTech = loadTechnology(techcode);
    119                 if (pair.req !== "")
    120                 {
    121                     if ("generic" in newTech.reqs)
    122                         newTech.reqs.generic.concat(techPairs[pair.req].techs);
    123                     else
    124                     {
    125                         for (let civkey of Object.keys(newTech.reqs))
    126                             newTech.reqs[civkey].concat(techPairs[pair.req].techs);
    127                     }
    128                 }
    129                 g_ParsedData.techs[techcode] = newTech;
     119
     120                if (!newTech.reqs)
     121                    newTech.reqs = {};
     122                else if (newTech.reqs === false)
     123                    continue;
     124
     125                for (let option of pair.reqs)
     126                    for (let type in option)
     127                        for (let opt in newTech.reqs)
     128                        {
     129                            if (!newTech.reqs[opt][type])
     130                                newTech.reqs[opt][type] = [];
     131                            newTech.reqs[opt][type] = newTech.reqs[opt][type].concat(option[type]);
     132                        }
     133
     134                g_ParsedData.techs[civCode][techcode] = newTech;
    130135            }
    131136        }
    132137    }
    133138
    134139    // Establish phase order
    135     g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs);
     140    g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs[civCode]);
    136141    for (let phasecode of g_ParsedData.phaseList)
    137142    {
    138143        let phaseInfo = loadTechData(phasecode);
    function selectCiv(civCode)  
    170175    for (let structCode of g_Lists.structures)
    171176    {
    172177        let structInfo = g_ParsedData.structures[structCode];
     178        structInfo.phase = GetPhaseOfTemplate(structInfo);
    173179        let structPhaseIdx = g_ParsedData.phaseList.indexOf(structInfo.phase);
    174180
    175181        // If this building is shared with another civ,
    function selectCiv(civCode)  
    181187        for (let prod of structInfo.production.technology)
    182188            if (prod in techPairs)
    183189                structInfo.production.technology.splice(
    184                     structInfo.production.technology.indexOf(prod), 1,
    185                     techPairs[prod].techs[0], techPairs[prod].techs[1]
     190                    structInfo.production.technology.indexOf(prod),
     191                    1, ...techPairs[prod].techs
    186192                );
    187193
    188194        // Sort techs by phase
    189195        let newProdTech = {};
    190196        for (let prod of structInfo.production.technology)
    191197        {
    192             let phase = "";
    193 
    194             if (depath(prod).slice(0,5) === "phase")
    195             {
    196                 phase = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[prod].actualPhase);
    197                 if (phase > 0)
    198                     phase = g_ParsedData.phaseList[phase - 1];
    199             }
    200             else if (g_SelectedCiv in g_ParsedData.techs[prod].reqs)
    201             {
    202                 for (let req of g_ParsedData.techs[prod].reqs[g_SelectedCiv])
    203                     if (depath(req).slice(0,5) === "phase")
    204                         phase = req;
    205             }
    206             else if ("generic" in g_ParsedData.techs[prod].reqs)
    207             {
    208                 for (let req of g_ParsedData.techs[prod].reqs.generic)
    209                     if (depath(req).slice(0,5) === "phase")
    210                         phase = req;
    211             }
     198            let phase = GetPhaseOfTechnology(prod);
     199            if (phase === false)
     200                continue;
    212201
    213             if (depath(phase).slice(0,5) !== "phase" ||
    214                 g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
    215             {
    216                 if (structInfo.phase !== false)
    217                     phase = structInfo.phase;
    218                 else
    219                     phase = g_ParsedData.phaseList[0];
    220             }
     202            if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
     203                phase = structInfo.phase;
    221204
    222205            if (!(phase in newProdTech))
    223206                newProdTech[phase] = [];
    function selectCiv(civCode)  
    225208            newProdTech[phase].push(prod);
    226209        }
    227210
    228         // Determine phase for units
     211        // Sort units by phase
    229212        let newProdUnits = {};
    230213        for (let prod of structInfo.production.units)
    231214        {
    232215            if (!g_ParsedData.units[prod])
    233216                continue;
    234217
    235             let unit = g_ParsedData.units[prod];
    236             let phase = "";
    237 
    238             if (unit.phase !== false)
    239             {
    240                 if (g_ParsedData.phaseList.indexOf(unit.phase) < 0)
    241                     phase = g_ParsedData.phases[unit.phase].actualPhase;
    242                 else
    243                     phase = unit.phase;
    244             }
    245             else if (unit.required !== undefined)
    246             {
    247                 if (g_ParsedData.phases[unit.required])
    248                     phase = g_ParsedData.phases[unit.required].actualPhase;
    249                 else if (g_ParsedData.techs[unit.required])
    250                 {
    251                     let reqs = g_ParsedData.techs[unit.required].reqs;
    252                     if (reqs[g_SelectedCiv])
    253                         phase = reqs[g_SelectedCiv][0];
    254                     else if (reqs.generic)
    255                         phase = reqs.generic[0];
    256                     else
    257                         warn("Empty requirements found on technology " + unit.required);
    258                 }
    259                 else
    260                     warn("Technology " + unit.required + " for " + prod + " not found.");
    261             }
     218            let phase = GetPhaseOfTemplate(g_ParsedData.units[prod]);
     219            if (phase === false)
     220                continue;
    262221
    263             if (depath(phase).slice(0,5) !== "phase" || g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
    264                 if (structInfo.phase !== false)
    265                     phase = structInfo.phase;
    266                 else
    267                     phase = g_ParsedData.phaseList[0];
     222            if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
     223                phase = structInfo.phase;
    268224
    269225            if (!(phase in newProdUnits))
    270226                newProdUnits[phase] = [];
    function selectCiv(civCode)  
    279235    }
    280236
    281237    // Determine the buildList for the civ (grouped by phase)
    282     var buildList = {};
    283     var trainerList = [];
     238    let buildList = {};
     239    let trainerList = [];
    284240    for (let pha of g_ParsedData.phaseList)
    285241        buildList[pha] = [];
    286242    for (let structCode of g_Lists.structures)
    287243    {
    288         if (!g_ParsedData.structures[structCode].phase || startStructs.indexOf(structCode) > -1)
    289             g_ParsedData.structures[structCode].phase = g_ParsedData.phaseList[0];
    290 
    291         let myPhase = g_ParsedData.structures[structCode].phase;
    292         if (g_ParsedData.phaseList.indexOf(myPhase) === -1)
    293             myPhase = g_ParsedData.phases[myPhase].actualPhase;
    294 
    295         buildList[myPhase].push(structCode);
     244        let phase = g_ParsedData.structures[structCode].phase;
     245        buildList[phase].push(structCode);
    296246    }
    297247    for (let unitCode of g_Lists.units)
    298248        if (g_ParsedData.units[unitCode] && g_ParsedData.units[unitCode].production)
  • binaries/data/mods/public/simulation/ai/common-api/gamestate.js

    diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js
    index 32669ef33..b3ddef78a 100644
    a b m.GameState.prototype.init = function(SharedScript, state, player) {  
    3636        let k = techs.indexOf(this.phases[i].name);
    3737        if (k !== -1)
    3838        {
    39             this.phases[i].requirements = (this.getTemplate(techs[k]))._template.requirements;
     39            this.phases[i].requirements = DeriveTechnologyRequirements(this.getTemplate(techs[k])._template, this.civ());
    4040            continue;
    4141        }
    4242        for (let tech of techs)
    m.GameState.prototype.init = function(SharedScript, state, player) {  
    4545            if (template.replaces && template.replaces.indexOf(this.phases[i].name) != -1)
    4646            {
    4747                this.phases[i].name = tech;
    48                 this.phases[i].requirements = template.requirements;
     48                this.phases[i].requirements = DeriveTechnologyRequirements(template, this.civ());
    4949                break;
    5050            }
    5151        }
    m.GameState.prototype.cityPhase = function()  
    169169    return this.phases[2].name;
    170170};
    171171
    172 m.GameState.prototype.getPhaseRequirements = function(i)
     172m.GameState.prototype.getPhaseEntityRequirements = function(i)
    173173{
    174     if (!this.phases[i-1].requirements)
    175         return undefined;
    176     let requirements = this.phases[i-1].requirements;
    177     if (requirements.number)
    178         return requirements;
    179     else if (requirements.all)
     174    let entityReqs = [];
     175
     176    for (let requirement of this.phases[i-1].requirements)
    180177    {
    181         for (let req of requirements.all)
    182             if (req.number)
    183                 return req;
     178        if (!requirement.entities)
     179            continue;
     180        for (let entity of requirement.entities)
     181            if (entity.check == "count")
     182                entityReqs.push({
     183                    "class": entity.class,
     184                    "count": entity.number
     185                });
    184186    }
    185     return undefined;
    186 };
     187
     188    return entityReqs;
     189}
    187190
    188191m.GameState.prototype.isResearched = function(template)
    189192{
    m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec  
    213216    if (noRequirementCheck === true)
    214217        return true;
    215218
    216     // not already researched, check if we can.
    217     // basically a copy of the function in technologyManager since we can't use it.
    218     // Checks the requirements for a technology to see if it can be researched at the current time
    219 
    220     // The technology which this technology supersedes is required
    221     if (template.supersedes() && !this.playerData.researchedTechs[template.supersedes()])
    222         return false;
    223 
    224219    // if this is a pair, we must check that the pair tech is not being researched
    225220    if (template.pair())
    226221    {
    m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec  
    231226            return false;
    232227    }
    233228
    234     return this.checkTechRequirements(template.requirements());
     229    return this.checkTechRequirements(template.requirements(this.playerData.civ));
    235230};
    236231
    237232/**
    238  * Private function for checking a set of requirements is met
    239  * basically copies TechnologyManager
     233 * Private function for checking a set of requirements is met.
     234 * Basically copies TechnologyManager, but compares against
     235 * variables only available within the AI
    240236 */
    241237m.GameState.prototype.checkTechRequirements = function(reqs)
    242238{
    243     // If there are no requirements then all requirements are met
    244239    if (!reqs)
     240        return false;
     241
     242    if (!reqs.length)
    245243        return true;
    246244
    247     if (reqs.all)
    248         return reqs.all.every(r => this.checkTechRequirements(r));
    249     if (reqs.any)
    250         return reqs.any.some(r => this.checkTechRequirements(r));
    251     if (reqs.civ)
    252         return this.playerData.civ == reqs.civ;
    253     if (reqs.notciv)
    254         return this.playerData.civ != reqs.notciv;
    255     if (reqs.tech)
    256         return this.playerData.researchedTechs[reqs.tech] !== undefined && this.playerData.researchedTechs[reqs.tech];
    257     if (reqs.class && reqs.numberOfTypes)
    258         return this.playerData.typeCountsByClass[reqs.class] &&
    259             Object.keys(this.playerData.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes;
    260     if (reqs.class && reqs.number)
    261         return this.playerData.classCounts[reqs.class] &&
    262             this.playerData.classCounts[reqs.class] >= reqs.number;
    263 
    264     // The technologies requirements are not a recognised format
    265     error("Bad requirements " + uneval(reqs));
    266     return false;
     245    function doesEntitySpecPass(entity)
     246    {
     247        switch (entity.check)
     248        {
     249        case "count":
     250            if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number)
     251                return false;
     252            break;
     253
     254        case "variants":
     255            if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number)
     256                return false;
     257            break;
     258        }
     259        return true;
     260    };
     261
     262    return reqs.some(req => {
     263        return Object.keys(req).every(type => {
     264            switch (type)
     265            {
     266            case "techs":
     267                return req[type].every(tech => !!this.playerData.researchedTechs[tech]);
     268
     269            case "entities":
     270                return req[type].every(doesEntitySpecPass, this);
     271            }
     272            return false;
     273        });
     274    });
    267275};
    268276
    269277m.GameState.prototype.getMap = function()
  • binaries/data/mods/public/simulation/ai/common-api/technology.js

    diff --git a/binaries/data/mods/public/simulation/ai/common-api/technology.js b/binaries/data/mods/public/simulation/ai/common-api/technology.js
    index 14788a768..3f9a9189b 100644
    a b m.Technology.prototype.researchTime = function()  
    9797    return this._template.researchTime;
    9898};
    9999
    100 m.Technology.prototype.requirements = function()
     100m.Technology.prototype.requirements = function(civ)
    101101{
    102     if (!this._template.requirements)
    103         return undefined;
    104     return this._template.requirements;
     102    return DeriveTechnologyRequirements(this._template, civ);
    105103};
    106104
    107105m.Technology.prototype.autoResearch = function()
  • binaries/data/mods/public/simulation/ai/petra/headquarters.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js
    index 255a6fbb5..f5c1c40a5 100644
    a b m.HQ.prototype.buildTemple = function(gameState, queues)  
    12851285    {
    12861286        if (gameState.currentPhase() < 2 || this.econState !== "cityPhasing")
    12871287            return;
    1288         let requirements = gameState.getPhaseRequirements(3);
    1289         if (!requirements || !requirements.number)
    1290             return;
    1291         if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number)
     1288        let requirements = gameState.getPhaseEntityRequirements(3);
     1289        if (!requirements.length)
    12921290            return;
     1291        for (let entityReq of requirements)
     1292            if (gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length >= entityReq.count)
     1293                return;
    12931294    }
    12941295    if (!this.canBuild(gameState, "structures/{civ}_temple"))
    12951296        return;
    m.HQ.prototype.manageCorral = function(gameState, queues)  
    13991400 * build more houses if needed.
    14001401 * kinda ugly, lots of special cases to both build enough houses but not tooo many…
    14011402 */
    1402 m.HQ.prototype.buildMoreHouses = function(gameState,queues)
     1403m.HQ.prototype.buildMoreHouses = function(gameState, queues)
    14031404{
    14041405    if (gameState.getPopulationMax() <= gameState.getPopulationLimit())
    14051406        return;
    m.HQ.prototype.buildMoreHouses = function(gameState,queues)  
    14291430        queues.house.addPlan(plan);
    14301431    }
    14311432
    1432     if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseRequirements(2))
     1433    if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseEntityRequirements(2).length)
    14331434    {
    1434         let requirements = gameState.getPhaseRequirements(2);
    1435         let count = gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length;
    1436         if (requirements && count < requirements.number && this.stopBuilding.has(gameState.applyCiv("structures/{civ}_house")))
     1435        let houseTemplateName = gameState.applyCiv("structures/{civ}_house");
     1436        let houseTemplate = gameState.getTemplate(houseTemplateName);
     1437
     1438        let needed = 0;
     1439        for (let entityReq of gameState.getPhaseEntityRequirements(2))
    14371440        {
    1438             if (this.Config.debug > 1)
    1439                 API3.warn("no room to place a house ... try to be less restrictive");
    1440             this.stopBuilding.delete(gameState.applyCiv("structures/{civ}_house"));
    1441             this.requireHouses = true;
     1441            if (!houseTemplate.hasClass(entityReq.class))
     1442                continue;
     1443
     1444            let count = gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length;
     1445            if (count < entityReq.count && this.stopBuilding.has(houseTemplateName))
     1446            {
     1447                if (this.Config.debug > 1)
     1448                    API3.warn("no room to place a house ... try to be less restrictive");
     1449                this.stopBuilding.delete(houseTemplateName);
     1450                this.requireHouses = true;
     1451            }
     1452            needed = Math.max(needed, entityReq.count - count);
    14421453        }
     1454
    14431455        let houseQueue = queues.house.plans;
    14441456        for (let i = 0; i < numPlanned; ++i)
    1445         {
    14461457            if (houseQueue[i].isGo(gameState))
    1447                 ++count;
    1448             else if (count < requirements.number)
     1458                --needed;
     1459            else if (needed > 0)
    14491460            {
    14501461                houseQueue[i].isGo = function () { return true; };
    1451                 ++count;
     1462                --needed;
    14521463            }
    1453         }
    14541464    }
    14551465
    14561466    if (this.requireHouses)
    14571467    {
    1458         let requirements = gameState.getPhaseRequirements(2);
    1459         if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number)
    1460             this.requireHouses = undefined;
     1468        let houseTemplate = gameState.getTemplate(gameState.applyCiv("structures/{civ}_house"));
     1469            if (gameState.getPhaseEntityRequirements(2).every(req =>
     1470                !houseTemplate.hasClass(req.class) || gameState.getOwnStructures().filter(API3.Filters.byClass(req.class)).length >= req.count))
     1471                this.requireHouses = undefined;
    14611472    }
    14621473
    14631474    // When population limit too tight
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
    index 932c8cd3d..04ced0a3e 100644
    a b GuiInterface.prototype.GetTemplateData = function(player, extendedName)  
    658658    return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
    659659};
    660660
    661 GuiInterface.prototype.GetTechnologyData = function(player, name)
     661GuiInterface.prototype.GetTechnologyData = function(player, data)
    662662{
    663663    let cmpDataTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_DataTemplateManager);
    664     let template = cmpDataTemplateManager.GetTechnologyTemplate(name);
     664    let template = cmpDataTemplateManager.GetTechnologyTemplate(data.name);
    665665
    666666    if (!template)
    667667    {
    668         warn("Tried to get data for invalid technology: " + name);
     668        warn("Tried to get data for invalid technology: " + data.name);
    669669        return null;
    670670    }
    671671
    672672    let cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
    673     return GetTechnologyDataHelper(template, cmpPlayer.GetCiv(), Resources);
     673    return GetTechnologyDataHelper(template, data.civ || cmpPlayer.GetCiv(), Resources);
    674674};
    675675
    676676GuiInterface.prototype.IsTechnologyResearched = function(player, data)
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

    diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js
    index c9c90a51e..720cafdfe 100644
    a b ProductionQueue.prototype.GetTechnologiesList = function()  
    162162
    163163    // Remove any technologies that can't be researched by this civ
    164164    techs = techs.filter(tech => {
    165         let reqs = cmpTechnologyManager.GetTechnologyTemplate(tech).requirements || null;
     165        let reqs = DeriveTechnologyRequirements(cmpTechnologyManager.GetTechnologyTemplate(tech), cmpPlayer.GetCiv());
    166166        return cmpTechnologyManager.CheckTechnologyRequirements(reqs, true);
    167167    });
    168168
  • binaries/data/mods/public/simulation/components/TechnologyManager.js

    diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js
    index f5a17a78d..33a372df6 100644
    a b TechnologyManager.prototype.IsTechnologyResearched = function(tech)  
    9797// Checks the requirements for a technology to see if it can be researched at the current time
    9898TechnologyManager.prototype.CanResearch = function(tech)
    9999{
    100     var template = this.GetTechnologyTemplate(tech);
     100    let template = this.GetTechnologyTemplate(tech);
     101
    101102    if (!template)
    102103    {
    103104        warn("Technology \"" + tech + "\" does not exist");
    104105        return false;
    105106    }
    106107
    107     // The technology which this technology supersedes is required
    108     if (template.supersedes && !this.IsTechnologyResearched(template.supersedes))
    109         return false;
    110 
    111108    if (template.top && this.IsInProgress(template.top) ||
    112109        template.bottom && this.IsInProgress(template.bottom))
    113110        return false;
    TechnologyManager.prototype.CanResearch = function(tech)  
    121118    if (this.IsTechnologyResearched(tech))
    122119        return false;
    123120
    124     return this.CheckTechnologyRequirements(template.requirements || null);
     121    return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Player).GetCiv()));
    125122};
    126123
    127124/**
    128125 * Private function for checking a set of requirements is met
    129  * @param reqs Object of technology requirements as given by the technology template
    130  * @param civonly A boolean set to true if only the civ requirement is checked
     126 * @param {object} reqs - Technology requirements as derived from the technology template by globalscripts
     127 * @param {boolean} civonly - True if only the civ requirement is to be checked
    131128 *
    132  * @return true if the requirements are checked
    133  * false otherwise
     129 * @return true if the requirements pass, false otherwise
    134130 */
    135 TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly)
     131TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false)
    136132{
    137     // If there are no requirements then all requirements are met
     133    let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     134
    138135    if (!reqs)
     136        return false;
     137
     138    if (civonly || !reqs.length)
    139139        return true;
    140140
    141     if (reqs.all)
    142         return reqs.all.every(r => this.CheckTechnologyRequirements(r, civonly));
    143     if (reqs.any)
    144         return reqs.any.some(r => this.CheckTechnologyRequirements(r, civonly));
    145     if (reqs.civ)
    146     {
    147         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    148         return cmpPlayer && cmpPlayer.GetCiv() == reqs.civ;
    149     }
    150     if (reqs.notciv)
     141    return reqs.some(req => {
     142        return Object.keys(req).every(type => {
     143            switch (type)
     144            {
     145            case "techs":
     146                return req[type].every(this.IsTechnologyResearched, this);
     147
     148            case "entities":
     149                return req[type].every(this.DoesEntitySpecPass, this);
     150            }
     151            return false;
     152        });
     153    });
     154}
     155
     156TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
     157{
     158    switch (entity.check)
    151159    {
    152         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    153         return cmpPlayer && cmpPlayer.GetCiv() != reqs.notciv;
     160    case "count":
     161        if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
     162            return false;
     163        break;
     164
     165    case "variants":
     166        if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
     167            return false;
     168        break;
    154169    }
    155     if (civonly)
    156         return true;
    157     if (reqs.tech)
    158         return this.IsTechnologyResearched(reqs.tech);
    159     if (reqs.class && reqs.numberOfTypes)
    160         return this.typeCountsByClass[reqs.class] &&
    161             Object.keys(this.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes;
    162     if (reqs.class && reqs.number)
    163         return this.classCounts[reqs.class] &&
    164             this.classCounts[reqs.class] >= reqs.number;
    165     // The technologies requirements are not a recognised format
    166     error("Bad requirements " + uneval(reqs));
    167     return false;
     170    return true;
    168171};
    169172
    170173TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
  • new file inaries/data/mods/public/simulation/components/tests/test_Technologies.js

    diff --git a/binaries/data/mods/public/simulation/components/tests/test_Technologies.js b/binaries/data/mods/public/simulation/components/tests/test_Technologies.js
    new file mode 100644
    index 000000000..0aea0b6b6
    - +  
     1// TODO: Move this to a folder of tests for GlobalScripts (once one is created)
     2
     3// No requirements set in template
     4let template = {};
     5TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     6
     7/**
     8 * First, the basics:
     9 */
     10
     11// Technology Requirement
     12template.requirements = { "tech": "expected_tech" };
     13TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
     14
     15// Entity Requirement: Count of entities matching given class
     16template.requirements = { "entity": { "class": "Village", "number": 5 } };
     17TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
     18
     19// Entity Requirement: Count of entities matching given class
     20template.requirements = { "entity": { "class": "Village", "numberOfTypes": 5 } };
     21TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
     22
     23// Single `civ`
     24template.requirements = { "civ": "athen" };
     25TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     26TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
     27
     28// Single `notciv`
     29template.requirements = { "notciv": "athen" };
     30TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     31TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), []);
     32
     33
     34/**
     35 * Basic `all`s:
     36 */
     37
     38// Multiple techs
     39template.requirements = { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
     40TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C"] }]);
     41
     42// Multiple entity definitions
     43template.requirements = {
     44    "all": [
     45        { "entity": { "class": "class_A", "number": 5 } },
     46        { "entity": { "class": "class_B", "number": 5 } },
     47        { "entity": { "class": "class_C", "number": 5 } }
     48    ]
     49};
     50TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"),
     51    [{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "count" }, { "class": "class_C", "number": 5, "check": "count" }] }]);
     52
     53// A `tech` and an `entity` (went to sea, in a beautiful pea-green boat)
     54template.requirements = { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
     55TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
     56
     57// Multiple `civ`s
     58template.requirements = { "all": [{"civ": "civ_A"}, {"civ": "civ_B"}, {"civ": "civ_C"}] };
     59TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []);
     60TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []);
     61TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []);
     62TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false);
     63
     64// Multiple `notciv`s
     65template.requirements = { "all": [{"notciv": "civ_A"}, {"notciv": "civ_B"}, {"notciv": "civ_C"}] };
     66TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false);
     67TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false);
     68TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false);
     69TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
     70
     71// A `civ` with a tech/entity
     72template.requirements = { "all": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
     73TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]);
     74TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
     75
     76template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
     77TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
     78TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
     79
     80template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
     81TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
     82TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false);
     83
     84// A `notciv` with a tech/entity
     85template.requirements = { "all": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
     86TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     87TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
     88
     89template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
     90TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     91TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
     92
     93template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
     94TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     95TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
     96
     97
     98/**
     99 * Basic `any`s:
     100 */
     101
     102// Multiple techs
     103template.requirements = { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] };
     104TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "techs": ["tech_B"] }, { "techs": ["tech_C"] }]);
     105
     106// Multiple entity definitions
     107template.requirements = {
     108    "any": [
     109        { "entity": { "class": "class_A", "number": 5 } },
     110        { "entity": { "class": "class_B", "number": 5 } },
     111        { "entity": { "class": "class_C", "number": 5 } }
     112    ]
     113};
     114TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     115    { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
     116    { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] },
     117    { "entities": [{ "class": "class_C", "number": 5, "check": "count" }] }
     118]);
     119
     120// A tech or an entity
     121template.requirements = { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] };
     122TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]);
     123
     124// Multiple `civ`s
     125template.requirements = { "any": [{"civ": "civ_A"}, {"civ": "civ_B"}, {"civ": "civ_C"}] };
     126TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []);
     127TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []);
     128TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []);
     129TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false);
     130
     131// Multiple `notciv`s
     132template.requirements = { "any": [{"notciv": "civ_A"}, {"notciv": "civ_B"}, {"notciv": "civ_C"}] };
     133TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false);
     134TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false);
     135TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false);
     136TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []);
     137
     138// A `civ` or a tech/entity
     139template.requirements = { "any": [{ "civ": "athen" }, { "tech": "expected_tech" }] };
     140TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     141TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
     142
     143template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
     144TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     145TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
     146
     147template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
     148TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     149TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
     150
     151// A `notciv` or a tech
     152template.requirements = { "any": [{ "notciv": "athen" }, { "tech": "expected_tech" }] };
     153TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     154TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]);
     155
     156template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] };
     157TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     158TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]);
     159
     160template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] };
     161TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     162TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]);
     163
     164
     165/**
     166 * Complicated `all`s, part 1 - an `all` inside an `all`:
     167 */
     168
     169// Techs
     170template.requirements = {
     171    "all": [
     172        { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
     173        { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
     174    ]
     175};
     176TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C", "tech_D"] }]);
     177
     178// Techs and entities
     179template.requirements = {
     180    "all": [
     181        { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
     182        { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
     183    ]
     184};
     185TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{
     186    "techs": ["tech_A", "tech_B"],
     187    "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }]
     188}]);
     189
     190// Two `civ`s, without and with a tech
     191template.requirements = {
     192    "all": [
     193        { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
     194    ]
     195};
     196TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     197TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     198
     199template.requirements = {
     200    "all": [
     201        { "tech": "required_tech" },
     202        { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
     203    ]
     204};
     205TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     206TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     207
     208// Two `notciv`s, without and with a tech
     209template.requirements = {
     210    "all": [
     211        { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     212    ]
     213};
     214TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     215TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
     216
     217template.requirements = {
     218    "all": [
     219        { "tech": "required_tech" },
     220        { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     221    ]
     222};
     223TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     224TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     225
     226// Inner `all` has a tech and a `civ`/`notciv`
     227template.requirements = {
     228    "all": [
     229        { "all": [{ "tech": "tech_A" }, { "civ": "maur" }] },
     230        { "tech": "tech_B" }
     231    ]
     232};
     233TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_B"] }]);
     234TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_A", "tech_B"] }]);
     235
     236template.requirements = {
     237    "all": [
     238        { "all": [{ "tech": "tech_A" }, { "notciv": "maur" }] },
     239        { "tech": "tech_B" }
     240    ]
     241}
     242TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B"] }]);
     243TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_B"] }]);
     244
     245
     246/**
     247 * Complicated `all`s, part 2 - an `any` inside an `all`:
     248 */
     249
     250// Techs
     251template.requirements = {
     252    "all": [
     253        { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
     254        { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
     255    ]
     256};
     257TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     258    { "techs": ["tech_A", "tech_C"] },
     259    { "techs": ["tech_A", "tech_D"] },
     260    { "techs": ["tech_B", "tech_C"] },
     261    { "techs": ["tech_B", "tech_D"] }
     262]);
     263
     264// Techs and entities
     265template.requirements = {
     266    "all": [
     267        { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
     268        { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
     269    ]
     270};
     271TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     272    { "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
     273    { "techs": ["tech_A", "tech_B"] },
     274    { "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] },
     275    { "entities": [{ "class": "class_A", "number": 5, "check": "count" }], "techs": ["tech_B"] }
     276]);
     277
     278// Two `civ`s, without and with a tech
     279template.requirements = {
     280    "all": [
     281        { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
     282    ]
     283};
     284TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     285TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     286
     287template.requirements = {
     288    "all": [
     289        { "tech": "required_tech" },
     290        { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
     291    ]
     292};
     293TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     294TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     295
     296// Two `notciv`s, without and with a tech
     297template.requirements = {
     298    "all": [
     299        { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     300    ]
     301};
     302TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     303TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
     304
     305template.requirements = {
     306    "all": [
     307        { "tech": "required_tech" },
     308        { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     309    ]
     310};
     311TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     312TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     313
     314
     315/**
     316 * Complicated `any`s, part 1 - an `all` inside an `any`:
     317 */
     318
     319// Techs
     320template.requirements = {
     321    "any": [
     322        { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
     323        { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
     324    ]
     325};
     326TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     327    { "techs": ["tech_A", "tech_B"] },
     328    { "techs": ["tech_C", "tech_D"] }
     329]);
     330
     331// Techs and entities
     332template.requirements = {
     333    "any": [
     334        { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
     335        { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
     336    ]
     337};
     338TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     339    { "techs": ["tech_A"], "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
     340    { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }], "techs": ["tech_B"] }
     341]);
     342
     343// Two `civ`s, without and with a tech
     344template.requirements = {
     345    "any": [
     346        { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
     347    ]
     348};
     349TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     350TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     351
     352template.requirements = {
     353    "any": [
     354        { "tech": "required_tech" },
     355        { "all": [{ "civ": "athen" }, { "civ": "spart" }] }
     356    ]
     357};
     358// Note: these requirements don't really make sense, as the `any` makes the `civ`s in the the inner `all` irrelevant.
     359// We test it anyway as a precursor to later tests.
     360TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     361TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     362
     363// Two `notciv`s, without and with a tech
     364template.requirements = {
     365    "any": [
     366        { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     367    ]
     368};
     369TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     370TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
     371
     372template.requirements = {
     373    "any": [
     374        { "tech": "required_tech" },
     375        { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     376    ]
     377};
     378// Note: these requirements have a result that might seen unexpected at first glance.
     379// This is because the `notciv`s are rendered irrelevant by the `any`, and they have nothing else to operate on.
     380// We test it anyway as a precursor for later tests.
     381TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     382TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     383
     384// Inner `all` has a tech and a `civ`/`notciv`
     385template.requirements = {
     386    "any": [
     387        { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
     388        { "tech": "tech2" }
     389    ]
     390};
     391TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
     392TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
     393
     394template.requirements = {
     395    "any": [
     396        { "all": [{ "notciv": "civA" }, { "tech": "tech1" }] },
     397        { "tech": "tech2" }
     398    ]
     399};
     400TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech2"] }]);
     401TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]);
     402
     403
     404/**
     405 * Complicated `any`s, part 2 - an `any` inside an `any`:
     406 */
     407
     408// Techs
     409template.requirements = {
     410    "any": [
     411        { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] },
     412        { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] }
     413    ]
     414};
     415TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     416    { "techs": ["tech_A"] },
     417    { "techs": ["tech_B"] },
     418    { "techs": ["tech_C"] },
     419    { "techs": ["tech_D"] }
     420]);
     421
     422// Techs and entities
     423template.requirements = {
     424    "any": [
     425        { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] },
     426        { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] }
     427    ]
     428};
     429TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [
     430    { "techs": ["tech_A"] },
     431    { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] },
     432    { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] },
     433    { "techs": ["tech_B"] }
     434]);
     435
     436// Two `civ`s, without and with a tech
     437template.requirements = {
     438    "any": [
     439        { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
     440    ]
     441};
     442TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []);
     443TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false);
     444
     445template.requirements = {
     446    "any": [
     447        { "tech": "required_tech" },
     448        { "any": [{ "civ": "athen" }, { "civ": "spart" }] }
     449    ]
     450};
     451// These requirements may not make sense, as the `civ`s are unable to restrict the requirements due to the outer `any`
     452TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     453TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     454
     455// Two `notciv`s, without and with a tech
     456template.requirements = {
     457    "any": [
     458        { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     459    ]
     460};
     461TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false);
     462TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []);
     463
     464template.requirements = {
     465    "any": [
     466        { "tech": "required_tech" },
     467        { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] }
     468    ]
     469};
     470// These requirements may not make sense, as the `notciv`s are made irrelevant by the outer `any`
     471TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]);
     472TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]);
     473
     474
     475/**
     476 * Further tests
     477 */
     478
     479template.requirements = {
     480    "all": [
     481        { "tech": "tech1" },
     482        { "any": [{ "civ": "civA" }, { "civ": "civB" }] },
     483        { "notciv": "civC" }
     484    ]
     485};
     486TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
     487TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
     488
     489template.requirements = {
     490    "any": [
     491        { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
     492        { "all": [{ "civ": "civB" }, { "tech": "tech2" }] }
     493    ]
     494};
     495TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
     496TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]);
     497TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false);
     498
     499template.requirements = {
     500    "any": [
     501        { "all": [{ "civ": "civA" }, { "tech": "tech1" }] },
     502        { "all": [
     503            { "any": [{ "civ": "civB" }, { "civ": "civC" }] },
     504            { "tech": "tech2" }
     505        ] }
     506    ]
     507};
     508TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]);
     509TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), [{ "techs": ["tech2"] }]);
     510TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civD"), false);
  • binaries/data/mods/public/simulation/data/technologies/phase_city.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_city.json b/binaries/data/mods/public/simulation/data/technologies/phase_city.json
    index 809a20696..c14fed5dd 100644
    a b  
    66    },
    77    "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology.",
    88    "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 },
    9     "requirements": { "all": [{ "class": "Town", "number": 4 }, { "notciv": "athen" }] },
     9    "requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "notciv": "athen" }] },
    1010    "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).",
    1111    "supersedes": "phase_town",
    1212    "icon": "city_phase.png",
  • binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json b/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json
    index a2c2b1085..2aaf38dd9 100644
    a b  
    55    },
    66    "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.",
    77    "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 },
    8     "requirements": { "all": [{ "class": "Town", "number": 4 }, { "civ": "athen" }] },
     8    "requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "civ": "athen" }] },
    99    "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).",
    1010    "supersedes": "phase_town_athen",
    1111    "replaces": ["phase_city"],
  • binaries/data/mods/public/simulation/data/technologies/phase_town.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_town.json b/binaries/data/mods/public/simulation/data/technologies/phase_town.json
    index 087435dbb..2bb12d10f 100644
    a b  
    66    },
    77    "description": "Advances from a small village to a bustling town, ready to expand rapidly.",
    88    "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 },
    9     "requirements": { "all": [{ "class": "Village", "number": 5 }, { "notciv": "athen" }] },
     9    "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "notciv": "athen" }] },
    1010    "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).",
    1111    "supersedes": "phase_village",
    1212    "icon": "town_phase.png",
  • binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json b/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json
    index e5ae4bf0f..9cf810c49 100644
    a b  
    55    },
    66    "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.",
    77    "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 },
    8     "requirements": { "all": [{ "class": "Village", "number": 5 }, { "civ": "athen" }] },
     8    "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] },
    99    "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).",
    1010    "supersedes": "phase_village",
    1111    "replaces": ["phase_town"],
  • binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json b/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json
    index 8a969440a..134223281 100644
    a b  
    55    "requirements": {
    66        "all": [
    77            { "tech": "phase_city" },
    8             {
    9                 "all": [
    10                     { "notciv": "brit"},
    11                     { "notciv": "gaul" },
    12                     { "notciv": "iber" },
    13                     { "notciv": "maur" },
    14                     { "notciv": "pers" },
    15                     { "notciv": "sele" }
    16                 ]
    17             }
     8            { "notciv": "brit"},
     9            { "notciv": "gaul" },
     10            { "notciv": "iber" },
     11            { "notciv": "maur" },
     12            { "notciv": "pers" },
     13            { "notciv": "sele" }
    1814        ]
    1915    },
    2016    "requirementsTooltip": "Unlocked in City Phase.",
  • binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json

    diff --git a/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json b/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json
    index 57f8ad689..d6007e5ff 100644
    a b  
    22    "genericName": "Diaspora",
    33    "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.",
    44    "cost": { "food": 200, "wood": 200, "stone": 100, "metal": 100 },
    5     "requirements": { "class": "Trader", "number": 3 },
     5    "requirements": { "entity": { "class": "Trader", "number": 3 } },
    66    "requirementsTooltip": "Requires 3 Traders",
    77    "supersedes": "unlock_shared_los",
    88    "icon": "diaspora.png",