Ticket #3993: notciv_v6-1.patch

File notciv_v6-1.patch, 39.2 KB (added by s0600204, 8 years ago)

Fix small problem with techs with no requirements at all.

  • 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..072bfea 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 array 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 ? [] : false;
     79}
     80
     81/**
     82 * Derives the technology requirements from a given technology template.
     83 * Takes into account the `supersedes` attribute.
     84 *
     85 * @param {object} template - The template object. Loading of the template must have already occured.
     86 *
     87 * @return Derived technology requirements. See `InterpretTechRequirements` for object's syntax.
     88 */
     89function DeriveTechnologyRequirements(template)
     90{
     91    let requirements = {}
     92
     93    if (template.requirements)
     94    {
     95        let op = Object.keys(template.requirements)[0];
     96        let val = template.requirements[op];
     97        requirements = InterpretTechRequirements(op, val);
     98    }
     99
     100    if (template.supersedes)
     101    {
     102        if (requirements.generic)
     103            for (let k in requirements.generic)
     104            {
     105                if (!requirements.generic[k].techs)
     106                    requirements.generic[k].techs = [[]];
     107                for (let t in requirements.generic[k].techs)
     108                    requirements.generic[k].techs[t].push(template.supersedes);
     109            }
     110        else if (!Object.keys(requirements).length)
     111            requirements.generic = [{ "techs": [[template.supersedes]] }];
     112        else
     113            for (let civ in requirements)
     114                if (requirements[civ])
     115                    for (let k in requirements[civ])
     116                    {
     117                        if (!requirements[civ][k].techs)
     118                            requirements[civ][k].techs = [[]];
     119                        for (let t in requirements[civ][k].techs)
     120                            requirements[civ][k].techs[t].push(template.supersedes);
     121                    }
     122    }
     123
     124    return requirements;
     125}
     126
     127/**
     128 * Interprets the prerequisite requirements of a technology.
     129 *
     130 * Takes the initial { key: value } from the short-form requirements object in entity templates,
     131 * and parses it into an object that can be more easily checked by simulation and gui.
     132 *
     133 * Works recursively if needed.
     134 *
     135 * The returned object is in the form:
     136 * ```
     137 *  {
     138 *      "civA": [
     139 *          { "techs": [["tech1", "tech2"]] }
     140 *      ],
     141 *      "civB": [
     142 *          { "entities": [[{
     143 *              "class": "human",
     144 *              "number": 2,
     145 *              "variant": "count"
     146 *          }]] }
     147 *      ],
     148 *      "civC": false,
     149 *      "generic": [
     150 *          { "techs": [["tech3"], ["tech4"]] }
     151 *      ]
     152 *  }
     153 * ```
     154 * (Or, to translate:
     155 * - `civA` needs both `tech1` and `tech2`
     156 * - `civB` needs 2 entities with the `human` class
     157 * - `civC` cannot research this tech at all
     158 * - and everyone else needs either `tech2` or `tech3`)
     159 *
     160 * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
     161 * @param {mixed} value - The value associated with the above operation.
     162 *
     163 * @return Object containing the requirements, sorted.
     164 */
     165function InterpretTechRequirements(operator, value)
     166{
     167    let requirements = {};
     168    const subReqTypes = ["techs", "entities"];
     169
     170    switch (operator)
     171    {
     172    case "civ":
     173        requirements[value] = [];
     174        break;
     175
     176    case "notciv":
     177        requirements[value] = false;
     178        requirements.generic = [];
     179        break;
     180
     181    case "class":
     182    case "number":
     183    case "numberOfTypes":
     184        // do nothing
     185        break;
     186
     187    case "entity":
     188        requirements.generic = [{ "entities": [[{
     189            "class": value.class,
     190            "number": value.number || value.numberOfTypes,
     191            "check": value.number ? "count" : "variants"
     192        }]] }];
     193        break;
     194
     195    case "tech":
     196        requirements.generic = [{ "techs": [[value]] }];
     197        break;
     198
     199    case "all":
     200    {
     201        let civs = [];
     202        let subReqs = [{}];
     203        for (let subvalue of value)
     204            for (let newOper in subvalue)
     205            {
     206                let newValue = subvalue[newOper];
     207                let result = InterpretTechRequirements(newOper, newValue)
     208
     209                let subtype = subReqTypes.length;
     210                switch (newOper)
     211                {
     212                case "civ":
     213                    civs.push(Object.keys(result)[0]);
     214                    break;
     215
     216                case "notciv":
     217                    for (let civ in result)
     218                        requirements[civ] = result[civ];
     219                    break;
     220
     221                case "tech":
     222                    --subtype;
     223                case "entity":
     224                    subtype = subReqTypes[--subtype];
     225                    for (let sr in subReqs)
     226                    {
     227                        if (!subReqs[sr][subtype])
     228                            subReqs[sr][subtype] = [[]];
     229                        for (let e in subReqs[sr][subtype])
     230                            subReqs[sr][subtype][e] = subReqs[sr][subtype][e].concat(result.generic[0][subtype][0]);
     231                    }
     232                    break;
     233
     234                case "any":
     235                {
     236                    for (let civ in result)
     237                        if (civ === "generic")
     238                        {
     239                            let newReqs = [];
     240                            for (let sr in subReqs)
     241                                for (let type of subReqTypes)
     242                                    for (let option of result.generic)
     243                                        if (option[type])
     244                                            for (let t in option[type])
     245                                                if (!subReqs[sr][type] || !subReqs[sr][type].length)
     246                                                    newReqs.push(option);
     247                                                else
     248                                                    for (let k in subReqs[sr][type])
     249                                                    {
     250                                                        if (!newReqs[sr])
     251                                                            newReqs[sr] = {};
     252                                                        if (!newReqs[sr][type])
     253                                                            newReqs[sr][type] = [];
     254                                                        newReqs[sr][type].push(subReqs[sr][type][k].concat(option[type][t]));
     255                                                    }
     256                            if (newReqs.length)
     257                                subReqs = newReqs;
     258                        }
     259                        else if (result[civ] === false)
     260                            requirements[civ] = false;
     261                        else
     262                            civs.push(civ);
     263                }
     264                break
     265
     266                case "all":
     267                {
     268                    for (let civ in result)
     269                    {
     270                        if (civ === "generic" && result.generic[0])
     271                        {
     272                            for (let sr in subReqs)
     273                                for (let type of subReqTypes)
     274                                    if (result.generic[0][type])
     275                                    {
     276                                        if (!subReqs[sr][type])
     277                                            subReqs[sr][type] = [];
     278                                        if (subReqs[sr][type].length)
     279                                            for (let k in result.generic[0][type])
     280                                                result.generic[0][type][k] = subReqs[sr][type][0].concat(result.generic[0][type][k]);
     281                                        subReqs[sr][type] = result.generic[0][type];
     282                                    }
     283                        }
     284                        else if (result[civ] === false)
     285                        {
     286                            requirements[civ] = false;
     287                            requirements.generic = [];
     288                        }
     289                        else if (civ !== "generic")
     290                            civs.push(civ);
     291                    }
     292                }
     293                break;
     294
     295                }
     296            }
     297
     298        if (!Object.keys(subReqs[0]).length)
     299            subReqs = [];
     300
     301        if (!civs.length && subReqs.length)
     302            requirements.generic = subReqs;
     303        else
     304            for (let civ of civs)
     305                requirements[civ] = subReqs;
     306    }
     307    break;
     308
     309    case "any":
     310    {
     311        for (let subvalue of value)
     312            for (let newOper in subvalue)
     313            {
     314                let newValue = subvalue[newOper];
     315                let result = InterpretTechRequirements(newOper, newValue)
     316
     317                let subtype = subReqTypes.length;
     318                switch (newOper)
     319                {
     320                case "civ":
     321                case "notciv":
     322                    for (let civ in result)
     323                        requirements[civ] = result[civ];
     324                    break;
     325
     326                case "tech":
     327                    --subtype;
     328                case "entity":
     329                {
     330                    subtype = subReqTypes[--subtype];
     331
     332                    if (!requirements.generic)
     333                        requirements.generic = [];
     334                    let newReq = {};
     335                    newReq[subtype] = [result.generic[0][subtype][0]];
     336                    requirements.generic.push(newReq);
     337                }
     338                break;
     339
     340                case "all":
     341                    for (let civ in result)
     342                    {
     343                        requirements[civ] = [{}];
     344                        for (let type of subReqTypes)
     345                            if (result[civ][0][type])
     346                                requirements[civ][0][type] = result[civ][0][type];
     347                    }
     348                    break;
     349
     350                case "any":
     351                    for (let civ in result)
     352                    {
     353                        if (!requirements[civ])
     354                            requirements[civ] = [];
     355                        for (let opt of result[civ])
     356                            requirements[civ].push(opt);
     357                    }
     358                    break;
     359                }
     360            }
     361    }
     362    break;
     363
     364    default:
     365        warn("Unknown requirement operator: "+operator);
     366    }
     367
     368    return requirements;
     369}
  • 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..eaef316 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 depath(path)
     44{
     45    return path.slice(path.lastIndexOf("/") + 1);
     46}
  • 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 fb12b00..6ee4fbf 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                let reqs = GetCivSpecificReqsOfTech(template.reqs, GetSimState().players[player].civ);
     768                for (let req of reqs)
    767769                {
    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
     770                    if (!req.entities)
     771                        continue;
     772
     773                    let entityCounts = [];
     774                    for (let option of req.entities)
     775                        for (let entity of option)
     776                        {
     777                            let current = 0;
     778                            switch (entity.check)
     779                            {
     780                            case "count":
     781                                current = GetSimState().players[player].classCounts[entity.class] || 0;
     782                                break;
     783
     784                            case "variants":
     785                                current = GetSimState().players[player].typeCountsByClass[entity.class] ? Object.keys(GetSimState().players[player].typeCountsByClass[entity.class]).length : 0;
     786                                break;
     787                            }
     788
     789                            let remaining = entity.number - current;
     790                            if (remaining < 1)
     791                                continue;
     792
     793                            entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
     794                                "number": remaining,
     795                                "class": entity.class
     796                            }));
     797                        }
     798
     799                    tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
     800                        "entityCounts": entityCounts.join(translate(", "))
    773801                    });
    774802                }
    775803                tooltips.push(tip);
  • 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 e5f76b6..6502fb5 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 in which a given technology can be
     108 * first researched. Works recursively through the given tech's
     109 * pre-requisite and superseded 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(GetActualPhase(techName));
     122        if (phaseIdx > 0)
     123            return g_ParsedData.phaseList[phaseIdx - 1];
     124    }
     125
     126    let techReqs = GetCivSpecificReqsOfTech(g_ParsedData.techs[techName].reqs, g_SelectedCiv);
     127    if (!techReqs)
     128        return false;
     129
     130    for (let option in techReqs)
     131        if (techReqs[option].techs)
     132            for (let techs in techReqs[option].techs)
     133                for (let req of techReqs[option].techs[techs])
     134                {
     135                    if (depath(req).slice(0, 5) === "phase")
     136                        return req;
     137                    phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(req)));
     138                }
     139    return g_ParsedData.phaseList[phaseIdx] || false;
     140}
     141
     142function GetActualPhase(phaseName)
     143{
     144    if (g_ParsedData.phases[phaseName])
     145        return g_ParsedData.phases[phaseName].actualPhase;
     146
     147    warn("Unrecognised phase (" + techName + ")");
     148    return g_ParsedData.phaseList[0];
     149}
     150
     151function GetPhaseOfTemplate(template)
     152{
     153    if (!template.requiredTechnology)
     154        return g_ParsedData.phaseList[0];
     155
     156    if (depath(template.requiredTechnology).slice(0, 5) == "phase")
     157        return GetActualPhase(template.requiredTechnology);
     158
     159    return GetPhaseOfTechnology(template.requiredTechnology);
     160}
  • 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 1dfd332..82c0784 100644
    a b function loadUnit(templateName)  
    3939{
    4040    if (!Engine.TemplateExists(templateName))
    4141        return null;
    42     var template = loadTemplate(templateName);
    43 
    44     var unit = GetTemplateDataHelper(template, null, g_AuraData);
    45     unit.phase = false;
    46 
    47     if (unit.requiredTechnology)
    48     {
    49         if (depath(unit.requiredTechnology).slice(0, 5) == "phase")
    50             unit.phase = unit.requiredTechnology;
    51         else if (unit.requiredTechnology.length)
    52             unit.required = unit.requiredTechnology;
    53     }
     42    let template = loadTemplate(templateName);
     43    let unit = GetTemplateDataHelper(template, null, g_AuraData);
    5444
    5545    unit.gather = getGatherRates(templateName);
    5646
    function loadUnit(templateName)  
    9383
    9484function loadStructure(templateName)
    9585{
    96     var template = loadTemplate(templateName);
    97     var structure = GetTemplateDataHelper(template, null, g_AuraData);
    98     structure.phase = false;
    99 
    100     if (structure.requiredTechnology)
    101     {
    102         if (depath(structure.requiredTechnology).slice(0, 5) == "phase")
    103             structure.phase = structure.requiredTechnology;
    104         else if (structure.requiredTechnology.length)
    105             structure.required = structure.requiredTechnology;
    106     }
     86    let template = loadTemplate(templateName);
     87    let structure = GetTemplateDataHelper(template, null, g_AuraData);
    10788
    10889    structure.production = {
    10990        "technology": [],
    function loadStructure(templateName)  
    179160
    180161function loadTechnology(techName)
    181162{
    182     var template = loadTechData(techName);
    183     var tech = GetTechnologyDataHelper(template, g_SelectedCiv);
    184     tech.reqs = {};
     163    let template = loadTechData(techName);
     164    let tech = GetTechnologyDataHelper(template, g_SelectedCiv);
    185165
    186166    if (template.pair !== undefined)
    187167        tech.pair = template.pair;
    188168
    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 
    240169    return tech;
    241170}
    242171
    function loadTechnologyPair(pairCode)  
    258187
    259188    return {
    260189        "techs": [ pairInfo.top, pairInfo.bottom ],
    261         "req": pairInfo.supersedes || ""
     190        "reqs": DeriveTechnologyRequirements(pairInfo)
    262191    };
    263192}
    264193
    265194/**
    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 /**
    332195 * Unravel phases
    333196 *
    334197 * @param techs The current available store of techs
    function unravelPhases(techs)  
    343206    {
    344207        let techdata = techs[techcode];
    345208
    346         if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2)
     209        if (!techdata.reqs.generic || !techdata.reqs.generic[0].techs || techdata.reqs.generic[0].techs[0].length < 2)
    347210            continue;
    348211
    349         let reqTech = techs[techcode].reqs.generic[1];
     212        let reqTech = techs[techcode].reqs.generic[0].techs[0][1];
    350213
    351214        // Tech that can't be researched anywhere
    352215        if (!(reqTech in techs))
    function unravelPhases(techs)  
    355218        if (!("generic" in techs[reqTech].reqs))
    356219            continue;
    357220
    358         let reqPhase = techs[reqTech].reqs.generic[0];
    359         let myPhase = techs[techcode].reqs.generic[0];
     221        let reqPhase = techs[reqTech].reqs.generic[0].techs[0][0];
     222        let myPhase = techs[techcode].reqs.generic[0].techs[0][0];
    360223
    361224        if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase")
    362225            continue;
    function unravelPhases(techs)  
    364227        let reqPhasePos = phaseList.indexOf(reqPhase);
    365228        let myPhasePos = phaseList.indexOf(myPhase);
    366229
    367         if (phaseList.length === 0)
     230        if (!phaseList.length)
    368231            phaseList = [reqPhase, myPhase];
    369232        else if (reqPhasePos < 0 && myPhasePos > -1)
    370233            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..04d6049 100644
    a b function selectCiv(civCode)  
    6262    };
    6363
    6464    // get initial units
    65     var startStructs = [];
    6665    for (let entity of g_CivData[civCode].StartEntities)
    6766    {
    6867        if (entity.Template.slice(0, 5) == "units")
    6968            g_Lists.units.push(entity.Template);
    7069        else if (entity.Template.slice(0, 6) == "struct")
    71         {
    7270            g_Lists.structures.push(entity.Template);
    73             startStructs.push(entity.Template);
    74         }
    7571    }
    7672
    7773    // Load units and structures
    function selectCiv(civCode)  
    115111            else
    116112            {
    117113                let newTech = loadTechnology(techcode);
    118                 if (pair.req !== "")
     114                for (let civ in pair.reqs)
    119115                {
    120                     if ("generic" in newTech.reqs)
    121                         newTech.reqs.generic.concat(techPairs[pair.req].techs);
    122                     else
     116                    if (!newTech.reqs[civ])
     117                        newTech.reqs[civ] = {};
     118                    else if (newTech.reqs[civ] === false)
     119                        continue;
     120
     121                    if (pair.reqs[civ] === false)
    123122                    {
    124                         for (let civkey of Object.keys(newTech.reqs))
    125                             newTech.reqs[civkey].concat(techPairs[pair.req].techs);
     123                        newTech.reqs[civ] = false;
     124                        continue;
    126125                    }
     126
     127                    for (let type in pair.reqs[civ])
     128                        if (pair.reqs[civ][type])
     129                            newTech.reqs[civ][type].concat(pair.reqs[civ][type])
    127130                }
    128131                g_ParsedData.techs[techcode] = newTech;
    129132            }
    function selectCiv(civCode)  
    169172    for (let structCode of g_Lists.structures)
    170173    {
    171174        let structInfo = g_ParsedData.structures[structCode];
     175        structInfo.phase = GetPhaseOfTemplate(structInfo);
    172176        let structPhaseIdx = g_ParsedData.phaseList.indexOf(structInfo.phase);
    173177
    174178        // If this building is shared with another civ,
    function selectCiv(civCode)  
    180184        for (let prod of structInfo.production.technology)
    181185            if (prod in techPairs)
    182186                structInfo.production.technology.splice(
    183                     structInfo.production.technology.indexOf(prod), 1,
    184                     techPairs[prod].techs[0], techPairs[prod].techs[1]
     187                    structInfo.production.technology.indexOf(prod),
     188                    1, ...techPairs[prod].techs
    185189                );
    186190
    187191        // Sort techs by phase
    188192        let newProdTech = {};
    189193        for (let prod of structInfo.production.technology)
    190194        {
    191             let phase = "";
    192 
    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             }
     195            let phase = GetPhaseOfTechnology(prod);
     196            if (phase === false)
     197                continue;
    211198
    212             if (depath(phase).slice(0,5) !== "phase" ||
    213                 g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
    214             {
    215                 if (structInfo.phase !== false)
    216                     phase = structInfo.phase;
    217                 else
    218                     phase = g_ParsedData.phaseList[0];
    219             }
     199            if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
     200                phase = structInfo.phase;
    220201
    221202            if (!(phase in newProdTech))
    222203                newProdTech[phase] = [];
    function selectCiv(civCode)  
    224205            newProdTech[phase].push(prod);
    225206        }
    226207
    227         // Determine phase for units
     208        // Sort units by phase
    228209        let newProdUnits = {};
    229210        for (let prod of structInfo.production.units)
    230211        {
    231212            if (!g_ParsedData.units[prod])
    232213                continue;
    233214
    234             let unit = g_ParsedData.units[prod];
    235             let phase = "";
    236 
    237             if (unit.phase !== false)
    238             {
    239                 if (g_ParsedData.phaseList.indexOf(unit.phase) < 0)
    240                     phase = g_ParsedData.phases[unit.phase].actualPhase;
    241                 else
    242                     phase = unit.phase;
    243             }
    244             else if (unit.required !== undefined)
    245             {
    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.");
    260             }
     215            let phase = GetPhaseOfTemplate(g_ParsedData.units[prod]);
     216            if (phase === false)
     217                continue;
    261218
    262             if (depath(phase).slice(0,5) !== "phase" || g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
    263                 if (structInfo.phase !== false)
    264                     phase = structInfo.phase;
    265                 else
    266                     phase = g_ParsedData.phaseList[0];
     219            if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx)
     220                phase = structInfo.phase;
    267221
    268222            if (!(phase in newProdUnits))
    269223                newProdUnits[phase] = [];
    function selectCiv(civCode)  
    278232    }
    279233
    280234    // Determine the buildList for the civ (grouped by phase)
    281     var buildList = {};
    282     var trainerList = [];
     235    let buildList = {};
     236    let trainerList = [];
    283237    for (let pha of g_ParsedData.phaseList)
    284238        buildList[pha] = [];
    285239    for (let structCode of g_Lists.structures)
    286240    {
    287         if (!g_ParsedData.structures[structCode].phase || startStructs.indexOf(structCode) > -1)
    288             g_ParsedData.structures[structCode].phase = g_ParsedData.phaseList[0];
    289 
    290         let myPhase = g_ParsedData.structures[structCode].phase;
    291         if (g_ParsedData.phaseList.indexOf(myPhase) === -1)
    292             myPhase = g_ParsedData.phases[myPhase].actualPhase;
    293 
    294         buildList[myPhase].push(structCode);
     241        let phase = g_ParsedData.structures[structCode].phase;
     242        buildList[phase].push(structCode);
    295243    }
    296244    for (let unitCode of g_Lists.units)
    297245        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 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..b10c278 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)
     137        return false;
     138
     139    if (civonly || !reqs.length)
    139140        return true;
    140141
    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)
     142    return reqs.some(req => {
     143        return Object.keys(req).every(type => {
     144            switch (type)
     145            {
     146            case "techs":
     147                return req[type].some(tech => tech.every(this.IsTechnologyResearched, this));
     148
     149            case "entities":
     150                return req[type].some(entity => entity.every(this.DoesEntitySpecPass, this));
     151            }
     152            return false;
     153        });
     154    });
     155}
     156
     157TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
     158{
     159    switch (entity.check)
    151160    {
    152         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    153         return cmpPlayer && cmpPlayer.GetCiv() != reqs.notciv;
     161    case "count":
     162        if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
     163            return false;
     164        break;
     165
     166    case "variants":
     167        if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
     168            return false;
     169        break;
    154170    }
    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;
     171    return true;
    168172};
    169173
    170174TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
  • 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",