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);
|