LCOV - code coverage report
Current view: top level - globalscripts - Technologies.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 127 161 78.9 %
Date: 2023-04-02 12:52:40 Functions: 7 10 70.0 %

          Line data    Source code
       1             : /**
       2             :  * This file contains shared logic for applying tech modifications in GUI, AI,
       3             :  * and simulation scripts. As such it must be fully deterministic and not store
       4             :  * any global state, but each context should do its own caching as needed.
       5             :  * Also it cannot directly access the simulation and requires data passed to it.
       6             :  */
       7             : 
       8             : /**
       9             :  * Returns modified property value modified by the applicable tech
      10             :  * modifications.
      11             :  *
      12             :  * @param modifications array of modificiations
      13             :  * @param classes Array containing the class list of the template.
      14             :  * @param originalValue Number storing the original value. Can also be
      15             :  * non-numeric, but then only "replace" and "tokens" techs can be supported.
      16             :  */
      17             : function GetTechModifiedProperty(modifications, classes, originalValue)
      18             : {
      19          52 :         if (!modifications.length)
      20           0 :                 return originalValue;
      21             : 
      22             :         // From indicative profiling, splitting in two sub-functions or checking directly
      23             :         // is about as efficient, but splitting makes it easier to report errors.
      24          52 :         if (typeof originalValue === "string")
      25           9 :                 return GetTechModifiedProperty_string(modifications, classes, originalValue);
      26          43 :         if (typeof originalValue === "number")
      27          43 :                 return GetTechModifiedProperty_numeric(modifications, classes, originalValue);
      28           0 :         return GetTechModifiedProperty_generic(modifications, classes, originalValue);
      29             : }
      30             : 
      31             : function GetTechModifiedProperty_generic(modifications, classes, originalValue)
      32             : {
      33           0 :         for (let modification of modifications)
      34             :         {
      35           0 :                 if (!DoesModificationApply(modification, classes))
      36           0 :                         continue;
      37           0 :                 if (!modification.replace)
      38           0 :                         warn("GetTechModifiedProperty: modification format not recognised : " + uneval(modification));
      39             : 
      40           0 :                 return modification.replace;
      41             :         }
      42             : 
      43           0 :         return originalValue;
      44             : }
      45             : 
      46             : function GetTechModifiedProperty_numeric(modifications, classes, originalValue)
      47             : {
      48          43 :         let multiply = 1;
      49          43 :         let add = 0;
      50             : 
      51          43 :         for (let modification of modifications)
      52             :         {
      53          82 :                 if (!DoesModificationApply(modification, classes))
      54          35 :                         continue;
      55          47 :                 if (modification.replace !== undefined)
      56           6 :                         return modification.replace;
      57          41 :                 if (modification.multiply)
      58           3 :                         multiply *= modification.multiply;
      59          38 :                 else if (modification.add)
      60          38 :                         add += modification.add;
      61             :                 else
      62           0 :                         warn("GetTechModifiedProperty: numeric modification format not recognised : " + uneval(modification));
      63             :         }
      64          37 :         return originalValue * multiply + add;
      65             : }
      66             : 
      67             : function GetTechModifiedProperty_string(modifications, classes, originalValue)
      68             : {
      69           9 :         let value = originalValue;
      70           9 :         for (let modification of modifications)
      71             :         {
      72          12 :                 if (!DoesModificationApply(modification, classes))
      73           5 :                         continue;
      74           7 :                 if (modification.replace !== undefined)
      75           1 :                         return modification.replace;
      76             :                 // Multiple token replacement works, though ordering is not technically guaranteed.
      77             :                 // In practice, the order will be that of 'research', which ought to be fine,
      78             :                 // and operations like adding tokens are order-independent anyways,
      79             :                 // but modders beware if replacement or deletions are implemented.
      80           6 :                 if (modification.tokens !== undefined)
      81           6 :                         value = HandleTokens(value, modification.tokens);
      82             :                 else
      83           0 :                         warn("GetTechModifiedProperty: string modification format not recognised : " + uneval(modification));
      84             :         }
      85           8 :         return value;
      86             : }
      87             : 
      88             : 
      89             : /**
      90             :  * Returns whether the given modification applies to the entity containing the given class list
      91             :  * NB: returns true if modifications.affects is empty, to allow "affects anything" modifiers.
      92             :  */
      93             : function DoesModificationApply(modification, classes)
      94             : {
      95          95 :         if (!modification.affects || !modification.affects.length)
      96           1 :                 return true;
      97          94 :         return MatchesClassList(classes, modification.affects);
      98             : }
      99             : 
     100             : /**
     101             :  * Returns a modified list of tokens.
     102             :  * Supports "A>B" to replace A by B, "-A" to remove A, and the rest will add tokens.
     103             :  */
     104             : function HandleTokens(originalValue, modification)
     105             : {
     106          13 :         let tokens = originalValue === "" ? [] : originalValue.split(/\s+/);
     107          13 :         let newTokens = modification === "" ? [] : modification.split(/\s+/);
     108          13 :         for (let token of newTokens)
     109             :         {
     110          36 :                 if (token.indexOf(">") !== -1)
     111             :                 {
     112          11 :                         let [oldToken, newToken] = token.split(">");
     113          11 :                         let index = tokens.indexOf(oldToken);
     114          11 :                         if (index !== -1)
     115           5 :                                 tokens[index] = newToken;
     116             :                 }
     117          25 :                 else if (token[0] == "-")
     118             :                 {
     119          12 :                         let index = tokens.indexOf(token.substr(1));
     120          12 :                         if (index !== -1)
     121           4 :                                 tokens.splice(index, 1);
     122             :                 }
     123             :                 else
     124          13 :                         tokens.push(token);
     125             :         }
     126          13 :         return tokens.join(" ");
     127             : }
     128             : 
     129             : /**
     130             :  * Derives the technology requirements from a given technology template.
     131             :  * Takes into account the `supersedes` attribute.
     132             :  *
     133             :  * @param {Object} template - The template object. Loading of the template must have already occured.
     134             :  *
     135             :  * @return Derived technology requirements. See `InterpretTechRequirements` for object's syntax.
     136             :  */
     137             : function DeriveTechnologyRequirements(template, civ)
     138             : {
     139         144 :         let requirements = [];
     140             : 
     141         144 :         if (template.requirements)
     142             :         {
     143         114 :                 let op = Object.keys(template.requirements)[0];
     144         114 :                 let val = template.requirements[op];
     145         114 :                 requirements = InterpretTechRequirements(civ, op, val);
     146             :         }
     147             : 
     148         144 :         if (template.supersedes && requirements)
     149             :         {
     150           0 :                 if (!requirements.length)
     151           0 :                         requirements.push({});
     152             : 
     153           0 :                 for (let req of requirements)
     154             :                 {
     155           0 :                         if (!req.techs)
     156           0 :                                 req.techs = [];
     157           0 :                         req.techs.push(template.supersedes);
     158             :                 }
     159             :         }
     160             : 
     161         144 :         return requirements;
     162             : }
     163             : 
     164             : /**
     165             :  * Interprets the prerequisite requirements of a technology.
     166             :  *
     167             :  * Takes the initial { key: value } from the short-form requirements object in entity templates,
     168             :  * and parses it into an object that can be more easily checked by simulation and gui.
     169             :  *
     170             :  * Works recursively if needed.
     171             :  *
     172             :  * The returned object is in the form:
     173             :  * ```
     174             :  *      { "techs": ["tech1", "tech2"] },
     175             :  *      { "techs": ["tech3"] }
     176             :  * ```
     177             :  * or
     178             :  * ```
     179             :  *      { "entities": [[{
     180             :  *              "class": "human",
     181             :  *              "number": 2,
     182             :  *              "check": "count"
     183             :  *      }
     184             :  * or
     185             :  * ```
     186             :  *      false;
     187             :  * ```
     188             :  * (Or, to translate:
     189             :  * 1. need either both `tech1` and `tech2`, or `tech3`
     190             :  * 2. need 2 entities with the `human` class
     191             :  * 3. cannot research this tech at all)
     192             :  *
     193             :  * @param {string} civ - The civ code
     194             :  * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any".
     195             :  * @param {mixed} value - The value associated with the above operation.
     196             :  *
     197             :  * @return Object containing the requirements for the given civ, or false if the civ cannot research the tech.
     198             :  */
     199             : function InterpretTechRequirements(civ, operator, value)
     200             : {
     201         473 :         let requirements = [];
     202             : 
     203         473 :         switch (operator)
     204             :         {
     205             :         case "civ":
     206          97 :                 return !civ || civ == value ? [] : false;
     207             : 
     208             :         case "notciv":
     209          70 :                 return civ == value ? false : [];
     210             : 
     211             :         case "entity":
     212             :         {
     213          33 :                 let number = value.number || value.numberOfTypes || 0;
     214          33 :                 if (number > 0)
     215          33 :                         requirements.push({
     216             :                                 "entities": [{
     217             :                                         "class": value.class,
     218             :                                         "number": number,
     219             :                                         "check": value.number ? "count" : "variants"
     220             :                                 }]
     221             :                         });
     222          33 :                 break;
     223             :         }
     224             : 
     225             :         case "tech":
     226          83 :                 requirements.push({
     227             :                         "techs": [value]
     228             :                 });
     229          83 :                 break;
     230             : 
     231             :         case "all":
     232             :         {
     233         104 :                 let civPermitted = undefined; // tri-state (undefined, false, or true)
     234         104 :                 for (let subvalue of value)
     235             :                 {
     236         198 :                         let newOper = Object.keys(subvalue)[0];
     237         198 :                         let newValue = subvalue[newOper];
     238         198 :                         let result = InterpretTechRequirements(civ, newOper, newValue);
     239             : 
     240         198 :                         switch (newOper)
     241             :                         {
     242             :                         case "civ":
     243          57 :                                 if (result)
     244          23 :                                         civPermitted = true;
     245          34 :                                 else if (civPermitted !== true)
     246          27 :                                         civPermitted = false;
     247          57 :                                 break;
     248             : 
     249             :                         case "notciv":
     250          37 :                                 if (!result)
     251          12 :                                         return false;
     252          25 :                                 break;
     253             : 
     254             :                         case "any":
     255          17 :                                 if (!result)
     256           7 :                                         return false;
     257             :                                 // else, fall through
     258             : 
     259             :                         case "all":
     260          26 :                                 if (!result)
     261             :                                 {
     262           6 :                                         let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
     263           6 :                                         if (!nullcivreqs || !nullcivreqs.length)
     264           4 :                                                 civPermitted = false;
     265           6 :                                         continue;
     266             :                                 }
     267             :                                 // else, fall through
     268             : 
     269             :                         case "tech":
     270             :                         case "entity":
     271             :                         {
     272          91 :                                 if (result.length)
     273             :                                 {
     274          81 :                                         if (!requirements.length)
     275          62 :                                                 requirements.push({});
     276             : 
     277          81 :                                         let newRequirements = [];
     278          81 :                                         for (let currReq of requirements)
     279          83 :                                                 for (let res of result)
     280             :                                                 {
     281          89 :                                                         let newReq = {};
     282          89 :                                                         for (let subtype in currReq)
     283          26 :                                                                 newReq[subtype] = currReq[subtype];
     284             : 
     285          89 :                                                         for (let subtype in res)
     286             :                                                         {
     287          91 :                                                                 if (!newReq[subtype])
     288          72 :                                                                         newReq[subtype] = [];
     289          91 :                                                                 newReq[subtype] = newReq[subtype].concat(res[subtype]);
     290             :                                                         }
     291          89 :                                                         newRequirements.push(newReq);
     292             :                                                 }
     293          81 :                                         requirements = newRequirements;
     294             :                                 }
     295          91 :                                 break;
     296             :                         }
     297             : 
     298             :                         }
     299             :                 }
     300          85 :                 if (civPermitted === false) // if and only if false
     301          22 :                         return false;
     302          63 :                 break;
     303             :         }
     304             : 
     305             :         case "any":
     306             :         {
     307          86 :                 let civPermitted = false;
     308          86 :                 for (let subvalue of value)
     309             :                 {
     310         151 :                         let newOper = Object.keys(subvalue)[0];
     311         151 :                         let newValue = subvalue[newOper];
     312         151 :                         let result = InterpretTechRequirements(civ, newOper, newValue);
     313             : 
     314         151 :                         switch (newOper)
     315             :                         {
     316             : 
     317             :                         case "civ":
     318          38 :                                 if (result)
     319          14 :                                         return [];
     320          24 :                                 break;
     321             : 
     322             :                         case "notciv":
     323          31 :                                 if (!result)
     324          10 :                                         return false;
     325          21 :                                 civPermitted = true;
     326          21 :                                 break;
     327             : 
     328             :                         case "any":
     329          12 :                                 if (!result)
     330             :                                 {
     331           4 :                                         let nullcivreqs = InterpretTechRequirements(null, newOper, newValue);
     332           4 :                                         if (!nullcivreqs || !nullcivreqs.length)
     333           4 :                                                 continue;
     334           0 :                                         return false;
     335             :                                 }
     336             :                                 // else, fall through
     337             : 
     338             :                         case "all":
     339          36 :                                 if (!result)
     340          14 :                                         continue;
     341          22 :                                 civPermitted = true;
     342             :                                 // else, fall through
     343             : 
     344             :                         case "tech":
     345             :                         case "entity":
     346          64 :                                 for (let res of result)
     347          60 :                                         requirements.push(res);
     348          64 :                                 break;
     349             : 
     350             :                         }
     351             :                 }
     352          62 :                 if (!civPermitted && !requirements.length)
     353          14 :                         return false;
     354          48 :                 break;
     355             :         }
     356             : 
     357             :         default:
     358           0 :                 warn("Unknown requirement operator: "+operator);
     359             :         }
     360             : 
     361         227 :         return requirements;
     362             : }
     363             : 
     364             : /**
     365             :  * Determine order of phases.
     366             :  *
     367             :  * @param {Object} phases - The current available store of phases.
     368             :  * @return {array} List of phases
     369             :  */
     370             : function UnravelPhases(phases)
     371             : {
     372           0 :         let phaseMap = {};
     373           0 :         for (let phaseName in phases)
     374             :         {
     375           0 :                 let phaseData = phases[phaseName];
     376           0 :                 if (!phaseData.reqs.length || !phaseData.reqs[0].techs || !phaseData.replaces)
     377           0 :                         continue;
     378             : 
     379           0 :                 let myPhase = phaseData.replaces[0];
     380           0 :                 let reqPhase = phaseData.reqs[0].techs[0];
     381           0 :                 if (phases[reqPhase] && phases[reqPhase].replaces)
     382           0 :                         reqPhase = phases[reqPhase].replaces[0];
     383             : 
     384           0 :                 phaseMap[myPhase] = reqPhase;
     385           0 :                 if (!phaseMap[reqPhase])
     386           0 :                         phaseMap[reqPhase] = undefined;
     387             :         }
     388             : 
     389           0 :         let phaseList = Object.keys(phaseMap);
     390           0 :         phaseList.sort((a, b) => phaseList.indexOf(a) - phaseList.indexOf(phaseMap[b]));
     391             : 
     392           0 :         return phaseList;
     393             : }

Generated by: LCOV version 1.14