Ticket #3993: notciv_v4.patch

File notciv_v4.patch, 30.1 KB (added by s0600204, 8 years ago)

As requested in irc, moved to globalscripts.

  • 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 b6783e2..ce75e28 100644
    a b function DoesModificationApply(modification, classes)  
    5050{
    5151    return MatchesClassList(classes, modification.affects);
    5252}
     53
     54
     55/**
     56 * Fetch the civ-specific technology requirements of a technology
     57 *
     58 * @param {object} techReqs - The technology requirements, derived from
     59 *                            InterpretTechRequirements
     60 * @param {string} civ - The civ
     61 *
     62 * @return An object containing the requirements specific to this civ
     63 *         for this technology or false if the civ cannot research this
     64 *         technology
     65 */
     66function GetCivSpecificReqsOfTech(techReqs, civ)
     67{
     68    if (civ in techReqs)
     69    {
     70        if (techReqs[civ] === false)
     71            return false;
     72        return techReqs[civ] || {};
     73    }
     74
     75    if (techReqs.generic)
     76        return techReqs.generic || {};
     77
     78    return !Object.keys(techReqs).length;
     79}
     80
     81function DeriveTechnologyRequirements(template)
     82{
     83    let requirements = {}
     84
     85    if (template.requirements)
     86    {
     87        let op = Object.keys(template.requirements)[0];
     88        let val = template.requirements[op];
     89        requirements = InterpretTechRequirements(op, val);
     90    }
     91
     92    if (template.supersedes)
     93    {
     94        if (requirements.generic)
     95        {
     96            if (!requirements.generic.techs)
     97                requirements.generic.techs = [];
     98            requirements.generic.techs.push(template.supersedes);
     99        }
     100        else if (!Object.keys(requirements).length)
     101            requirements.generic = { "techs": [template.supersedes] };
     102        else
     103            for (let ck in requirements)
     104                if (requirements[ck])
     105                {
     106                    if (!requirements[ck].techs)
     107                        requirements[ck].techs = [];
     108                    requirements[ck].techs.push(template.supersedes);
     109                }
     110    }
     111
     112    return requirements;
     113}
     114
     115/**
     116 * Interprets the prerequisite requirements of a technology.
     117 *
     118 * Takes the initial { key: value } from the short-form requirements object in entity templates,
     119 * and parses it into an object that can be more easily checked by simulation and gui.
     120 *
     121 * Works recursively if needed.
     122 *
     123 * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
     124 * @param {mixed} value - The value associated with the above operation.
     125 *
     126 * @return Object containing the requirements, sorted.
     127 */
     128function InterpretTechRequirements(operator, value)
     129{
     130    let requirements = {};
     131
     132    switch (operator)
     133    {
     134    case "civ":
     135        requirements[value] = {};
     136        break;
     137
     138    case "notciv":
     139        requirements[value] = false;
     140        break;
     141
     142    case "class":
     143    case "number":
     144        // do nothing
     145        break;
     146
     147    case "entity":
     148        requirements.generic = { "entities": [{
     149            "class": value.class,
     150            "number": value.number || value.numberOfTypes,
     151            "check": value.number ? "count" : "variants"
     152        }]};
     153        break;
     154
     155    case "tech":
     156        if (depath(value).slice(0,4) === "pair")
     157            return { "generic": loadTechnologyPair(value).techs };
     158        return { "generic": { "techs": [value] } };
     159
     160    case "all":
     161    {
     162        let civs = [];
     163        let techs = [];
     164        let entities = [];
     165        for (let subvalue of value)
     166            for (let newOper in subvalue)
     167            {
     168                let newValue = subvalue[newOper];
     169                let result = InterpretTechRequirements(newOper, newValue)
     170
     171                switch (newOper)
     172                {
     173                case "civ":
     174                    civs.push(Object.keys(result)[0]);
     175                    break;
     176
     177                case "notciv":
     178                    requirements[Object.keys(result)[0]] = false;
     179                    break;
     180
     181                case "tech":
     182                    techs = techs.concat(result.generic.techs);
     183                    break;
     184
     185                case "entity":
     186                    entities = entities.concat(result.generic.entities);
     187                    break;
     188
     189                case "any":
     190                case "all":
     191                {
     192                    if (result.generic)
     193                        techs = techs.concat(result.generic);
     194                    else
     195                        for (let civ in result)
     196                            if (result[civ] === false)
     197                                requirements[civ] = false;
     198                            else if (newOper === "any")
     199                                civs.push(civ);
     200                            else
     201                                warn("Incomprehensible technology requirements - "+operator +":"+uneval(value));
     202                }
     203                break;
     204
     205                }
     206            }
     207        if (!civs.length && (techs.length || entities.length))
     208        {
     209            requirements.generic = {};
     210            if (techs.length)
     211                requirements.generic.techs = techs;
     212            if (entities.length)
     213                requirements.generic.entities = entities;
     214        }
     215        else
     216            for (let civ of civs)
     217            {
     218                requirements[civ] = {};
     219                if (techs.length)
     220                    requirements[civ].techs = techs;
     221                if (entities.length)
     222                    requirements[civ].entities = entities;
     223            }
     224    }
     225    break;
     226
     227    case "any":
     228    {
     229        for (let subvalue of value)
     230            for (let newOper in subvalue)
     231            {
     232                let newValue = subvalue[newOper];
     233                let result = InterpretTechRequirements(newOper, newValue)
     234
     235                switch (newOper)
     236                {
     237                case "civ":
     238                    requirements[Object.keys(result)[0]] = {};
     239                    break;
     240
     241                case "notciv":
     242                    requirements[Object.keys(result)[0]] = false;
     243                    break;
     244
     245                case "tech":
     246                    if (!requirements.generic)
     247                        requirements.generic = { "techs": [] };
     248                    requirements.generic.techs.push(result.generic.techs);
     249                    break;
     250
     251                case "entity":
     252                    if (!requirements.generic)
     253                        requirements.generic = { "entities": [] };
     254                    requirements.generic.entities.push(result.generic.entities);
     255                    break;
     256
     257                case "all":
     258                    for (let civ in result)
     259                    {
     260                        requirements[civ].techs = result[civ].techs;
     261                    }
     262                    break;
     263
     264                case "any":
     265                    for (let civ in result)
     266                    {
     267                        if (!requirements[civ])
     268                        {
     269                            warn("Possible any/all req operator problem - "+operator +":"+uneval(value));
     270                            requirements[civ] = {};
     271                        }
     272                        for (let val of result[civ])
     273                            requirements.generic.techs.push(val);
     274                    }
     275                    break;
     276                }
     277            }
     278    }
     279    break;
     280
     281    default:
     282        warn("Unknown requirement operator: "+operator);
     283    }
     284
     285    return requirements;
     286}
  • 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 9b622db..8f76311 100644
    a b function GetTechnologyDataHelper(template, civ)  
    371371    ret.tooltip = template.tooltip;
    372372    ret.requirementsTooltip = template.requirementsTooltip || "";
    373373
    374     if (template.requirements && template.requirements.class)
    375         ret.classRequirements = {
    376             "class": template.requirements.class,
    377             "number": template.requirements.number
    378         };
     374    ret.reqs = DeriveTechnologyRequirements(template);
    379375
    380376    ret.description = template.description;
    381377
  • 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 6146b46..fa216c9 100644
    a b function shuffleArray(source)  
    3434    }
    3535    return result;
    3636}
     37
     38function depath(path)
     39{
     40    return path.slice(path.lastIndexOf("/") + 1);
     41}
  • 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 c21fc4f..66e4103 100644
    a b g_SelectionPanels.Research = {  
    731731        setPanelObjectPosition(pair, data.i, data.rowLength);
    732732
    733733        // Handle one or two techs
     734        let player = data.unitEntState.player;
    734735        for (let i in techs)
    735736        {
    736737            let tech = techs[i];
    g_SelectionPanels.Research = {  
    743744
    744745            let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
    745746                "cost": template.cost,
    746                 "player": data.unitEntState.player
     747                "player": player
    747748            });
    748749
    749750            let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
    750751                "tech": tech,
    751                 "player": data.unitEntState.player
     752                "player": player
    752753            });
    753754
    754755            let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
    g_SelectionPanels.Research = {  
    763764            if (!requirementsPassed)
    764765            {
    765766                let tip = template.requirementsTooltip;
    766                 if (template.classRequirements)
    767                 {
    768                     let player = data.unitEntState.player;
    769                     let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0;
    770                     let remaining = template.classRequirements.number - current;
    771                     tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), {
    772                         "number": remaining
    773                     });
    774                 }
     767                let reqs = GetCivSpecificReqsOfTech(template.reqs, GetSimState().players[player].civ);
     768                if (reqs.entities)
     769                    for (let entity of reqs.entities)
     770                        if (entity.check == "count")
     771                        {
     772                            let current = GetSimState().players[player].classCounts[entity.class] || 0;
     773                            let remaining = entity.number - current;
     774                            tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), {
     775                                "number": remaining
     776                            });
     777                        }
    775778                tooltips.push(tip);
    776779            }
    777780            tooltips.push(getNeededResourcesTooltip(neededResources));
  • 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 eef5c6c..7d2f706 100644
    a b function fetchTokens(templateName, keypath)  
    9292    return val._string.split(" ");
    9393}
    9494
    95 function depath(path)
    96 {
    97     return path.slice(path.lastIndexOf("/") + 1);
    98 }
    99 
    10095/**
    10196 * This is needed because getEntityCostTooltip in tooltip.js needs to get
    10297 * the template data of the different wallSet pieces. In the session this
    function GetTemplateData(templateName)  
    107102    var template = loadTemplate(templateName);
    108103    return GetTemplateDataHelper(template, null, g_AuraData);
    109104}
     105
     106/**
     107 * Determines and returns the phase of a given technology. Works
     108 * recursively through the given tech's pre-requisite and superseded
     109 * techs if necessary.
     110 *
     111 * @param {string} techName - The Technology's name
     112 * @return The name of the phase the technology belongs to, or false if
     113 *         the current civ can't research this tech
     114 */
     115function GetPhaseOfTechnology(techName)
     116{
     117    let phaseIdx = -1;
     118
     119    if (depath(techName).slice(0, 5) === "phase")
     120    {
     121        phaseIdx = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[techName].actualPhase);
     122        if (phaseIdx > 0)
     123            return g_ParsedData.phaseList[phaseIdx - 1];
     124        warn("Unrecognised phase (" + techName + ")");
     125        return false;
     126    }
     127
     128    let techReqs = GetCivSpecificReqsOfTech(g_ParsedData.techs[techName].reqs, g_SelectedCiv);
     129    if (!techReqs)
     130        return false;
     131
     132    for (let req of techReqs.techs)
     133    {
     134        if (depath(req).slice(0, 5) === "phase")
     135            return req;
     136        phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(req)));
     137    }
     138    return g_ParsedData.phaseList[phaseIdx] || false;
     139}
  • 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 534747b..050cddd 100644
    a b function loadStructure(templateName)  
    179179
    180180function loadTechnology(techName)
    181181{
    182     var template = loadTechData(techName);
    183     var tech = GetTechnologyDataHelper(template, g_SelectedCiv);
    184     tech.reqs = {};
     182    let template = loadTechData(techName);
     183    let tech = GetTechnologyDataHelper(template, g_SelectedCiv);
    185184
    186185    if (template.pair !== undefined)
    187186        tech.pair = template.pair;
    188187
    189     if (template.requirements !== undefined)
    190     {
    191         for (let op in template.requirements)
    192         {
    193             let val = template.requirements[op];   
    194             let req = calcReqs(op, val);
    195 
    196             switch (op)
    197             {
    198             case "tech":
    199                 tech.reqs.generic = req;
    200                 break;
    201 
    202             case "civ":
    203                 tech.reqs[req] = [];
    204                 break;
    205 
    206             case "any":
    207                 if (req[0].length > 0)
    208                     for (let r of req[0])
    209                     {
    210                         let v = req[0][r];
    211                         if (typeof r == "number")
    212                             tech.reqs[v] = [];
    213                         else
    214                             tech.reqs[r] = v;
    215                     }
    216                 if (req[1].length > 0)
    217                     tech.reqs.generic = req[1];
    218                 break;
    219 
    220             case "all":
    221                 if (!req[0].length)
    222                     tech.reqs.generic = req[1];
    223                 else
    224                     for (let r of req[0])
    225                         tech.reqs[r] = req[1];
    226                 break;
    227             }
    228         }
    229     }
    230 
    231     if (template.supersedes !== undefined)
    232     {
    233         if (tech.reqs.generic !== undefined)
    234             tech.reqs.generic.push(template.supersedes);
    235         else
    236             for (let ck of Object.keys(tech.reqs))
    237                 tech.reqs[ck].push(template.supersedes);
    238     }
    239 
    240188    return tech;
    241189}
    242190
    function loadTechnologyPair(pairCode)  
    263211}
    264212
    265213/**
    266  * Calculate the prerequisite requirements of a technology.
    267  * Works recursively if needed.
    268  *
    269  * @param op The base operation. Can be "civ", "tech", "all" or "any".
    270  * @param val The value associated with the above operation.
    271  *
    272  * @return Sorted requirments.
    273  */
    274 function calcReqs(op, val)
    275 {
    276     switch (op)
    277     {
    278     case "civ":
    279     case "class":
    280     case "notciv":
    281     case "number":
    282         // nothing needs doing
    283         break;
    284 
    285     case "tech":
    286         if (depath(val).slice(0,4) === "pair")
    287             return loadTechnologyPair(val).techs;
    288         return [ val ];
    289 
    290     case "all":
    291     case "any":
    292         let t = [];
    293         let c = [];
    294         for (let nv of val)
    295         {
    296             for (let o in nv)
    297             {
    298                 let v = nv[o];
    299                 let r = calcReqs(o, v);
    300                 switch (o)
    301                 {
    302                 case "civ":
    303                 case "notciv":
    304                     c.push(r);
    305                     break;
    306 
    307                 case "tech":
    308                     t = t.concat(r);
    309                     break;
    310 
    311                 case "any":
    312                     c = c.concat(r[0]);
    313                     t = t.concat(r[1]);
    314                     break;
    315 
    316                 case "all":
    317                     for (let ci in r[0])
    318                         c[ci] = r[1];
    319                     t = t;
    320                 }
    321             }
    322         }
    323         return [ c, t ];
    324 
    325     default:
    326         warn("Unknown reqs operator: "+op);
    327     }
    328     return val;
    329 }
    330 
    331 /**
    332214 * Unravel phases
    333215 *
    334216 * @param techs The current available store of techs
    function unravelPhases(techs)  
    343225    {
    344226        let techdata = techs[techcode];
    345227
    346         if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2)
     228        if (!techdata.reqs.generic || !techdata.reqs.generic.techs || techdata.reqs.generic.techs.length < 2)
    347229            continue;
    348230
    349         let reqTech = techs[techcode].reqs.generic[1];
     231        let reqTech = techs[techcode].reqs.generic.techs[1];
    350232
    351233        // Tech that can't be researched anywhere
    352234        if (!(reqTech in techs))
    function unravelPhases(techs)  
    355237        if (!("generic" in techs[reqTech].reqs))
    356238            continue;
    357239
    358         let reqPhase = techs[reqTech].reqs.generic[0];
    359         let myPhase = techs[techcode].reqs.generic[0];
     240        let reqPhase = techs[reqTech].reqs.generic.techs[0];
     241        let myPhase = techs[techcode].reqs.generic.techs[0];
    360242
    361243        if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase")
    362244            continue;
    function unravelPhases(techs)  
    364246        let reqPhasePos = phaseList.indexOf(reqPhase);
    365247        let myPhasePos = phaseList.indexOf(myPhase);
    366248
    367         if (phaseList.length === 0)
     249        if (!phaseList.length)
    368250            phaseList = [reqPhase, myPhase];
    369251        else if (reqPhasePos < 0 && myPhasePos > -1)
    370252            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 65bfd9e..1415022 100644
    a b function selectCiv(civCode)  
    188188        let newProdTech = {};
    189189        for (let prod of structInfo.production.technology)
    190190        {
    191             let phase = "";
     191            let phase = GetPhaseOfTechnology(prod);
    192192
    193             if (depath(prod).slice(0,5) === "phase")
    194             {
    195                 phase = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[prod].actualPhase);
    196                 if (phase > 0)
    197                     phase = g_ParsedData.phaseList[phase - 1];
    198             }
    199             else if (g_SelectedCiv in g_ParsedData.techs[prod].reqs)
    200             {
    201                 for (let req of g_ParsedData.techs[prod].reqs[g_SelectedCiv])
    202                     if (depath(req).slice(0,5) === "phase")
    203                         phase = req;
    204             }
    205             else if ("generic" in g_ParsedData.techs[prod].reqs)
    206             {
    207                 for (let req of g_ParsedData.techs[prod].reqs.generic)
    208                     if (depath(req).slice(0,5) === "phase")
    209                         phase = req;
    210             }
     193            if (phase === false)
     194                continue;
    211195
    212             if (depath(phase).slice(0,5) !== "phase" ||
    213                 g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
     196            if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
    214197            {
    215198                if (structInfo.phase !== false)
    216199                    phase = structInfo.phase;
    function selectCiv(civCode)  
    243226            }
    244227            else if (unit.required !== undefined)
    245228            {
    246                 if (g_ParsedData.phases[unit.required])
    247                     phase = g_ParsedData.phases[unit.required].actualPhase;
    248                 else if (g_ParsedData.techs[unit.required])
    249                 {
    250                     let reqs = g_ParsedData.techs[unit.required].reqs;
    251                     if (reqs[g_SelectedCiv])
    252                         phase = reqs[g_SelectedCiv][0];
    253                     else if (reqs.generic)
    254                         phase = reqs.generic[0];
    255                     else
    256                         warn("Empty requirements found on technology " + unit.required);
    257                 }
    258                 else
    259                     warn("Technology " + unit.required + " for " + prod + " not found.");
     229                phase = GetPhaseOfTechnology(unit.required);
     230                if (phase === false)
     231                    continue;
    260232            }
    261233
    262234            if (depath(phase).slice(0,5) !== "phase" || g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
  • 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 2aad561..bae3a05 100644
    a b m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec  
    199199
    200200    if (noRequirementCheck === true)
    201201        return true;
    202    
    203     // not already researched, check if we can.
    204     // basically a copy of the function in technologyManager since we can't use it.
    205     // Checks the requirements for a technology to see if it can be researched at the current time
    206        
    207     // The technology which this technology supersedes is required
    208     if (template.supersedes() && !this.playerData.researchedTechs[template.supersedes()])
    209         return false;
    210202
    211203    // if this is a pair, we must check that the pair tech is not being researched
    212204    if (template.pair())
    m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec  
    222214};
    223215
    224216/**
    225  * Private function for checking a set of requirements is met
    226  * basically copies TechnologyManager
     217 * Private function for checking a set of requirements is met.
     218 * Basically copies TechnologyManager, but compares against
     219 * variables only available within the AI
    227220 */
    228221m.GameState.prototype.checkTechRequirements = function(reqs)
    229222{
    230     // If there are no requirements then all requirements are met
     223    reqs = GetCivSpecificReqsOfTech(reqs, this.playerData.civ);
     224
    231225    if (!reqs)
    232         return true;
    233    
    234     if (reqs.all)
    235         return reqs.all.every(r => this.checkTechRequirements(r));
    236     if (reqs.any)
    237         return reqs.any.some(r => this.checkTechRequirements(r));
    238     if (reqs.civ)
    239         return this.playerData.civ == reqs.civ;
    240     if (reqs.notciv)
    241         return this.playerData.civ != reqs.notciv;
    242     if (reqs.tech)
    243         return this.playerData.researchedTechs[reqs.tech] !== undefined && this.playerData.researchedTechs[reqs.tech];
    244     if (reqs.class && reqs.numberOfTypes)
    245         return this.playerData.typeCountsByClass[reqs.class] &&
    246             Object.keys(this.playerData.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes;
    247     if (reqs.class && reqs.number)
    248         return this.playerData.classCounts[reqs.class] &&
    249             this.playerData.classCounts[reqs.class] >= reqs.number;
    250    
    251     // The technologies requirements are not a recognised format
    252     error("Bad requirements " + uneval(reqs));
    253     return false;
     226        return false;
     227
     228    if (reqs.techs)
     229        for (let prerequisite of reqs.techs)
     230            if (!this.playerData.researchedTechs[prerequisite])
     231                return false;
     232
     233    if (reqs.entities)
     234        for (let entity of reqs.entities)
     235        {
     236            switch (entity.check)
     237            {
     238            case "count":
     239                if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number)
     240                    return false;
     241                break;
     242
     243            case "variants":
     244                if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number)
     245                    return false;
     246                break;
     247            }
     248        }
     249
     250    return true;
    254251};
    255252
    256253m.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 09f6504..5ae1fc5 100644
    a b m.Technology.prototype.researchTime = function()  
    9999
    100100m.Technology.prototype.requirements = function()
    101101{
    102     if (!this._template.requirements)
    103         return undefined;
    104     return this._template.requirements;
     102    return DeriveTechnologyRequirements(this._template);
    105103};
    106104
    107105m.Technology.prototype.autoResearch = function()
  • 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 6c72202..6d8cb98 100644
    a b ProductionQueue.prototype.GetTechnologiesList = function()  
    168168
    169169    // Remove any technologies that can't be researched by this civ
    170170    techs = techs.filter(tech => {
    171         let reqs = cmpTechnologyManager.GetTechnologyTemplate(tech).requirements || null;
     171        let reqs = DeriveTechnologyRequirements(cmpTechnologyManager.GetTechnologyTemplate(tech));
    172172        return cmpTechnologyManager.CheckTechnologyRequirements(reqs, true);
    173173    });
    174174
  • 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 f1f9cc7..610e3bc 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));
    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    reqs = GetCivSpecificReqsOfTech(reqs, cmpPlayer.GetCiv());
     135
    138136    if (!reqs)
    139         return true;
     137        return false;
    140138
    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)
    151     {
    152         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    153         return cmpPlayer && cmpPlayer.GetCiv() != reqs.notciv;
    154     }
    155139    if (civonly)
    156140        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;
    168 };
     141
     142    if (reqs.techs)
     143        for (let prerequisite of reqs.techs)
     144            if (!this.IsTechnologyResearched(prerequisite))
     145                return false;
     146
     147    if (reqs.entities)
     148        for (let entity of reqs.entities)
     149        {
     150            switch (entity.check)
     151            {
     152            case "count":
     153                if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
     154                    return false;
     155                break;
     156
     157            case "variants":
     158                if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
     159                    return false;
     160                break;
     161            }
     162        }
     163
     164    return true;
     165}
    169166
    170167TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
    171168{
  • 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 01c5345..73d37a4 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 97b63da..42dd624 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 087435d..2bb12d1 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 e5ae4bf..9cf810c 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/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 57f8ad6..d6007e5 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",