Ticket #3993: notciv_v5-1.patch

File notciv_v5-1.patch, 37.2 KB (added by s0600204, 8 years ago)

Updated to deal with problem with auto-generated requirements tooltip. For those looking for a diff of changes 'tween v5 and v5.1 -> https://github.com/s0600204/0ad/commit/5d0ac4e4689b9ca294146013edd542801a802f72

  • 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..764bd45 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
     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        {
     104            if (!requirements.generic.techs)
     105                requirements.generic.techs = [];
     106            requirements.generic.techs.push(template.supersedes);
     107        }
     108        else if (!Object.keys(requirements).length)
     109            requirements.generic = { "techs": [template.supersedes] };
     110        else
     111            for (let ck in requirements)
     112                if (requirements[ck])
     113                {
     114                    if (!requirements[ck].techs)
     115                        requirements[ck].techs = [];
     116                    requirements[ck].techs.push(template.supersedes);
     117                }
     118    }
     119
     120    return requirements;
     121}
     122
     123/**
     124 * Interprets the prerequisite requirements of a technology.
     125 *
     126 * Takes the initial { key: value } from the short-form requirements object in entity templates,
     127 * and parses it into an object that can be more easily checked by simulation and gui.
     128 *
     129 * Works recursively if needed.
     130 *
     131 * The returned object is in the form:
     132 * ```
     133 *  {
     134 *      "civA": {
     135 *          "techs": ["tech1"]
     136 *      },
     137 *      "civB": {
     138 *          "entities": [{
     139 *              "class": "human",
     140 *              "number": 2,
     141 *              "variant": "count"
     142 *          }]
     143 *      },
     144 *      "civC": false,
     145 *      "generic": {
     146 *          "techs": ["tech2"]
     147 *      }
     148 *  }
     149 * ```
     150 * (Or, to translate:
     151 * - `civA` needs `tech1`
     152 * - `civB` needs 2 entities with the `human` class
     153 * - `civC` cannot research this tech at all
     154 * - and everyone else needs `tech2`)
     155 *
     156 * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
     157 * @param {mixed} value - The value associated with the above operation.
     158 *
     159 * @return Object containing the requirements, sorted.
     160 */
     161function InterpretTechRequirements(operator, value)
     162{
     163    let requirements = {};
     164
     165    switch (operator)
     166    {
     167    case "civ":
     168        requirements[value] = {};
     169        break;
     170
     171    case "notciv":
     172        requirements[value] = false;
     173        requirements.generic = {};
     174        break;
     175
     176    case "class":
     177    case "number":
     178    case "numberOfTypes":
     179        // do nothing
     180        break;
     181
     182    case "entity":
     183        requirements.generic = { "entities": [{
     184            "class": value.class,
     185            "number": value.number || value.numberOfTypes,
     186            "check": value.number ? "count" : "variants"
     187        }]};
     188        break;
     189
     190    case "tech":
     191        requirements.generic = { "techs": [value] };
     192        break;
     193
     194    case "all":
     195    {
     196        let civs = [];
     197        let techs = [];
     198        let entities = [];
     199        for (let subvalue of value)
     200            for (let newOper in subvalue)
     201            {
     202                let newValue = subvalue[newOper];
     203                let result = InterpretTechRequirements(newOper, newValue)
     204
     205                switch (newOper)
     206                {
     207                case "civ":
     208                    civs.push(Object.keys(result)[0]);
     209                    break;
     210
     211                case "notciv":
     212                    for (let civ in result)
     213                        requirements[civ] = result[civ];
     214                    break;
     215
     216                case "tech":
     217                    techs = techs.concat(result.generic.techs);
     218                    break;
     219
     220                case "entity":
     221                    entities = entities.concat(result.generic.entities);
     222                    break;
     223
     224                case "any":
     225                case "all":
     226                {
     227                    for (let civ in result)
     228                        if (result[civ] === false)
     229                            requirements[civ] = false;
     230                        else if (newOper === "any")
     231                            civs.push(civ);
     232                }
     233                break;
     234
     235                }
     236            }
     237        if (!civs.length && (techs.length || entities.length))
     238        {
     239            requirements.generic = {};
     240            if (techs.length)
     241                requirements.generic.techs = techs;
     242            if (entities.length)
     243                requirements.generic.entities = entities;
     244        }
     245        else
     246            for (let civ of civs)
     247            {
     248                requirements[civ] = {};
     249                if (techs.length)
     250                    requirements[civ].techs = techs;
     251                if (entities.length)
     252                    requirements[civ].entities = entities;
     253            }
     254    }
     255    break;
     256
     257    case "any":
     258    {
     259        for (let subvalue of value)
     260            for (let newOper in subvalue)
     261            {
     262                let newValue = subvalue[newOper];
     263                let result = InterpretTechRequirements(newOper, newValue)
     264
     265                switch (newOper)
     266                {
     267                case "civ":
     268                case "notciv":
     269                    for (let civ in result)
     270                        requirements[civ] = result[civ];
     271                    break;
     272
     273                case "tech":
     274                    if (!requirements.generic)
     275                        requirements.generic = { "techs": [] };
     276                    requirements.generic.techs.push(result.generic.techs);
     277                    break;
     278
     279                case "entity":
     280                    if (!requirements.generic)
     281                        requirements.generic = { "entities": [] };
     282                    requirements.generic.entities.push(result.generic.entities);
     283                    break;
     284
     285                case "all":
     286                    for (let civ in result)
     287                    {
     288                        requirements[civ] = {};
     289                        if (result[civ].techs)
     290                            requirements[civ].techs = result[civ].techs;
     291                        if (result[civ].entities)
     292                            requirements[civ].entities = result[civ].entities;
     293                    }
     294                    break;
     295
     296                case "any":
     297                    for (let civ in result)
     298                    {
     299                        if (!requirements[civ])
     300                        {
     301                            warn("Possible any/all req operator problem - "+operator +":"+uneval(value));
     302                            requirements[civ] = {};
     303                        }
     304                        if (result[civ].techs)
     305                            for (let val of result[civ].techs)
     306                                requirements.generic.techs.push(val);
     307                        if (result[civ].entities)
     308                            for (let val of result[civ].entities)
     309                                requirements.generic.entities.push(val);
     310                    }
     311                    break;
     312                }
     313            }
     314    }
     315    break;
     316
     317    default:
     318        warn("Unknown requirement operator: "+operator);
     319    }
     320
     321    return requirements;
     322}
  • 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 c21fc4f..46a77d3 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                if (reqs.entities)
    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                    let entityCounts = [];
     771                    for (let entity of reqs.entities)
     772                    {
     773                        let current = 0;
     774                        switch (entity.check)
     775                        {
     776                        case "count":
     777                            current = GetSimState().players[player].classCounts[entity.class] || 0;
     778                            break;
     779
     780                        case "variants":
     781                            current = GetSimState().players[player].typeCountsByClass[entity.class] ? Object.keys(GetSimState().players[player].typeCountsByClass[entity.class]).length : 0;
     782                            break;
     783                        }
     784
     785                        let remaining = entity.number - current;
     786                        if (remaining < 1)
     787                            continue;
     788
     789                        entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
     790                            "number": remaining,
     791                            "class": entity.class
     792                        }));
     793                    }
     794                    tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
     795                        "entityCounts": entityCounts.join(translate(", "))
    773796                    });
    774797                }
    775798                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 eef5c6c..8c7fe49 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 req of techReqs.techs)
     131    {
     132        if (depath(req).slice(0, 5) === "phase")
     133            return req;
     134        phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(req)));
     135    }
     136    return g_ParsedData.phaseList[phaseIdx] || false;
     137}
     138
     139function GetActualPhase(phaseName)
     140{
     141    if (g_ParsedData.phases[phaseName])
     142        return g_ParsedData.phases[phaseName].actualPhase;
     143
     144    warn("Unrecognised phase (" + techName + ")");
     145    return g_ParsedData.phaseList[0];
     146}
     147
     148function GetPhaseOfTemplate(template)
     149{
     150    if (!template.requiredTechnology)
     151        return g_ParsedData.phaseList[0];
     152
     153    if (depath(template.requiredTechnology).slice(0, 5) == "phase")
     154        return GetActualPhase(template.requiredTechnology);
     155
     156    return GetPhaseOfTechnology(template.requiredTechnology);
     157}
  • 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..c93b56a 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.techs || techdata.reqs.generic.techs.length < 2)
    347210            continue;
    348211
    349         let reqTech = techs[techcode].reqs.generic[1];
     212        let reqTech = techs[techcode].reqs.generic.techs[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.techs[0];
     222        let myPhase = techs[techcode].reqs.generic.techs[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..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",