LCOV - code coverage report
Current view: top level - simulation/components - Researcher.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 135 159 84.9 %
Date: 2023-04-02 12:52:40 Functions: 20 26 76.9 %

          Line data    Source code
       1             : function Researcher() {}
       2             : 
       3           2 : Researcher.prototype.Schema =
       4             :         "<a:help>Allows the entity to research technologies.</a:help>" +
       5             :         "<a:example>" +
       6             :                 "<TechCostMultiplier>" +
       7             :                         "<food>0.5</food>" +
       8             :                         "<wood>0.1</wood>" +
       9             :                         "<stone>0</stone>" +
      10             :                         "<metal>2</metal>" +
      11             :                         "<time>0.9</time>" +
      12             :                 "</TechCostMultiplier>" +
      13             :                 "<Technologies datatype='tokens'>" +
      14             :                         "\n    phase_town_{civ}\n    phase_metropolis_ptol\n    unlock_shared_los\n    wonder_population_cap\n  " +
      15             :                 "</Technologies>" +
      16             :         "</a:example>" +
      17             :         "<optional>" +
      18             :                 "<element name='Technologies' a:help='Space-separated list of technology names that this building can research. When present, the special string \"{civ}\" will be automatically replaced either by the civ code of the building&apos;s owner if such a tech exists, or by \"generic\".'>" +
      19             :                         "<attribute name='datatype'>" +
      20             :                                 "<value>tokens</value>" +
      21             :                         "</attribute>" +
      22             :                         "<text/>" +
      23             :                 "</element>" +
      24             :         "</optional>" +
      25             :         "<optional>" +
      26             :                 "<element name='TechCostMultiplier' a:help='Multiplier to modify resources cost and research time of technologies researched in this building.'>" +
      27             :                         Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
      28             :                 "</element>" +
      29             :         "</optional>";
      30             : 
      31             : /**
      32             :  * This object represents a technology being researched.
      33             :  * @param {string} templateName - The name of the template we ought to research.
      34             :  * @param {number} researcher - The entity ID of our researcher.
      35             :  * @param {string} metadata - Optionally any metadata to attach to us.
      36             :  */
      37           2 : Researcher.prototype.Item = function(templateName, researcher, metadata)
      38             : {
      39           7 :         this.templateName = templateName;
      40           7 :         this.researcher = researcher;
      41           7 :         this.metadata = metadata;
      42             : };
      43             : 
      44             : /**
      45             :  * Prepare for the queue.
      46             :  * @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
      47             :  * @return {boolean} - Whether the item was successfully initiated.
      48             :  */
      49           2 : Researcher.prototype.Item.prototype.Queue = function(techCostMultiplier)
      50             : {
      51           4 :         this.player = QueryOwnerInterface(this.researcher).GetPlayerID();
      52           4 :         const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
      53           4 :         if (!cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher, techCostMultiplier))
      54           0 :                 return false;
      55             : 
      56           4 :         return true;
      57             : };
      58             : 
      59           2 : Researcher.prototype.Item.prototype.Stop = function()
      60             : {
      61           2 :         QueryPlayerIDInterface(this.player, IID_TechnologyManager).StoppedResearch(this.templateName);
      62           2 :         delete this.started;
      63             : };
      64             : 
      65             : /**
      66             :  * Called when the first work is performed.
      67             :  */
      68           2 : Researcher.prototype.Item.prototype.Start = function()
      69             : {
      70           3 :         this.started = true;
      71             : };
      72             : 
      73           2 : Researcher.prototype.Item.prototype.Finish = function()
      74             : {
      75           2 :         this.finished = true;
      76             : };
      77             : 
      78             : /**
      79             :  * @param {number} allocatedTime - The time allocated to this item.
      80             :  * @return {number} - The time used for this item.
      81             :  */
      82           2 : Researcher.prototype.Item.prototype.Progress = function(allocatedTime)
      83             : {
      84           5 :         if (!this.started)
      85           3 :                 this.Start();
      86           5 :         if (this.paused)
      87           0 :                 this.Unpause();
      88           5 :         const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
      89           5 :         const usedTime = cmpTechnologyManager.Progress(this.templateName, allocatedTime);
      90           5 :         if (!cmpTechnologyManager.IsTechnologyQueued(this.templateName))
      91           2 :                 this.Finish();
      92           5 :         return usedTime;
      93             : };
      94             : 
      95           2 : Researcher.prototype.Item.prototype.Pause = function()
      96             : {
      97           0 :         QueryPlayerIDInterface(this.player, IID_TechnologyManager).Pause(this.templateName);
      98           0 :         this.paused = true;
      99             : };
     100             : 
     101           2 : Researcher.prototype.Item.prototype.Unpause = function()
     102             : {
     103           0 :         delete this.paused;
     104             : };
     105             : 
     106             : /**
     107             :  * @return {Object} - Some basic information of this item.
     108             :  */
     109           2 : Researcher.prototype.Item.prototype.GetBasicInfo = function()
     110             : {
     111           0 :         const result = QueryPlayerIDInterface(this.player, IID_TechnologyManager).GetBasicInfo(this.templateName);
     112           0 :         result.technologyTemplate = this.templateName;
     113           0 :         result.metadata = this.metadata;
     114           0 :         return result;
     115             : };
     116             : 
     117           2 : Researcher.prototype.Item.prototype.SerializableAttributes = [
     118             :         "metadata",
     119             :         "paused",
     120             :         "player",
     121             :         "researcher",
     122             :         "started",
     123             :         "templateName"
     124             : ];
     125             : 
     126           2 : Researcher.prototype.Item.prototype.Serialize = function(id)
     127             : {
     128           3 :         const result = {
     129             :                 "id": id
     130             :         };
     131           3 :         for (const att of this.SerializableAttributes)
     132          18 :                 if (this.hasOwnProperty(att))
     133          15 :                         result[att] = this[att];
     134           3 :         return result;
     135             : };
     136             : 
     137           2 : Researcher.prototype.Item.prototype.Deserialize = function(data)
     138             : {
     139           3 :         for (const att of this.SerializableAttributes)
     140          18 :                 if (att in data)
     141          15 :                         this[att] = data[att];
     142             : };
     143             : 
     144           2 : Researcher.prototype.Init = function()
     145             : {
     146           9 :         this.nextID = 1;
     147           9 :         this.queue = new Map();
     148             : };
     149             : 
     150           2 : Researcher.prototype.Serialize = function()
     151             : {
     152           3 :         const queue = [];
     153           3 :         for (const [id, item] of this.queue)
     154           3 :                 queue.push(item.Serialize(id));
     155             : 
     156           3 :         return {
     157             :                 "nextID": this.nextID,
     158             :                 "queue": queue
     159             :         };
     160             : };
     161             : 
     162           2 : Researcher.prototype.Deserialize = function(data)
     163             : {
     164           3 :         this.Init();
     165           3 :         this.nextID = data.nextID;
     166           3 :         for (const item of data.queue)
     167             :         {
     168           3 :                 const newItem = new this.Item();
     169           3 :                 newItem.Deserialize(item);
     170           3 :                 this.queue.set(item.id, newItem);
     171             :         }
     172             : };
     173             : 
     174             : /*
     175             :  * Returns list of technologies that can be researched by this entity.
     176             :  */
     177           2 : Researcher.prototype.GetTechnologiesList = function()
     178             : {
     179          10 :         const string = ApplyValueModificationsToEntity("Researcher/Technologies/_string", this.template?.Technologies?._string || "", this.entity);
     180          10 :         if (!string)
     181           0 :                 return [];
     182             : 
     183          10 :         const owner = Engine.QueryInterface(this.entity, IID_Ownership)?.GetOwner();
     184          10 :         if (!owner || owner === INVALID_PLAYER)
     185           0 :                 return [];
     186             : 
     187          10 :         const playerEnt = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(owner);
     188          10 :         if (!playerEnt)
     189           0 :                 return [];
     190             : 
     191          10 :         const cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
     192          10 :         if (!cmpTechnologyManager)
     193           0 :                 return [];
     194             : 
     195          10 :         const cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
     196          10 :         if (!cmpPlayer)
     197           0 :                 return [];
     198             : 
     199          10 :         let techs = string.split(/\s+/);
     200             : 
     201             :         // Replace the civ specific technologies.
     202          10 :         const civ = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv();
     203          10 :         for (let i = 0; i < techs.length; ++i)
     204             :         {
     205          29 :                 const tech = techs[i];
     206          29 :                 if (tech.indexOf("{civ}") == -1)
     207          15 :                         continue;
     208          14 :                 const civTech = tech.replace("{civ}", civ);
     209          14 :                 techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic");
     210             :         }
     211             : 
     212             :         // Remove any technologies that can't be researched by this civ.
     213          10 :         techs = techs.filter(tech =>
     214          29 :                 cmpTechnologyManager.CheckTechnologyRequirements(
     215             :                         DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), civ),
     216             :                         true));
     217             : 
     218          10 :         const techList = [];
     219          10 :         const superseded = {};
     220             : 
     221          10 :         const disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
     222             : 
     223             :         // Add any top level technologies to an array which corresponds to the displayed icons.
     224             :         // Also store what technology is superseded in the superseded object { "tech1":"techWhichSupercedesTech1", ... }.
     225          10 :         for (const tech of techs)
     226             :         {
     227          29 :                 if (disabledTechnologies && disabledTechnologies[tech])
     228           2 :                         continue;
     229             : 
     230          27 :                 const template = TechnologyTemplates.Get(tech);
     231          27 :                 if (!template.supersedes || techs.indexOf(template.supersedes) === -1)
     232          27 :                         techList.push(tech);
     233             :                 else
     234           0 :                         superseded[template.supersedes] = tech;
     235             :         }
     236             : 
     237             :         // Now make researched/in progress techs invisible.
     238          10 :         for (const i in techList)
     239             :         {
     240          27 :                 let tech = techList[i];
     241          27 :                 while (this.IsTechnologyResearchedOrInProgress(tech))
     242           1 :                         tech = superseded[tech];
     243             : 
     244          27 :                 techList[i] = tech;
     245             :         }
     246             : 
     247          10 :         const ret = [];
     248             : 
     249             :         // This inserts the techs into the correct positions to line up the technology pairs.
     250          10 :         for (let i = 0; i < techList.length; ++i)
     251             :         {
     252          27 :                 const tech = techList[i];
     253          27 :                 if (!tech)
     254             :                 {
     255           1 :                         ret[i] = undefined;
     256           1 :                         continue;
     257             :                 }
     258             : 
     259          26 :                 const template = TechnologyTemplates.Get(tech);
     260          26 :                 if (template.top)
     261           0 :                         ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
     262             :                 else
     263          26 :                         ret[i] = tech;
     264             :         }
     265             : 
     266          10 :         return ret;
     267             : };
     268             : 
     269             : /**
     270             :  * @return {Object} - The multipliers to change the costs of any research with.
     271             :  */
     272           2 : Researcher.prototype.GetTechCostMultiplier = function()
     273             : {
     274           4 :         const techCostMultiplier = {};
     275           4 :         for (const res of Resources.GetCodes().concat(["time"]))
     276          14 :                 techCostMultiplier[res] = ApplyValueModificationsToEntity(
     277             :                     "Researcher/TechCostMultiplier/" + res,
     278             :                     +(this.template?.TechCostMultiplier?.[res] || 1),
     279             :                     this.entity);
     280             : 
     281           4 :         return techCostMultiplier;
     282             : };
     283             : 
     284             : /**
     285             :  * Checks whether we can research the given technology, minding paired techs.
     286             :  */
     287           2 : Researcher.prototype.IsTechnologyResearchedOrInProgress = function(tech)
     288             : {
     289          28 :         if (!tech)
     290           1 :                 return false;
     291             : 
     292          27 :         const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
     293          27 :         if (!cmpTechnologyManager)
     294           0 :                 return false;
     295             : 
     296          27 :         const template = TechnologyTemplates.Get(tech);
     297          27 :         if (template.top)
     298           0 :                 return cmpTechnologyManager.IsTechnologyResearched(template.top) ||
     299             :                     cmpTechnologyManager.IsInProgress(template.top) ||
     300             :                     cmpTechnologyManager.IsTechnologyResearched(template.bottom) ||
     301             :                     cmpTechnologyManager.IsInProgress(template.bottom);
     302             : 
     303          27 :         return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech);
     304             : };
     305             : 
     306             : /**
     307             :  * @param {string} templateName - The technology to queue.
     308             :  * @param {string} metadata - Any metadata attached to the item.
     309             :  * @return {number} - The ID of the item. -1 if the item could not be researched.
     310             :  */
     311           2 : Researcher.prototype.QueueTechnology = function(templateName, metadata)
     312             : {
     313           4 :         if (!this.GetTechnologiesList().some(tech =>
     314           4 :                 tech && (tech == templateName ||
     315             :                         tech.pair && (tech.top == templateName || tech.bottom == templateName))))
     316             :         {
     317           0 :                 error("This entity cannot research " + templateName + ".");
     318           0 :                 return -1;
     319             :         }
     320             : 
     321           4 :         const item = new this.Item(templateName, this.entity, metadata);
     322             : 
     323           4 :         const techCostMultiplier = this.GetTechCostMultiplier();
     324           4 :         if (!item.Queue(techCostMultiplier))
     325           0 :                 return -1;
     326             : 
     327           4 :         const id = this.nextID++;
     328           4 :         this.queue.set(id, item);
     329           4 :         return id;
     330             : };
     331             : 
     332             : /**
     333             :  * @param {number} id - The id of the technology researched here we need to stop.
     334             :  */
     335           2 : Researcher.prototype.StopResearching = function(id)
     336             : {
     337           2 :         this.queue.get(id).Stop();
     338           2 :         this.queue.delete(id);
     339             : };
     340             : 
     341             : /**
     342             :  * @param {number} id - The id of the technology.
     343             :  */
     344           2 : Researcher.prototype.PauseTechnology = function(id)
     345             : {
     346           0 :         this.queue.get(id).Pause();
     347             : };
     348             : 
     349             : /**
     350             :  * @param {number} id - The ID of the item to check.
     351             :  * @return {boolean} - Whether we are currently training the item.
     352             :  */
     353           2 : Researcher.prototype.HasItem = function(id)
     354             : {
     355           0 :         return this.queue.has(id);
     356             : };
     357             : 
     358             : /**
     359             :  * @parameter {number} id - The id of the research.
     360             :  * @return {Object} - Some basic information about the research.
     361             :  */
     362           2 : Researcher.prototype.GetResearchingTechnology = function(id)
     363             : {
     364           0 :         return this.queue.get(id).GetBasicInfo();
     365             : };
     366             : 
     367             : /**
     368             :  * @param {number} id - The ID of the item we spent time on.
     369             :  * @param {number} allocatedTime - The time we spent on the given item.
     370             :  * @return {number} - The time we've actually used.
     371             :  */
     372           2 : Researcher.prototype.Progress = function(id, allocatedTime)
     373             : {
     374           5 :         const item = this.queue.get(id);
     375           5 :         const usedTime = item.Progress(allocatedTime);
     376           5 :         if (item.finished)
     377           2 :                 this.queue.delete(id);
     378           5 :         return usedTime;
     379             : };
     380             : 
     381           2 : Engine.RegisterComponentType(IID_Researcher, "Researcher", Researcher);

Generated by: LCOV version 1.14