LCOV - code coverage report
Current view: top level - simulation/components - TechnologyManager.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 156 238 65.5 %
Date: 2023-04-02 12:52:40 Functions: 29 39 74.4 %

          Line data    Source code
       1             : function TechnologyManager() {}
       2             : 
       3           2 : TechnologyManager.prototype.Schema =
       4             :         "<empty/>";
       5             : 
       6             : /**
       7             :  * This object represents a technology under research.
       8             :  * @param {string} templateName - The name of the template to research.
       9             :  * @param {number} player - The player ID researching.
      10             :  * @param {number} researcher - The entity ID researching.
      11             :  */
      12           2 : TechnologyManager.prototype.Technology = function(templateName, player, researcher)
      13             : {
      14           9 :         this.player = player;
      15           9 :         this.researcher = researcher;
      16           9 :         this.templateName = templateName;
      17             : };
      18             : 
      19             : /**
      20             :  * Prepare for the queue.
      21             :  * @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
      22             :  * @return {boolean} - Whether the technology was successfully initiated.
      23             :  */
      24           2 : TechnologyManager.prototype.Technology.prototype.Queue = function(techCostMultiplier)
      25             : {
      26           5 :         const template = TechnologyTemplates.Get(this.templateName);
      27           5 :         if (!template)
      28           0 :                 return false;
      29             : 
      30           5 :         this.resources = {};
      31           5 :         if (template.cost)
      32           5 :                 for (const res in template.cost)
      33           5 :                         this.resources[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
      34             : 
      35             :         // ToDo: Subtract resources here or in cmpResearcher?
      36           5 :         const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
      37             :         // TrySubtractResources should report error to player (they ran out of resources).
      38           5 :         if (!cmpPlayer?.TrySubtractResources(this.resources))
      39           0 :                 return false;
      40             : 
      41           5 :         const time = techCostMultiplier.time * (template.researchTime || 0) * 1000;
      42           5 :         this.timeRemaining = time;
      43           5 :         this.timeTotal = time;
      44             : 
      45           5 :         const playerID = cmpPlayer.GetPlayerID();
      46           5 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).CallEvent("OnResearchQueued", {
      47             :                 "playerid": playerID,
      48             :                 "technologyTemplate": this.templateName,
      49             :                 "researcherEntity": this.researcher
      50             :         });
      51             : 
      52           5 :         return true;
      53             : };
      54             : 
      55           2 : TechnologyManager.prototype.Technology.prototype.Stop = function()
      56             : {
      57           3 :         const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
      58           3 :         cmpPlayer?.RefundResources(this.resources);
      59           3 :         delete this.resources;
      60             : 
      61           3 :         if (this.started && this.templateName.startsWith("phase"))
      62           0 :                 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
      63             :                         "type": "phase",
      64             :                         "players": [cmpPlayer.GetPlayerID()],
      65             :                         "phaseName": this.templateName,
      66             :                         "phaseState": "aborted"
      67             :                 });
      68             : };
      69             : 
      70             : /**
      71             :  * Called when the first work is performed.
      72             :  */
      73           2 : TechnologyManager.prototype.Technology.prototype.Start = function()
      74             : {
      75           4 :         this.started = true;
      76           4 :         if (!this.templateName.startsWith("phase"))
      77           4 :                 return;
      78             : 
      79           0 :         const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
      80           0 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
      81             :                 "type": "phase",
      82             :                 "players": [cmpPlayer.GetPlayerID()],
      83             :                 "phaseName": this.templateName,
      84             :                 "phaseState": "started"
      85             :         });
      86             : };
      87             : 
      88           2 : TechnologyManager.prototype.Technology.prototype.Finish = function()
      89             : {
      90           2 :         this.finished = true;
      91             : 
      92           2 :         const template = TechnologyTemplates.Get(this.templateName);
      93           2 :         if (template.soundComplete)
      94           0 :                 Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher);
      95             : 
      96           2 :         if (template.modifications)
      97             :         {
      98           0 :                 const cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
      99           0 :                 cmpModifiersManager.AddModifiers("tech/" + this.templateName, DeriveModificationsFromTech(template), this.player);
     100             :         }
     101             : 
     102           2 :         const cmpEntityLimits = Engine.QueryInterface(this.player, IID_EntityLimits);
     103           2 :         const cmpTechnologyManager = Engine.QueryInterface(this.player, IID_TechnologyManager);
     104           2 :         if (template.replaces && template.replaces.length > 0)
     105           0 :                 for (const i of template.replaces)
     106             :                 {
     107           0 :                         cmpTechnologyManager.MarkTechnologyAsResearched(i);
     108           0 :                         cmpEntityLimits?.UpdateLimitsFromTech(i);
     109             :                 }
     110             : 
     111           2 :         cmpTechnologyManager.MarkTechnologyAsResearched(this.templateName);
     112             : 
     113             :         // ToDo: Move to EntityLimits.js.
     114           2 :         cmpEntityLimits?.UpdateLimitsFromTech(this.templateName);
     115             : 
     116           2 :         const playerID = Engine.QueryInterface(this.player, IID_Player).GetPlayerID();
     117           2 :         Engine.PostMessage(this.player, MT_ResearchFinished, { "player": playerID, "tech": this.templateName });
     118             : 
     119           2 :         if (this.templateName.startsWith("phase") && !template.autoResearch)
     120           0 :                 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
     121             :                         "type": "phase",
     122             :                         "players": [playerID],
     123             :                         "phaseName": this.templateName,
     124             :                         "phaseState": "completed"
     125             :                 });
     126             : };
     127             : 
     128             : /**
     129             :  * @param {number} allocatedTime - The time allocated to this item.
     130             :  * @return {number} - The time used for this item.
     131             :  */
     132           2 : TechnologyManager.prototype.Technology.prototype.Progress = function(allocatedTime)
     133             : {
     134           6 :         if (!this.started)
     135           4 :                 this.Start();
     136           6 :         if (this.paused)
     137           1 :                 this.Unpause();
     138           6 :         if (this.timeRemaining > allocatedTime)
     139             :         {
     140           4 :                 this.timeRemaining -= allocatedTime;
     141           4 :                 return allocatedTime;
     142             :         }
     143           2 :         this.Finish();
     144           2 :         return this.timeRemaining;
     145             : };
     146             : 
     147           2 : TechnologyManager.prototype.Technology.prototype.Pause = function()
     148             : {
     149           1 :         this.paused = true;
     150             : };
     151             : 
     152           2 : TechnologyManager.prototype.Technology.prototype.Unpause = function()
     153             : {
     154           1 :         delete this.paused;
     155             : };
     156             : 
     157           2 : TechnologyManager.prototype.Technology.prototype.GetBasicInfo = function()
     158             : {
     159           1 :         return {
     160             :                 "paused": this.paused,
     161             :                 "progress": 1 - (this.timeRemaining / (this.timeTotal || 1)),
     162             :                 "researcher": this.researcher,
     163             :                 "templateName": this.templateName,
     164             :                 "timeRemaining": this.timeRemaining
     165             :         };
     166             : };
     167             : 
     168           2 : TechnologyManager.prototype.Technology.prototype.SerializableAttributes = [
     169             :         "paused",
     170             :         "player",
     171             :         "researcher",
     172             :         "resources",
     173             :         "started",
     174             :         "templateName",
     175             :         "timeRemaining",
     176             :         "timeTotal"
     177             : ];
     178             : 
     179           2 : TechnologyManager.prototype.Technology.prototype.Serialize = function()
     180             : {
     181           4 :         const result = {};
     182           4 :         for (const att of this.SerializableAttributes)
     183          32 :                 if (this.hasOwnProperty(att))
     184          28 :                         result[att] = this[att];
     185           4 :         return result;
     186             : };
     187             : 
     188           2 : TechnologyManager.prototype.Technology.prototype.Deserialize = function(data)
     189             : {
     190           4 :         for (const att of this.SerializableAttributes)
     191          32 :                 if (att in data)
     192          28 :                         this[att] = data[att];
     193             : };
     194             : 
     195           2 : TechnologyManager.prototype.Init = function()
     196             : {
     197             :         // Holds names of technologies that have been researched.
     198           6 :         this.researchedTechs = new Set();
     199             : 
     200             :         // Maps from technolgy name to the technology object.
     201           6 :         this.researchQueued = new Map();
     202             : 
     203           6 :         this.classCounts = {}; // stores the number of entities of each Class
     204           6 :         this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
     205             :                                      // {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
     206             : 
     207             :         // Some technologies are automatically researched when their conditions are met.  They have no cost and are
     208             :         // researched instantly.  This allows civ bonuses and more complicated technologies.
     209           6 :         this.unresearchedAutoResearchTechs = new Set();
     210           6 :         let allTechs = TechnologyTemplates.GetAll();
     211           6 :         for (let key in allTechs)
     212           0 :                 if (allTechs[key].autoResearch || allTechs[key].top)
     213           0 :                         this.unresearchedAutoResearchTechs.add(key);
     214             : };
     215             : 
     216           2 : TechnologyManager.prototype.SerializableAttributes = [
     217             :         "researchedTechs",
     218             :         "classCounts",
     219             :         "typeCountsByClass",
     220             :         "unresearchedAutoResearchTechs"
     221             : ];
     222             : 
     223           2 : TechnologyManager.prototype.Serialize = function()
     224             : {
     225           4 :         const result = {};
     226           4 :         for (const att of this.SerializableAttributes)
     227          16 :                 if (this.hasOwnProperty(att))
     228          16 :                         result[att] = this[att];
     229             : 
     230           4 :         result.researchQueued = [];
     231           4 :         for (const [techName, techObject] of this.researchQueued)
     232           4 :                 result.researchQueued.push(techObject.Serialize());
     233             : 
     234           4 :         return result;
     235             : };
     236             : 
     237           2 : TechnologyManager.prototype.Deserialize = function(data)
     238             : {
     239           4 :         for (const att of this.SerializableAttributes)
     240          16 :                 if (att in data)
     241          16 :                         this[att] = data[att];
     242             : 
     243           4 :         this.researchQueued = new Map();
     244           4 :         for (const tech of data.researchQueued)
     245             :         {
     246           4 :                 const newTech = new this.Technology();
     247           4 :                 newTech.Deserialize(tech);
     248           4 :                 this.researchQueued.set(tech.templateName, newTech);
     249             :         }
     250             : };
     251             : 
     252           2 : TechnologyManager.prototype.OnUpdate = function()
     253             : {
     254           0 :         this.UpdateAutoResearch();
     255             : };
     256             : 
     257             : // This function checks if the requirements of any autoresearch techs are met and if they are it researches them
     258           2 : TechnologyManager.prototype.UpdateAutoResearch = function()
     259             : {
     260           2 :         for (let key of this.unresearchedAutoResearchTechs)
     261             :         {
     262           0 :                 let tech = TechnologyTemplates.Get(key);
     263           0 :                 if ((tech.autoResearch && this.CanResearch(key)) ||
     264             :                         (tech.top && (this.IsTechnologyResearched(tech.top) || this.IsTechnologyResearched(tech.bottom))))
     265             :                 {
     266           0 :                         this.unresearchedAutoResearchTechs.delete(key);
     267           0 :                         this.ResearchTechnology(key);
     268           0 :                         return; // We will have recursively handled any knock-on effects so can just return
     269             :                 }
     270             :         }
     271             : };
     272             : 
     273             : // Checks an entity template to see if its technology requirements have been met
     274           2 : TechnologyManager.prototype.CanProduce = function(templateName)
     275             : {
     276           0 :         var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     277           0 :         var template = cmpTempManager.GetTemplate(templateName);
     278             : 
     279           0 :         if (template.Identity?.Requirements)
     280           0 :                 return RequirementsHelper.AreRequirementsMet(template.Identity.Requirements, Engine.QueryInterface(this.entity, IID_Player).GetPlayerID());
     281             :         // If there is no required technology then this entity can be produced
     282           0 :         return true;
     283             : };
     284             : 
     285           2 : TechnologyManager.prototype.IsTechnologyQueued = function(tech)
     286             : {
     287           3 :         return this.researchQueued.has(tech);
     288             : };
     289             : 
     290           2 : TechnologyManager.prototype.IsTechnologyResearched = function(tech)
     291             : {
     292           8 :         return this.researchedTechs.has(tech);
     293             : };
     294             : 
     295             : // Checks the requirements for a technology to see if it can be researched at the current time
     296           2 : TechnologyManager.prototype.CanResearch = function(tech)
     297             : {
     298           0 :         let template = TechnologyTemplates.Get(tech);
     299             : 
     300           0 :         if (!template)
     301             :         {
     302           0 :                 warn("Technology \"" + tech + "\" does not exist");
     303           0 :                 return false;
     304             :         }
     305             : 
     306           0 :         if (template.top && this.IsInProgress(template.top) ||
     307             :             template.bottom && this.IsInProgress(template.bottom))
     308           0 :                 return false;
     309             : 
     310           0 :         if (template.pair && !this.CanResearch(template.pair))
     311           0 :                 return false;
     312             : 
     313           0 :         if (this.IsInProgress(tech))
     314           0 :                 return false;
     315             : 
     316           0 :         if (this.IsTechnologyResearched(tech))
     317           0 :                 return false;
     318             : 
     319           0 :         return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Identity).GetCiv()));
     320             : };
     321             : 
     322             : /**
     323             :  * Private function for checking a set of requirements is met
     324             :  * @param {Object} reqs - Technology requirements as derived from the technology template by globalscripts
     325             :  * @param {boolean} civonly - True if only the civ requirement is to be checked
     326             :  *
     327             :  * @return true if the requirements pass, false otherwise
     328             :  */
     329           2 : TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false)
     330             : {
     331           9 :         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     332             : 
     333           9 :         if (!reqs)
     334           2 :                 return false;
     335             : 
     336           7 :         if (civonly || !reqs.length)
     337           5 :                 return true;
     338             : 
     339           2 :         return reqs.some(req => {
     340           2 :                 return Object.keys(req).every(type => {
     341           2 :                         switch (type)
     342             :                         {
     343             :                         case "techs":
     344           0 :                                 return req[type].every(this.IsTechnologyResearched, this);
     345             : 
     346             :                         case "entities":
     347           2 :                                 return req[type].every(this.DoesEntitySpecPass, this);
     348             :                         }
     349           0 :                         return false;
     350             :                 });
     351             :         });
     352             : };
     353             : 
     354           2 : TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
     355             : {
     356           2 :         switch (entity.check)
     357             :         {
     358             :         case "count":
     359           2 :                 if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
     360           1 :                         return false;
     361           1 :                 break;
     362             : 
     363             :         case "variants":
     364           0 :                 if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
     365           0 :                         return false;
     366           0 :                 break;
     367             :         }
     368           1 :         return true;
     369             : };
     370             : 
     371           2 : TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
     372             : {
     373             :         // This automatically updates classCounts and typeCountsByClass
     374           0 :         var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
     375           0 :         if (msg.to == playerID)
     376             :         {
     377           0 :                 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     378           0 :                 var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
     379             : 
     380           0 :                 var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
     381           0 :                 if (!cmpIdentity)
     382           0 :                         return;
     383             : 
     384           0 :                 var classes = cmpIdentity.GetClassesList();
     385             :                 // don't use foundations for the class counts but check if techs apply (e.g. health increase)
     386           0 :                 if (!Engine.QueryInterface(msg.entity, IID_Foundation))
     387             :                 {
     388           0 :                         for (let cls of classes)
     389             :                         {
     390           0 :                                 this.classCounts[cls] = this.classCounts[cls] || 0;
     391           0 :                                 this.classCounts[cls] += 1;
     392             : 
     393           0 :                                 this.typeCountsByClass[cls] = this.typeCountsByClass[cls] || {};
     394           0 :                                 this.typeCountsByClass[cls][template] = this.typeCountsByClass[cls][template] || 0;
     395           0 :                                 this.typeCountsByClass[cls][template] += 1;
     396             :                         }
     397             :                 }
     398             :         }
     399           0 :         if (msg.from == playerID)
     400             :         {
     401           0 :                 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     402           0 :                 var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
     403             : 
     404             :                 // don't use foundations for the class counts
     405           0 :                 if (!Engine.QueryInterface(msg.entity, IID_Foundation))
     406             :                 {
     407           0 :                         var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
     408           0 :                         if (cmpIdentity)
     409             :                         {
     410           0 :                                 var classes = cmpIdentity.GetClassesList();
     411           0 :                                 for (let cls of classes)
     412             :                                 {
     413           0 :                                         this.classCounts[cls] -= 1;
     414           0 :                                         if (this.classCounts[cls] <= 0)
     415           0 :                                                 delete this.classCounts[cls];
     416             : 
     417           0 :                                         this.typeCountsByClass[cls][template] -= 1;
     418           0 :                                         if (this.typeCountsByClass[cls][template] <= 0)
     419           0 :                                                 delete this.typeCountsByClass[cls][template];
     420             :                                 }
     421             :                         }
     422             :                 }
     423             :         }
     424             : };
     425             : 
     426             : /**
     427             :  * This does neither apply effects nor verify requirements.
     428             :  * @param {string} tech - The name of the technology to mark as researched.
     429             :  */
     430           2 : TechnologyManager.prototype.MarkTechnologyAsResearched = function(tech)
     431             : {
     432           2 :         this.researchedTechs.add(tech);
     433           2 :         this.UpdateAutoResearch();
     434             : };
     435             : 
     436             : /**
     437             :  * Note that this does not verify whether the requirements are met.
     438             :  * @param {string} tech - The technology to research.
     439             :  * @param {number} researcher - Optionally the entity to couple with the research.
     440             :  */
     441           2 : TechnologyManager.prototype.ResearchTechnology = function(tech, researcher = INVALID_ENTITY)
     442             : {
     443           0 :         if (this.IsTechnologyQueued(tech) || this.IsTechnologyResearched(tech))
     444           0 :                 return;
     445           0 :         const technology = new this.Technology(tech, this.entity, researcher);
     446           0 :         technology.Finish();
     447             : };
     448             : 
     449             : /**
     450             :  * Marks a technology as being queued for research at the given entityID.
     451             :  * @param {string} tech - The technology to queue.
     452             :  * @param {number} researcher - The entity ID of the entity researching this technology.
     453             :  * @param {Object} techCostMultiplier - The multipliers used when calculating the costs.
     454             :  *
     455             :  * @return {boolean} - Whether we successfully have queued the technology.
     456             :  */
     457           2 : TechnologyManager.prototype.QueuedResearch = function(tech, researcher, techCostMultiplier)
     458             : {
     459             :         // ToDo: Check whether the technology is researched already?
     460           5 :         const technology = new this.Technology(tech, this.entity, researcher);
     461           5 :         if (!technology.Queue(techCostMultiplier))
     462           0 :                 return false;
     463           5 :         this.researchQueued.set(tech, technology);
     464           5 :         return true;
     465             : };
     466             : 
     467             : /**
     468             :  * Marks a technology as not being currently researched and optionally sends a GUI notification.
     469             :  * @param {string} tech - The name of the technology to stop.
     470             :  * @param {boolean} notification - Whether a GUI notification ought to be sent.
     471             :  */
     472           2 : TechnologyManager.prototype.StoppedResearch = function(tech)
     473             : {
     474           3 :         this.researchQueued.get(tech).Stop();
     475           3 :         this.researchQueued.delete(tech);
     476             : };
     477             : 
     478             : /**
     479             :  * @param {string} tech -
     480             :  */
     481           2 : TechnologyManager.prototype.Pause = function(tech)
     482             : {
     483           1 :         this.researchQueued.get(tech).Pause();
     484             : };
     485             : 
     486             : /**
     487             :  * @param {string} tech - The technology to advance.
     488             :  * @param {number} allocatedTime - The time allocated to the technology.
     489             :  * @return {number} - The time we've actually used.
     490             :  */
     491           2 : TechnologyManager.prototype.Progress = function(techName, allocatedTime)
     492             : {
     493           6 :         const technology = this.researchQueued.get(techName);
     494           6 :         const usedTime = technology.Progress(allocatedTime);
     495           6 :         if (technology.finished)
     496           2 :                 this.researchQueued.delete(techName);
     497           6 :         return usedTime;
     498             : };
     499             : 
     500             : /**
     501             :  * @param {string} tech - The technology name to retreive some basic information for.
     502             :  * @return {Object} - Some basic information about the technology under research.
     503             :  */
     504           2 : TechnologyManager.prototype.GetBasicInfo = function(tech)
     505             : {
     506           0 :         return this.researchQueued.get(tech).GetBasicInfo();
     507             : };
     508             : 
     509             : /**
     510             :  * Checks whether a technology is set to be researched.
     511             :  */
     512           2 : TechnologyManager.prototype.IsInProgress = function(tech)
     513             : {
     514          10 :         return this.researchQueued.has(tech);
     515             : };
     516             : 
     517           2 : TechnologyManager.prototype.GetBasicInfoOfStartedTechs = function()
     518             : {
     519           1 :         const result = {};
     520           1 :         for (const [techName, tech] of this.researchQueued)
     521           1 :                 if (tech.started)
     522           1 :                         result[techName] = tech.GetBasicInfo();
     523           1 :         return result;
     524             : };
     525             : 
     526             : /**
     527             :  * Called by GUIInterface for PlayerData. AI use.
     528             :  */
     529           2 : TechnologyManager.prototype.GetQueuedResearch = function()
     530             : {
     531           0 :         return this.researchQueued;
     532             : };
     533             : 
     534             : /**
     535             :  * Returns the names of technologies that have already been researched.
     536             :  */
     537           2 : TechnologyManager.prototype.GetResearchedTechs = function()
     538             : {
     539           0 :         return this.researchedTechs;
     540             : };
     541             : 
     542           2 : TechnologyManager.prototype.GetClassCounts = function()
     543             : {
     544           0 :         return this.classCounts;
     545             : };
     546             : 
     547           2 : TechnologyManager.prototype.GetTypeCountsByClass = function()
     548             : {
     549           0 :         return this.typeCountsByClass;
     550             : };
     551             : 
     552           2 : Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager);

Generated by: LCOV version 1.14