mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 13:53:57 -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>
381 lines
10 KiB
JavaScript
381 lines
10 KiB
JavaScript
function Researcher() {}
|
|
|
|
Researcher.prototype.Schema =
|
|
"<a:help>Allows the entity to research technologies.</a:help>" +
|
|
"<a:example>" +
|
|
"<TechCostMultiplier>" +
|
|
"<food>0.5</food>" +
|
|
"<wood>0.1</wood>" +
|
|
"<stone>0</stone>" +
|
|
"<metal>2</metal>" +
|
|
"<time>0.9</time>" +
|
|
"</TechCostMultiplier>" +
|
|
"<Technologies datatype='tokens'>" +
|
|
"\n phase_town_{civ}\n phase_metropolis_ptol\n unlock_shared_los\n wonder_population_cap\n " +
|
|
"</Technologies>" +
|
|
"</a:example>" +
|
|
"<optional>" +
|
|
"<element name='Technologies' a:help='Space-separated list of technology names that this building can research. When present, the special string \"{civ}\" will be automatically replaced either by the civ code of the building's owner if such a tech exists, or by \"generic\".'>" +
|
|
"<attribute name='datatype'>" +
|
|
"<value>tokens</value>" +
|
|
"</attribute>" +
|
|
"<text/>" +
|
|
"</element>" +
|
|
"</optional>" +
|
|
"<optional>" +
|
|
"<element name='TechCostMultiplier' a:help='Multiplier to modify resources cost and research time of technologies researched in this building.'>" +
|
|
Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
|
|
"</element>" +
|
|
"</optional>";
|
|
|
|
/**
|
|
* This object represents a technology being researched.
|
|
* @param {string} templateName - The name of the template we ought to research.
|
|
* @param {number} researcher - The entity ID of our researcher.
|
|
* @param {string} metadata - Optionally any metadata to attach to us.
|
|
*/
|
|
Researcher.prototype.Item = function(templateName, researcher, metadata)
|
|
{
|
|
this.templateName = templateName;
|
|
this.researcher = researcher;
|
|
this.metadata = metadata;
|
|
};
|
|
|
|
/**
|
|
* Prepare for the queue.
|
|
* @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
|
|
* @return {boolean} - Whether the item was successfully initiated.
|
|
*/
|
|
Researcher.prototype.Item.prototype.Queue = function(techCostMultiplier)
|
|
{
|
|
this.player = QueryOwnerInterface(this.researcher).GetPlayerID();
|
|
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
|
|
if (!cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher, techCostMultiplier))
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.Stop = function()
|
|
{
|
|
QueryPlayerIDInterface(this.player, IID_TechnologyManager).StoppedResearch(this.templateName);
|
|
delete this.started;
|
|
};
|
|
|
|
/**
|
|
* Called when the first work is performed.
|
|
*/
|
|
Researcher.prototype.Item.prototype.Start = function()
|
|
{
|
|
this.started = true;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.Finish = function()
|
|
{
|
|
this.finished = true;
|
|
};
|
|
|
|
/**
|
|
* @param {number} allocatedTime - The time allocated to this item.
|
|
* @return {number} - The time used for this item.
|
|
*/
|
|
Researcher.prototype.Item.prototype.Progress = function(allocatedTime)
|
|
{
|
|
if (!this.started)
|
|
this.Start();
|
|
if (this.paused)
|
|
this.Unpause();
|
|
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
|
|
const usedTime = cmpTechnologyManager.Progress(this.templateName, allocatedTime);
|
|
if (!cmpTechnologyManager.IsTechnologyQueued(this.templateName))
|
|
this.Finish();
|
|
return usedTime;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.Pause = function()
|
|
{
|
|
QueryPlayerIDInterface(this.player, IID_TechnologyManager).Pause(this.templateName);
|
|
this.paused = true;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.Unpause = function()
|
|
{
|
|
delete this.paused;
|
|
};
|
|
|
|
/**
|
|
* @return {Object} - Some basic information of this item.
|
|
*/
|
|
Researcher.prototype.Item.prototype.GetBasicInfo = function()
|
|
{
|
|
const result = QueryPlayerIDInterface(this.player, IID_TechnologyManager).GetBasicInfo(this.templateName);
|
|
result.technologyTemplate = this.templateName;
|
|
result.metadata = this.metadata;
|
|
return result;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.SerializableAttributes = [
|
|
"metadata",
|
|
"paused",
|
|
"player",
|
|
"researcher",
|
|
"started",
|
|
"templateName"
|
|
];
|
|
|
|
Researcher.prototype.Item.prototype.Serialize = function(id)
|
|
{
|
|
const result = {
|
|
"id": id
|
|
};
|
|
for (const att of this.SerializableAttributes)
|
|
if (Object.hasOwn(this, att))
|
|
result[att] = this[att];
|
|
return result;
|
|
};
|
|
|
|
Researcher.prototype.Item.prototype.Deserialize = function(data)
|
|
{
|
|
for (const att of this.SerializableAttributes)
|
|
if (att in data)
|
|
this[att] = data[att];
|
|
};
|
|
|
|
Researcher.prototype.Init = function()
|
|
{
|
|
this.nextID = 1;
|
|
this.queue = new Map();
|
|
};
|
|
|
|
Researcher.prototype.Serialize = function()
|
|
{
|
|
const queue = [];
|
|
for (const [id, item] of this.queue)
|
|
queue.push(item.Serialize(id));
|
|
|
|
return {
|
|
"nextID": this.nextID,
|
|
"queue": queue
|
|
};
|
|
};
|
|
|
|
Researcher.prototype.Deserialize = function(data)
|
|
{
|
|
this.Init();
|
|
this.nextID = data.nextID;
|
|
for (const item of data.queue)
|
|
{
|
|
const newItem = new this.Item();
|
|
newItem.Deserialize(item);
|
|
this.queue.set(item.id, newItem);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Returns list of technologies that can be researched by this entity.
|
|
*/
|
|
Researcher.prototype.GetTechnologiesList = function()
|
|
{
|
|
const string = ApplyValueModificationsToEntity("Researcher/Technologies/_string", this.template?.Technologies?._string || "", this.entity);
|
|
if (!string)
|
|
return [];
|
|
|
|
const owner = Engine.QueryInterface(this.entity, IID_Ownership)?.GetOwner();
|
|
if (!owner || owner === INVALID_PLAYER)
|
|
return [];
|
|
|
|
const playerEnt = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(owner);
|
|
if (!playerEnt)
|
|
return [];
|
|
|
|
const cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
|
|
if (!cmpTechnologyManager)
|
|
return [];
|
|
|
|
const cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
|
|
if (!cmpPlayer)
|
|
return [];
|
|
|
|
let techs = string.split(/\s+/);
|
|
|
|
// Replace the civ specific technologies.
|
|
const civ = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv();
|
|
for (let i = 0; i < techs.length; ++i)
|
|
{
|
|
const tech = techs[i];
|
|
if (tech.indexOf("{civ}") == -1)
|
|
continue;
|
|
const civTech = tech.replace("{civ}", civ);
|
|
techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic");
|
|
}
|
|
|
|
// Remove any technologies that can't be researched by this civ.
|
|
techs = techs.filter(tech =>
|
|
cmpTechnologyManager.CheckTechnologyRequirements(
|
|
DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), civ),
|
|
true));
|
|
|
|
const techList = [];
|
|
const superseded = {};
|
|
|
|
const disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
|
|
|
|
// Add any top level technologies to an array which corresponds to the displayed icons.
|
|
// Also store what technology is superseded in the superseded object { "tech1":"techWhichSupercedesTech1", ... }.
|
|
for (const tech of techs)
|
|
{
|
|
if (disabledTechnologies && disabledTechnologies[tech])
|
|
continue;
|
|
|
|
const template = TechnologyTemplates.Get(tech);
|
|
if (!template.supersedes || techs.indexOf(template.supersedes) === -1)
|
|
techList.push(tech);
|
|
else
|
|
superseded[template.supersedes] = tech;
|
|
}
|
|
|
|
// Now make researched/in progress techs invisible.
|
|
for (const i in techList)
|
|
{
|
|
let tech = techList[i];
|
|
while (this.IsTechnologyResearchedOrInProgress(tech))
|
|
tech = superseded[tech];
|
|
|
|
techList[i] = tech;
|
|
}
|
|
|
|
const ret = [];
|
|
|
|
// This inserts the techs into the correct positions to line up the technology pairs.
|
|
for (let i = 0; i < techList.length; ++i)
|
|
{
|
|
const tech = techList[i];
|
|
if (!tech)
|
|
{
|
|
ret[i] = undefined;
|
|
continue;
|
|
}
|
|
|
|
const template = TechnologyTemplates.Get(tech);
|
|
if (template.top)
|
|
ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
|
|
else
|
|
ret[i] = tech;
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* @return {Object} - The multipliers to change the costs of any research with.
|
|
*/
|
|
Researcher.prototype.GetTechCostMultiplier = function()
|
|
{
|
|
const techCostMultiplier = {};
|
|
for (const res of Resources.GetCodes().concat(["time"]))
|
|
techCostMultiplier[res] = ApplyValueModificationsToEntity(
|
|
"Researcher/TechCostMultiplier/" + res,
|
|
+(this.template?.TechCostMultiplier?.[res] || 1),
|
|
this.entity);
|
|
|
|
return techCostMultiplier;
|
|
};
|
|
|
|
/**
|
|
* Checks whether we can research the given technology, minding paired techs.
|
|
*/
|
|
Researcher.prototype.IsTechnologyResearchedOrInProgress = function(tech)
|
|
{
|
|
if (!tech)
|
|
return false;
|
|
|
|
const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
|
|
if (!cmpTechnologyManager)
|
|
return false;
|
|
|
|
const template = TechnologyTemplates.Get(tech);
|
|
if (template.top)
|
|
return cmpTechnologyManager.IsTechnologyResearched(template.top) ||
|
|
cmpTechnologyManager.IsInProgress(template.top) ||
|
|
cmpTechnologyManager.IsTechnologyResearched(template.bottom) ||
|
|
cmpTechnologyManager.IsInProgress(template.bottom);
|
|
|
|
return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech);
|
|
};
|
|
|
|
/**
|
|
* @param {string} templateName - The technology to queue.
|
|
* @param {string} metadata - Any metadata attached to the item.
|
|
* @return {number} - The ID of the item. -1 if the item could not be researched.
|
|
*/
|
|
Researcher.prototype.QueueTechnology = function(templateName, metadata)
|
|
{
|
|
if (!this.GetTechnologiesList().some(tech =>
|
|
tech && (tech == templateName ||
|
|
tech.pair && (tech.top == templateName || tech.bottom == templateName))))
|
|
{
|
|
error("This entity cannot research " + templateName + ".");
|
|
return -1;
|
|
}
|
|
|
|
const item = new this.Item(templateName, this.entity, metadata);
|
|
|
|
const techCostMultiplier = this.GetTechCostMultiplier();
|
|
if (!item.Queue(techCostMultiplier))
|
|
return -1;
|
|
|
|
const id = this.nextID++;
|
|
this.queue.set(id, item);
|
|
return id;
|
|
};
|
|
|
|
/**
|
|
* @param {number} id - The id of the technology researched here we need to stop.
|
|
*/
|
|
Researcher.prototype.StopResearching = function(id)
|
|
{
|
|
this.queue.get(id).Stop();
|
|
this.queue.delete(id);
|
|
};
|
|
|
|
/**
|
|
* @param {number} id - The id of the technology.
|
|
*/
|
|
Researcher.prototype.PauseTechnology = function(id)
|
|
{
|
|
this.queue.get(id).Pause();
|
|
};
|
|
|
|
/**
|
|
* @param {number} id - The ID of the item to check.
|
|
* @return {boolean} - Whether we are currently training the item.
|
|
*/
|
|
Researcher.prototype.HasItem = function(id)
|
|
{
|
|
return this.queue.has(id);
|
|
};
|
|
|
|
/**
|
|
* @parameter {number} id - The id of the research.
|
|
* @return {Object} - Some basic information about the research.
|
|
*/
|
|
Researcher.prototype.GetResearchingTechnology = function(id)
|
|
{
|
|
return this.queue.get(id).GetBasicInfo();
|
|
};
|
|
|
|
/**
|
|
* @param {number} id - The ID of the item we spent time on.
|
|
* @param {number} allocatedTime - The time we spent on the given item.
|
|
* @return {number} - The time we've actually used.
|
|
*/
|
|
Researcher.prototype.Progress = function(id, allocatedTime)
|
|
{
|
|
const item = this.queue.get(id);
|
|
const usedTime = item.Progress(allocatedTime);
|
|
if (item.finished)
|
|
this.queue.delete(id);
|
|
return usedTime;
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_Researcher, "Researcher", Researcher);
|