LCOV - code coverage report
Current view: top level - simulation/components - ProductionQueue.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 162 219 74.0 %
Date: 2023-04-02 12:52:40 Functions: 30 39 76.9 %

          Line data    Source code
       1             : function ProductionQueue() {}
       2             : 
       3           1 : ProductionQueue.prototype.Schema =
       4             :         "<a:help>Helps the building to train new units and research technologies.</a:help>" +
       5             :         "<empty/>";
       6             : 
       7           1 : ProductionQueue.prototype.ProgressInterval = 1000;
       8           1 : ProductionQueue.prototype.MaxQueueSize = 16;
       9             : 
      10             : /**
      11             :  * This object represents an item in the queue.
      12             :  *
      13             :  * @param {number} producer - The entity ID of our producer.
      14             :  * @param {string} metadata - Optionally any metadata attached to us.
      15             :  */
      16           1 : ProductionQueue.prototype.Item = function(producer, metadata)
      17             : {
      18           8 :         this.producer = producer;
      19           8 :         this.metadata = metadata;
      20             : };
      21             : 
      22             : /**
      23             :  * @param {string} type - The type of queue to use.
      24             :  * @param {string} templateName - The template to queue.
      25             :  * @param {number} count - The amount of template to queue. Only applicable for type == "unit".
      26             :  *
      27             :  * @return {boolean} - Whether the item could be queued.
      28             :  */
      29           1 : ProductionQueue.prototype.Item.prototype.Queue = function(type, templateName, count)
      30             : {
      31           7 :         if (type == "unit")
      32           7 :                 return this.QueueEntity(templateName, count);
      33             : 
      34           0 :         if (type == "technology")
      35           0 :                 return this.QueueTechnology(templateName);
      36             : 
      37           0 :         warn("Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue (entity: " + this.producer + ").");
      38           0 :         return false;
      39             : };
      40             : 
      41             : /**
      42             :  * @param {string} templateName - The name of the entity to queue.
      43             :  * @param {number} count - The number of entities that should be produced.
      44             :  * @return {boolean} - Whether the batch was successfully created.
      45             :  */
      46           1 : ProductionQueue.prototype.Item.prototype.QueueEntity = function(templateName, count)
      47             : {
      48           7 :         const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer);
      49           7 :         if (!cmpTrainer)
      50           0 :                 return false;
      51           7 :         this.entity = cmpTrainer.QueueBatch(templateName, count, this.metadata);
      52           7 :         if (this.entity == -1)
      53           0 :                 return false;
      54           7 :         this.originalItem = {
      55             :                 "templateName": templateName,
      56             :                 "count": count,
      57             :                 "metadata": this.metadata
      58             :         };
      59             : 
      60           7 :         return true;
      61             : };
      62             : 
      63             : /**
      64             :  * @param {string} templateName - The name of the technology to queue.
      65             :  * @return {boolean} - Whether the technology was successfully queued.
      66             :  */
      67           1 : ProductionQueue.prototype.Item.prototype.QueueTechnology = function(templateName)
      68             : {
      69           0 :         const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher);
      70           0 :         if (!cmpResearcher)
      71           0 :                 return false;
      72           0 :         this.technology = cmpResearcher.QueueTechnology(templateName, this.metadata);
      73           0 :         return this.technology != -1;
      74             : };
      75             : 
      76             : /**
      77             :  * @param {number} id - The id this item needs to get.
      78             :  */
      79           1 : ProductionQueue.prototype.Item.prototype.SetID = function(id)
      80             : {
      81           7 :         this.id = id;
      82             : };
      83             : 
      84           1 : ProductionQueue.prototype.Item.prototype.Stop = function()
      85             : {
      86           1 :         if (this.entity > 0)
      87           1 :                 Engine.QueryInterface(this.producer, IID_Trainer)?.StopBatch(this.entity);
      88             : 
      89           1 :         if (this.technology > 0)
      90           0 :                 Engine.QueryInterface(this.producer, IID_Researcher)?.StopResearching(this.technology);
      91             : };
      92             : 
      93             : /**
      94             :  * Called when the first work is performed.
      95             :  */
      96           1 : ProductionQueue.prototype.Item.prototype.Start = function()
      97             : {
      98           6 :         this.started = true;
      99             : };
     100             : 
     101             : /**
     102             :  * @return {boolean} - Whether there is work done on the item.
     103             :  */
     104           1 : ProductionQueue.prototype.Item.prototype.IsStarted = function()
     105             : {
     106           6 :         return !!this.started;
     107             : };
     108             : 
     109             : /**
     110             :  * @return {boolean} - Whether this item is finished.
     111             :  */
     112           1 : ProductionQueue.prototype.Item.prototype.IsFinished = function()
     113             : {
     114           6 :         return !!this.finished;
     115             : };
     116             : 
     117             : /**
     118             :  * @param {number} allocatedTime - The time allocated to this item.
     119             :  * @return {number} - The time used for this item.
     120             :  */
     121           1 : ProductionQueue.prototype.Item.prototype.Progress = function(allocatedTime)
     122             : {
     123           6 :         if (this.paused)
     124           1 :                 this.Unpause();
     125           6 :         if (this.entity)
     126             :         {
     127           6 :                 const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer);
     128           6 :                 allocatedTime -= cmpTrainer.Progress(this.entity, allocatedTime);
     129           6 :                 if (!cmpTrainer.HasBatch(this.entity))
     130           6 :                         delete this.entity;
     131             :         }
     132           6 :         if (this.technology)
     133             :         {
     134           0 :                 const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher);
     135           0 :                 allocatedTime -= cmpResearcher.Progress(this.technology, allocatedTime);
     136           0 :                 if (!cmpResearcher.HasItem(this.technology))
     137           0 :                         delete this.technology;
     138             :         }
     139           6 :         if (!this.entity && !this.technology)
     140           6 :                 this.finished = true;
     141             : 
     142           6 :         return allocatedTime;
     143             : };
     144             : 
     145           1 : ProductionQueue.prototype.Item.prototype.Pause = function()
     146             : {
     147           1 :         this.paused = true;
     148           1 :         if (this.entity)
     149           1 :                 Engine.QueryInterface(this.producer, IID_Trainer).PauseBatch(this.entity);
     150           1 :         if (this.technology)
     151           0 :                 Engine.QueryInterface(this.producer, IID_Researcher).PauseTechnology(this.technology);
     152             : };
     153             : 
     154           1 : ProductionQueue.prototype.Item.prototype.Unpause = function()
     155             : {
     156           1 :         delete this.paused;
     157             : };
     158             : 
     159             : /**
     160             :  * @return {boolean} - Whether the item is currently paused.
     161             :  */
     162           1 : ProductionQueue.prototype.Item.prototype.IsPaused = function()
     163             : {
     164           0 :         return !!this.paused;
     165             : };
     166             : 
     167             : /**
     168             :  * @return {Object} - Some basic information of this item.
     169             :  */
     170           1 : ProductionQueue.prototype.Item.prototype.GetBasicInfo = function()
     171             : {
     172             :         let result;
     173          11 :         if (this.technology)
     174           0 :                 result = Engine.QueryInterface(this.producer, IID_Researcher).GetResearchingTechnology(this.technology);
     175          11 :         else if (this.entity)
     176          11 :                 result = Engine.QueryInterface(this.producer, IID_Trainer).GetBatch(this.entity);
     177          11 :         result.id = this.id;
     178          11 :         result.paused = this.paused;
     179          11 :         return result;
     180             : };
     181             : 
     182             : /**
     183             :  * @return {Object} - The originally queued item.
     184             :  */
     185           1 : ProductionQueue.prototype.Item.prototype.OriginalItem = function()
     186             : {
     187           1 :         return this.originalItem;
     188             : };
     189             : 
     190           1 : ProductionQueue.prototype.Item.prototype.SerializableAttributes = [
     191             :         "entity",
     192             :         "id",
     193             :         "metadata",
     194             :         "originalItem",
     195             :         "paused",
     196             :         "producer",
     197             :         "started",
     198             :         "technology"
     199             : ];
     200             : 
     201           1 : ProductionQueue.prototype.Item.prototype.Serialize = function()
     202             : {
     203           1 :         const result = {};
     204           1 :         for (const att of this.SerializableAttributes)
     205           8 :                 if (this.hasOwnProperty(att))
     206           5 :                         result[att] = this[att];
     207           1 :         return result;
     208             : };
     209             : 
     210           1 : ProductionQueue.prototype.Item.prototype.Deserialize = function(data)
     211             : {
     212           1 :         for (const att of this.SerializableAttributes)
     213           8 :                 if (att in data)
     214           5 :                         this[att] = data[att];
     215             : };
     216             : 
     217           1 : ProductionQueue.prototype.Init = function()
     218             : {
     219           2 :         this.nextID = 1;
     220           2 :         this.queue = [];
     221             : };
     222             : 
     223           1 : ProductionQueue.prototype.SerializableAttributes = [
     224             :         "autoqueuing",
     225             :         "nextID",
     226             :         "paused",
     227             :         "timer"
     228             : ];
     229             : 
     230           1 : ProductionQueue.prototype.Serialize = function()
     231             : {
     232           1 :         const result = {
     233             :                 "queue": []
     234             :         };
     235           1 :         for (const item of this.queue)
     236           1 :                 result.queue.push(item.Serialize());
     237             : 
     238           1 :         for (const att of this.SerializableAttributes)
     239           4 :                 if (this.hasOwnProperty(att))
     240           2 :                         result[att] = this[att];
     241             : 
     242           1 :         return result;
     243             : };
     244             : 
     245           1 : ProductionQueue.prototype.Deserialize = function(data)
     246             : {
     247           1 :         for (const att of this.SerializableAttributes)
     248           4 :                 if (att in data)
     249           2 :                         this[att] = data[att];
     250             : 
     251           1 :         this.queue = [];
     252             : 
     253           1 :         for (const item of data.queue)
     254             :         {
     255           1 :                 const newItem = new this.Item();
     256           1 :                 newItem.Deserialize(item);
     257           1 :                 this.queue.push(newItem);
     258             :         }
     259             : };
     260             : 
     261             : /**
     262             :  * @return {boolean} - Whether we are automatically queuing items.
     263             :  */
     264           1 : ProductionQueue.prototype.IsAutoQueueing = function()
     265             : {
     266           0 :         return !!this.autoqueuing;
     267             : };
     268             : 
     269             : /**
     270             :  * Turn on Auto-Queue.
     271             :  */
     272           1 : ProductionQueue.prototype.EnableAutoQueue = function()
     273             : {
     274           1 :         this.autoqueuing = true;
     275             : };
     276             : 
     277             : /**
     278             :  * Turn off Auto-Queue.
     279             :  */
     280           1 : ProductionQueue.prototype.DisableAutoQueue = function()
     281             : {
     282           1 :         delete this.autoqueuing;
     283             : };
     284             : 
     285             : /*
     286             :  * Adds a new batch of identical units to train or a technology to research to the production queue.
     287             :  * @param {string} templateName - The template to start production on.
     288             :  * @param {string} type - The type of production (i.e. "unit" or "technology").
     289             :  * @param {number} count - The amount of units to be produced. Ignored for a tech.
     290             :  * @param {any} metadata - Optionally any metadata to be attached to the item.
     291             :  * @param {boolean} pushFront - Whether to push the item to the front of the queue and pause any item(s) currently in progress.
     292             :  *
     293             :  * @return {boolean} - Whether the addition of the item has succeeded.
     294             :  */
     295           1 : ProductionQueue.prototype.AddItem = function(templateName, type, count, metadata, pushFront = false)
     296             : {
     297             :         // TODO: there should be a way for the GUI to determine whether it's going
     298             :         // to be possible to add a batch (based on resource costs and length limits).
     299             : 
     300           7 :         if (!this.queue.length)
     301             :         {
     302           5 :                 const cmpPlayer = QueryOwnerInterface(this.entity);
     303           5 :                 if (!cmpPlayer)
     304           0 :                         return false;
     305           5 :                 const player = cmpPlayer.GetPlayerID();
     306           5 :                 const cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade);
     307           5 :                 if (cmpUpgrade && cmpUpgrade.IsUpgrading())
     308             :                 {
     309           0 :                         let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     310           0 :                         cmpGUIInterface.PushNotification({
     311             :                                 "players": [player],
     312             :                                 "message": markForTranslation("Entity is being upgraded. Cannot start production."),
     313             :                                 "translateMessage": true
     314             :                         });
     315           0 :                         return false;
     316             :                 }
     317             :         }
     318           2 :         else if (this.queue.length >= this.MaxQueueSize)
     319             :         {
     320           0 :                 const cmpPlayer = QueryOwnerInterface(this.entity);
     321           0 :                 if (!cmpPlayer)
     322           0 :                         return false;
     323           0 :                 const player = cmpPlayer.GetPlayerID();
     324           0 :                 const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     325           0 :                 cmpGUIInterface.PushNotification({
     326             :                     "players": [player],
     327             :                     "message": markForTranslation("The production queue is full."),
     328             :                     "translateMessage": true,
     329             :                 });
     330           0 :                 return false;
     331             :         }
     332             : 
     333           7 :         const item = new this.Item(this.entity, metadata);
     334           7 :         if (!item.Queue(type, templateName, count))
     335           0 :                 return false;
     336             : 
     337           7 :         item.SetID(this.nextID++);
     338           7 :         if (pushFront)
     339             :         {
     340           1 :                 this.queue[0]?.Pause();
     341           1 :                 this.queue.unshift(item);
     342             :         }
     343             :         else
     344           6 :                 this.queue.push(item);
     345             : 
     346           7 :         Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
     347             : 
     348           7 :         if (!this.timer)
     349           4 :                 this.StartTimer();
     350           7 :         return true;
     351             : };
     352             : 
     353             : /*
     354             :  * @param {number} - The ID of the item to remove from the queue.
     355             :  */
     356           1 : ProductionQueue.prototype.RemoveItem = function(id)
     357             : {
     358           1 :         let itemIndex = this.queue.findIndex(item => item.id == id);
     359           1 :         if (itemIndex == -1)
     360           0 :                 return;
     361             : 
     362           1 :         this.queue.splice(itemIndex, 1)[0].Stop();
     363             : 
     364           1 :         Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
     365             : 
     366           1 :         if (!this.queue.length)
     367           1 :                 this.StopTimer();
     368             : };
     369             : 
     370           1 : ProductionQueue.prototype.SetAnimation = function(name)
     371             : {
     372          10 :         let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     373          10 :         if (cmpVisual)
     374           0 :                 cmpVisual.SelectAnimation(name, false, 1);
     375             : };
     376             : 
     377             : /*
     378             :  * Returns basic data from all batches in the production queue.
     379             :  */
     380           1 : ProductionQueue.prototype.GetQueue = function()
     381             : {
     382          11 :         return this.queue.map(item => item.GetBasicInfo());
     383             : };
     384             : 
     385             : /*
     386             :  * Removes all existing batches from the queue.
     387             :  */
     388           1 : ProductionQueue.prototype.ResetQueue = function()
     389             : {
     390           0 :         while (this.queue.length)
     391           0 :                 this.RemoveItem(this.queue[0].id);
     392             : 
     393           0 :         this.DisableAutoQueue();
     394             : };
     395             : 
     396             : /*
     397             :  * Increments progress on the first item in the production queue.
     398             :  * @param {Object} data - Unused in this case.
     399             :  * @param {number} lateness - The time passed since the expected time to fire the function.
     400             :  */
     401           1 : ProductionQueue.prototype.ProgressTimeout = function(data, lateness)
     402             : {
     403           4 :         if (this.paused)
     404           0 :                 return;
     405             : 
     406             :         // Allocate available time to as many queue items as it takes
     407             :         // until we've used up all the time (so that we work accurately
     408             :         // with items that take fractions of a second).
     409           4 :         let time = this.ProgressInterval + lateness;
     410             : 
     411           4 :         while (this.queue.length)
     412             :         {
     413           6 :                 let item = this.queue[0];
     414           6 :                 if (!item.IsStarted())
     415             :                 {
     416           6 :                         if (item.entity)
     417           6 :                                 this.SetAnimation("training");
     418           6 :                         if (item.technology)
     419           0 :                                 this.SetAnimation("researching");
     420             : 
     421           6 :                         item.Start();
     422             :                 }
     423           6 :                 time -= item.Progress(time);
     424           6 :                 if (!item.IsFinished())
     425             :                 {
     426           0 :                         Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
     427           0 :                         return;
     428             :                 }
     429             : 
     430           6 :                 this.queue.shift();
     431           6 :                 Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
     432             : 
     433             :                 // If autoqueuing, push a new unit on the queue immediately,
     434             :                 // but don't start right away. This 'wastes' some time, making
     435             :                 // autoqueue slightly worse than regular queuing, and also ensures
     436             :                 // that autoqueue doesn't train more than one item per turn,
     437             :                 // if the units would take fewer than ProgressInterval ms to train.
     438           6 :                 if (this.autoqueuing)
     439             :                 {
     440           1 :                         const autoqueueData = item.OriginalItem();
     441           1 :                         if (!autoqueueData)
     442           0 :                                 continue;
     443             : 
     444           1 :                         if (!this.AddItem(autoqueueData.templateName, "unit", autoqueueData.count, autoqueueData.metadata))
     445             :                         {
     446           0 :                                 this.DisableAutoQueue();
     447           0 :                                 const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     448           0 :                                 cmpGUIInterface.PushNotification({
     449             :                                         "players": [QueryOwnerInterface(this.entity).GetPlayerID()],
     450             :                                         "message": markForTranslation("Could not auto-queue unit, de-activating."),
     451             :                                         "translateMessage": true
     452             :                                 });
     453             :                         }
     454           1 :                         break;
     455             :                 }
     456             :         }
     457             : 
     458           4 :         if (!this.queue.length)
     459           3 :                 this.StopTimer();
     460             : };
     461             : 
     462           1 : ProductionQueue.prototype.PauseProduction = function()
     463             : {
     464           0 :         this.StopTimer();
     465           0 :         this.paused = true;
     466           0 :         this.queue[0]?.Pause();
     467             : };
     468             : 
     469           1 : ProductionQueue.prototype.UnpauseProduction = function()
     470             : {
     471           0 :         delete this.paused;
     472           0 :         this.StartTimer();
     473             : };
     474             : 
     475           1 : ProductionQueue.prototype.StartTimer = function()
     476             : {
     477           4 :         if (this.timer)
     478           0 :                 return;
     479             : 
     480           4 :         this.timer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).SetInterval(
     481             :                 this.entity,
     482             :                 IID_ProductionQueue,
     483             :                 "ProgressTimeout",
     484             :                 this.ProgressInterval,
     485             :                 this.ProgressInterval,
     486             :                 null
     487             :         );
     488             : };
     489             : 
     490           1 : ProductionQueue.prototype.StopTimer = function()
     491             : {
     492           4 :         if (!this.timer)
     493           0 :                 return;
     494             : 
     495           4 :         this.SetAnimation("idle");
     496           4 :         Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).CancelTimer(this.timer);
     497           4 :         delete this.timer;
     498             : };
     499             : 
     500             : /**
     501             :  * @return {boolean} - Whether this entity is currently producing.
     502             :  */
     503           1 : ProductionQueue.prototype.HasQueuedProduction = function()
     504             : {
     505           0 :         return this.queue.length > 0;
     506             : };
     507             : 
     508           1 : ProductionQueue.prototype.OnOwnershipChanged = function(msg)
     509             : {
     510             :         // Reset the production queue whenever the owner changes.
     511             :         // (This should prevent players getting surprised when they capture
     512             :         // an enemy building, and then loads of the enemy's civ's soldiers get
     513             :         // created from it. Also it means we don't have to worry about
     514             :         // updating the reserved pop slots.)
     515           0 :         this.ResetQueue();
     516             : };
     517             : 
     518           1 : ProductionQueue.prototype.OnGarrisonedStateChanged = function(msg)
     519             : {
     520           0 :         if (msg.holderID != INVALID_ENTITY)
     521           0 :                 this.PauseProduction();
     522             :         else
     523           0 :                 this.UnpauseProduction();
     524             : };
     525             : 
     526           1 : Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue);

Generated by: LCOV version 1.14