mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Enable recommended rule 'no-prototype-builtins' [1] and manually fix violations. [1] https://eslint.org/docs/latest/rules/no-prototype-builtins Ref: #8068 Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
526 lines
13 KiB
JavaScript
526 lines
13 KiB
JavaScript
function ProductionQueue() {}
|
|
|
|
ProductionQueue.prototype.Schema =
|
|
"<a:help>Helps the building to train new units and research technologies.</a:help>" +
|
|
"<empty/>";
|
|
|
|
ProductionQueue.prototype.ProgressInterval = 1000;
|
|
ProductionQueue.prototype.MaxQueueSize = 16;
|
|
|
|
/**
|
|
* This object represents an item in the queue.
|
|
*
|
|
* @param {number} producer - The entity ID of our producer.
|
|
* @param {string} metadata - Optionally any metadata attached to us.
|
|
*/
|
|
ProductionQueue.prototype.Item = function(producer, metadata)
|
|
{
|
|
this.producer = producer;
|
|
this.metadata = metadata;
|
|
};
|
|
|
|
/**
|
|
* @param {string} type - The type of queue to use.
|
|
* @param {string} templateName - The template to queue.
|
|
* @param {number} count - The amount of template to queue. Only applicable for type == "unit".
|
|
*
|
|
* @return {boolean} - Whether the item could be queued.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.Queue = function(type, templateName, count)
|
|
{
|
|
if (type == "unit")
|
|
return this.QueueEntity(templateName, count);
|
|
|
|
if (type == "technology")
|
|
return this.QueueTechnology(templateName);
|
|
|
|
warn("Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue (entity: " + this.producer + ").");
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* @param {string} templateName - The name of the entity to queue.
|
|
* @param {number} count - The number of entities that should be produced.
|
|
* @return {boolean} - Whether the batch was successfully created.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.QueueEntity = function(templateName, count)
|
|
{
|
|
const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer);
|
|
if (!cmpTrainer)
|
|
return false;
|
|
this.entity = cmpTrainer.QueueBatch(templateName, count, this.metadata);
|
|
if (this.entity == -1)
|
|
return false;
|
|
this.originalItem = {
|
|
"templateName": templateName,
|
|
"count": count,
|
|
"metadata": this.metadata
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* @param {string} templateName - The name of the technology to queue.
|
|
* @return {boolean} - Whether the technology was successfully queued.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.QueueTechnology = function(templateName)
|
|
{
|
|
const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher);
|
|
if (!cmpResearcher)
|
|
return false;
|
|
this.technology = cmpResearcher.QueueTechnology(templateName, this.metadata);
|
|
return this.technology != -1;
|
|
};
|
|
|
|
/**
|
|
* @param {number} id - The id this item needs to get.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.SetID = function(id)
|
|
{
|
|
this.id = id;
|
|
};
|
|
|
|
ProductionQueue.prototype.Item.prototype.Stop = function()
|
|
{
|
|
if (this.entity > 0)
|
|
Engine.QueryInterface(this.producer, IID_Trainer)?.StopBatch(this.entity);
|
|
|
|
if (this.technology > 0)
|
|
Engine.QueryInterface(this.producer, IID_Researcher)?.StopResearching(this.technology);
|
|
};
|
|
|
|
/**
|
|
* Called when the first work is performed.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.Start = function()
|
|
{
|
|
this.started = true;
|
|
};
|
|
|
|
/**
|
|
* @return {boolean} - Whether there is work done on the item.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.IsStarted = function()
|
|
{
|
|
return !!this.started;
|
|
};
|
|
|
|
/**
|
|
* @return {boolean} - Whether this item is finished.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.IsFinished = function()
|
|
{
|
|
return !!this.finished;
|
|
};
|
|
|
|
/**
|
|
* @param {number} allocatedTime - The time allocated to this item.
|
|
* @return {number} - The time used for this item.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.Progress = function(allocatedTime)
|
|
{
|
|
if (this.paused)
|
|
this.Unpause();
|
|
if (this.entity)
|
|
{
|
|
const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer);
|
|
allocatedTime -= cmpTrainer.Progress(this.entity, allocatedTime);
|
|
if (!cmpTrainer.HasBatch(this.entity))
|
|
delete this.entity;
|
|
}
|
|
if (this.technology)
|
|
{
|
|
const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher);
|
|
allocatedTime -= cmpResearcher.Progress(this.technology, allocatedTime);
|
|
if (!cmpResearcher.HasItem(this.technology))
|
|
delete this.technology;
|
|
}
|
|
if (!this.entity && !this.technology)
|
|
this.finished = true;
|
|
|
|
return allocatedTime;
|
|
};
|
|
|
|
ProductionQueue.prototype.Item.prototype.Pause = function()
|
|
{
|
|
this.paused = true;
|
|
if (this.entity)
|
|
Engine.QueryInterface(this.producer, IID_Trainer).PauseBatch(this.entity);
|
|
if (this.technology)
|
|
Engine.QueryInterface(this.producer, IID_Researcher).PauseTechnology(this.technology);
|
|
};
|
|
|
|
ProductionQueue.prototype.Item.prototype.Unpause = function()
|
|
{
|
|
delete this.paused;
|
|
};
|
|
|
|
/**
|
|
* @return {boolean} - Whether the item is currently paused.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.IsPaused = function()
|
|
{
|
|
return !!this.paused;
|
|
};
|
|
|
|
/**
|
|
* @return {Object} - Some basic information of this item.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.GetBasicInfo = function()
|
|
{
|
|
let result;
|
|
if (this.technology)
|
|
result = Engine.QueryInterface(this.producer, IID_Researcher).GetResearchingTechnology(this.technology);
|
|
else if (this.entity)
|
|
result = Engine.QueryInterface(this.producer, IID_Trainer).GetBatch(this.entity);
|
|
result.id = this.id;
|
|
result.paused = this.paused;
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* @return {Object} - The originally queued item.
|
|
*/
|
|
ProductionQueue.prototype.Item.prototype.OriginalItem = function()
|
|
{
|
|
return this.originalItem;
|
|
};
|
|
|
|
ProductionQueue.prototype.Item.prototype.SerializableAttributes = [
|
|
"entity",
|
|
"id",
|
|
"metadata",
|
|
"originalItem",
|
|
"paused",
|
|
"producer",
|
|
"started",
|
|
"technology"
|
|
];
|
|
|
|
ProductionQueue.prototype.Item.prototype.Serialize = function()
|
|
{
|
|
const result = {};
|
|
for (const att of this.SerializableAttributes)
|
|
if (Object.hasOwn(this, att))
|
|
result[att] = this[att];
|
|
return result;
|
|
};
|
|
|
|
ProductionQueue.prototype.Item.prototype.Deserialize = function(data)
|
|
{
|
|
for (const att of this.SerializableAttributes)
|
|
if (att in data)
|
|
this[att] = data[att];
|
|
};
|
|
|
|
ProductionQueue.prototype.Init = function()
|
|
{
|
|
this.nextID = 1;
|
|
this.queue = [];
|
|
};
|
|
|
|
ProductionQueue.prototype.SerializableAttributes = [
|
|
"autoqueuing",
|
|
"nextID",
|
|
"paused",
|
|
"timer"
|
|
];
|
|
|
|
ProductionQueue.prototype.Serialize = function()
|
|
{
|
|
const result = {
|
|
"queue": []
|
|
};
|
|
for (const item of this.queue)
|
|
result.queue.push(item.Serialize());
|
|
|
|
for (const att of this.SerializableAttributes)
|
|
if (Object.hasOwn(this, att))
|
|
result[att] = this[att];
|
|
|
|
return result;
|
|
};
|
|
|
|
ProductionQueue.prototype.Deserialize = function(data)
|
|
{
|
|
for (const att of this.SerializableAttributes)
|
|
if (att in data)
|
|
this[att] = data[att];
|
|
|
|
this.queue = [];
|
|
|
|
for (const item of data.queue)
|
|
{
|
|
const newItem = new this.Item();
|
|
newItem.Deserialize(item);
|
|
this.queue.push(newItem);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @return {boolean} - Whether we are automatically queuing items.
|
|
*/
|
|
ProductionQueue.prototype.IsAutoQueueing = function()
|
|
{
|
|
return !!this.autoqueuing;
|
|
};
|
|
|
|
/**
|
|
* Turn on Auto-Queue.
|
|
*/
|
|
ProductionQueue.prototype.EnableAutoQueue = function()
|
|
{
|
|
this.autoqueuing = true;
|
|
};
|
|
|
|
/**
|
|
* Turn off Auto-Queue.
|
|
*/
|
|
ProductionQueue.prototype.DisableAutoQueue = function()
|
|
{
|
|
delete this.autoqueuing;
|
|
};
|
|
|
|
/*
|
|
* Adds a new batch of identical units to train or a technology to research to the production queue.
|
|
* @param {string} templateName - The template to start production on.
|
|
* @param {string} type - The type of production (i.e. "unit" or "technology").
|
|
* @param {number} count - The amount of units to be produced. Ignored for a tech.
|
|
* @param {any} metadata - Optionally any metadata to be attached to the item.
|
|
* @param {boolean} pushFront - Whether to push the item to the front of the queue and pause any item(s) currently in progress.
|
|
*
|
|
* @return {boolean} - Whether the addition of the item has succeeded.
|
|
*/
|
|
ProductionQueue.prototype.AddItem = function(templateName, type, count, metadata, pushFront = false)
|
|
{
|
|
// TODO: there should be a way for the GUI to determine whether it's going
|
|
// to be possible to add a batch (based on resource costs and length limits).
|
|
|
|
if (!this.queue.length)
|
|
{
|
|
const cmpPlayer = QueryOwnerInterface(this.entity);
|
|
if (!cmpPlayer)
|
|
return false;
|
|
const player = cmpPlayer.GetPlayerID();
|
|
const cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade);
|
|
if (cmpUpgrade && cmpUpgrade.IsUpgrading())
|
|
{
|
|
const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
|
cmpGUIInterface.PushNotification({
|
|
"players": [player],
|
|
"message": markForTranslation("Entity is being upgraded. Cannot start production."),
|
|
"translateMessage": true
|
|
});
|
|
return false;
|
|
}
|
|
}
|
|
else if (this.queue.length >= this.MaxQueueSize)
|
|
{
|
|
const cmpPlayer = QueryOwnerInterface(this.entity);
|
|
if (!cmpPlayer)
|
|
return false;
|
|
const player = cmpPlayer.GetPlayerID();
|
|
const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
|
cmpGUIInterface.PushNotification({
|
|
"players": [player],
|
|
"message": markForTranslation("The production queue is full."),
|
|
"translateMessage": true,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
const item = new this.Item(this.entity, metadata);
|
|
if (!item.Queue(type, templateName, count))
|
|
return false;
|
|
|
|
item.SetID(this.nextID++);
|
|
if (pushFront)
|
|
{
|
|
this.queue[0]?.Pause();
|
|
this.queue.unshift(item);
|
|
}
|
|
else
|
|
this.queue.push(item);
|
|
|
|
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
|
|
|
|
if (!this.timer)
|
|
this.StartTimer();
|
|
return true;
|
|
};
|
|
|
|
/*
|
|
* @param {number} - The ID of the item to remove from the queue.
|
|
*/
|
|
ProductionQueue.prototype.RemoveItem = function(id)
|
|
{
|
|
const itemIndex = this.queue.findIndex(item => item.id == id);
|
|
if (itemIndex == -1)
|
|
return;
|
|
|
|
this.queue.splice(itemIndex, 1)[0].Stop();
|
|
|
|
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
|
|
|
|
if (!this.queue.length)
|
|
this.StopTimer();
|
|
};
|
|
|
|
ProductionQueue.prototype.SetAnimation = function(name)
|
|
{
|
|
const cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
|
if (cmpVisual)
|
|
cmpVisual.SelectAnimation(name, false, 1);
|
|
};
|
|
|
|
/*
|
|
* Returns basic data from all batches in the production queue.
|
|
*/
|
|
ProductionQueue.prototype.GetQueue = function()
|
|
{
|
|
return this.queue.map(item => item.GetBasicInfo());
|
|
};
|
|
|
|
/*
|
|
* Removes all existing batches from the queue.
|
|
*/
|
|
ProductionQueue.prototype.ResetQueue = function()
|
|
{
|
|
while (this.queue.length)
|
|
this.RemoveItem(this.queue[0].id);
|
|
|
|
this.DisableAutoQueue();
|
|
};
|
|
|
|
/*
|
|
* Increments progress on the first item in the production queue.
|
|
* @param {Object} data - Unused in this case.
|
|
* @param {number} lateness - The time passed since the expected time to fire the function.
|
|
*/
|
|
ProductionQueue.prototype.ProgressTimeout = function(data, lateness)
|
|
{
|
|
if (this.paused)
|
|
return;
|
|
|
|
// Allocate available time to as many queue items as it takes
|
|
// until we've used up all the time (so that we work accurately
|
|
// with items that take fractions of a second).
|
|
let time = this.ProgressInterval + lateness;
|
|
|
|
while (this.queue.length)
|
|
{
|
|
const item = this.queue[0];
|
|
if (!item.IsStarted())
|
|
{
|
|
if (item.entity)
|
|
this.SetAnimation("training");
|
|
if (item.technology)
|
|
this.SetAnimation("researching");
|
|
|
|
item.Start();
|
|
}
|
|
time -= item.Progress(time);
|
|
if (!item.IsFinished())
|
|
{
|
|
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
|
|
return;
|
|
}
|
|
|
|
this.queue.shift();
|
|
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
|
|
|
|
// If autoqueuing, push a new unit on the queue immediately,
|
|
// but don't start right away. This 'wastes' some time, making
|
|
// autoqueue slightly worse than regular queuing, and also ensures
|
|
// that autoqueue doesn't train more than one item per turn,
|
|
// if the units would take fewer than ProgressInterval ms to train.
|
|
if (this.autoqueuing)
|
|
{
|
|
const autoqueueData = item.OriginalItem();
|
|
if (!autoqueueData)
|
|
continue;
|
|
|
|
if (!this.AddItem(autoqueueData.templateName, "unit", autoqueueData.count, autoqueueData.metadata))
|
|
{
|
|
this.DisableAutoQueue();
|
|
const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
|
cmpGUIInterface.PushNotification({
|
|
"players": [QueryOwnerInterface(this.entity).GetPlayerID()],
|
|
"message": markForTranslation("Could not auto-queue unit, de-activating."),
|
|
"translateMessage": true
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!this.queue.length)
|
|
this.StopTimer();
|
|
};
|
|
|
|
ProductionQueue.prototype.PauseProduction = function()
|
|
{
|
|
this.StopTimer();
|
|
this.paused = true;
|
|
this.queue[0]?.Pause();
|
|
};
|
|
|
|
ProductionQueue.prototype.UnpauseProduction = function()
|
|
{
|
|
delete this.paused;
|
|
this.StartTimer();
|
|
};
|
|
|
|
ProductionQueue.prototype.StartTimer = function()
|
|
{
|
|
if (this.timer)
|
|
return;
|
|
|
|
this.timer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).SetInterval(
|
|
this.entity,
|
|
IID_ProductionQueue,
|
|
"ProgressTimeout",
|
|
this.ProgressInterval,
|
|
this.ProgressInterval,
|
|
null
|
|
);
|
|
};
|
|
|
|
ProductionQueue.prototype.StopTimer = function()
|
|
{
|
|
if (!this.timer)
|
|
return;
|
|
|
|
this.SetAnimation("idle");
|
|
Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).CancelTimer(this.timer);
|
|
delete this.timer;
|
|
};
|
|
|
|
/**
|
|
* @return {boolean} - Whether this entity is currently producing.
|
|
*/
|
|
ProductionQueue.prototype.HasQueuedProduction = function()
|
|
{
|
|
return this.queue.length > 0;
|
|
};
|
|
|
|
ProductionQueue.prototype.OnOwnershipChanged = function(msg)
|
|
{
|
|
// Reset the production queue whenever the owner changes.
|
|
// (This should prevent players getting surprised when they capture
|
|
// an enemy building, and then loads of the enemy's civ's soldiers get
|
|
// created from it. Also it means we don't have to worry about
|
|
// updating the reserved pop slots.)
|
|
this.ResetQueue();
|
|
};
|
|
|
|
ProductionQueue.prototype.OnGarrisonedStateChanged = function(msg)
|
|
{
|
|
if (msg.holderID != INVALID_ENTITY)
|
|
this.PauseProduction();
|
|
else
|
|
this.UnpauseProduction();
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue);
|