Index: binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- binaries/data/mods/public/simulation/components/Player.js
+++ binaries/data/mods/public/simulation/components/Player.js
@@ -390,6 +390,16 @@
return true;
};
+Player.prototype.RefundResources = function(amounts)
+{
+ const cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
+ if (cmpStatisticsTracker)
+ for (const type in amounts)
+ cmpStatisticsTracker.IncreaseResourceUsedCounter(type, -amounts[type]);
+
+ this.AddResources(amounts);
+};
+
Player.prototype.GetNextTradingGoods = function()
{
let value = randFloat(0, 100);
Index: binaries/data/mods/public/simulation/components/ProductionQueue.js
===================================================================
--- binaries/data/mods/public/simulation/components/ProductionQueue.js
+++ binaries/data/mods/public/simulation/components/ProductionQueue.js
@@ -213,129 +213,6 @@
};
/*
- * Returns list of technologies that can be researched by this building.
- */
-ProductionQueue.prototype.GetTechnologiesList = function()
-{
- if (!this.template.Technologies)
- return [];
-
- let string = this.template.Technologies._string;
- string = ApplyValueModificationsToEntity("ProductionQueue/Technologies/_string", string, this.entity);
-
- if (!string)
- return [];
-
- let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
- if (!cmpTechnologyManager)
- return [];
-
- let cmpPlayer = QueryOwnerInterface(this.entity);
- if (!cmpPlayer)
- return [];
-
- let techs = string.split(/\s+/);
-
- // Replace the civ specific technologies.
- for (let i = 0; i < techs.length; ++i)
- {
- let tech = techs[i];
- if (tech.indexOf("{civ}") == -1)
- continue;
- let civTech = tech.replace("{civ}", cmpPlayer.GetCiv());
- 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), cmpPlayer.GetCiv()),
- true));
-
- let techList = [];
- // Stores the tech which supersedes the key.
- let superseded = {};
-
- let 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 (let tech of techs)
- {
- if (disabledTechnologies && disabledTechnologies[tech])
- continue;
-
- let 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 (let i in techList)
- {
- let tech = techList[i];
- while (this.IsTechnologyResearchedOrInProgress(tech))
- tech = superseded[tech];
-
- techList[i] = tech;
- }
-
- let ret = [];
-
- // This inserts the techs into the correct positions to line up the technology pairs.
- for (let i = 0; i < techList.length; ++i)
- {
- let tech = techList[i];
- if (!tech)
- {
- ret[i] = undefined;
- continue;
- }
-
- let template = TechnologyTemplates.Get(tech);
- if (template.top)
- ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
- else
- ret[i] = tech;
- }
-
- return ret;
-};
-
-ProductionQueue.prototype.GetTechCostMultiplier = function()
-{
- let techCostMultiplier = {};
- for (let res in this.template.TechCostMultiplier)
- techCostMultiplier[res] = ApplyValueModificationsToEntity(
- "ProductionQueue/TechCostMultiplier/" + res,
- +this.template.TechCostMultiplier[res],
- this.entity);
-
- return techCostMultiplier;
-};
-
-ProductionQueue.prototype.IsTechnologyResearchedOrInProgress = function(tech)
-{
- if (!tech)
- return false;
-
- let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
- if (!cmpTechnologyManager)
- return false;
-
- let 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);
-};
-
-/*
* 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").
@@ -447,40 +324,19 @@
const time = this.GetBatchTime(count) * buildTime * 1000;
item.timeTotal = time;
item.timeRemaining = time;
+
+ // TrySubtractResources should report error to player (they ran out of resources).
+ if (!cmpPlayer.TrySubtractResources(item.resources))
+ return false;
}
else if (type == "technology")
{
- if (!TechnologyTemplates.Has(templateName))
+ const cmpResearcher = Engine.QueryInterface(this.entity, IID_Researcher);
+ if (!cmpResearcher)
return false;
-
- if (!this.GetTechnologiesList().some(tech =>
- tech &&
- (tech == templateName ||
- tech.pair &&
- (tech.top == templateName || tech.bottom == templateName))))
- {
- error("This entity cannot research " + templateName);
+ item.technology = cmpResearcher.QueueTechnology(templateName, metadata);
+ if (item.technology == -1)
return false;
- }
-
- item.technology = {
- "template": templateName,
- "resources": {}
- };
-
- let template = TechnologyTemplates.Get(templateName);
- let techCostMultiplier = this.GetTechCostMultiplier();
-
- if (template.cost)
- for (const res in template.cost)
- {
- item.technology.resources[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]);
- item.resources[res] = item.technology.resources[res];
- }
-
- const time = techCostMultiplier.time * (template.researchTime || 0) * 1000;
- item.timeTotal = time;
- item.timeRemaining = time;
}
else
{
@@ -488,10 +344,6 @@
return false;
}
- // TrySubtractResources should report error to player (they ran out of resources).
- if (!cmpPlayer.TrySubtractResources(item.resources))
- return false;
-
item.id = this.nextID++;
if (pushFront)
{
@@ -511,19 +363,6 @@
"metadata": metadata,
"trainerEntity": this.entity
});
- if (item.technology)
- {
- // Tell the technology manager that we have started researching this
- // such that players can't research the same thing twice.
- const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
- cmpTechnologyManager.QueuedResearch(templateName, this.entity);
-
- cmpTrigger.CallEvent("OnResearchQueued", {
- "playerid": player,
- "technologyTemplate": item.technology.template,
- "researcherEntity": this.entity
- });
- }
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null);
@@ -585,8 +424,6 @@
totalCosts[resource] = 0;
if (item.entity)
totalCosts[resource] += Math.floor(item.entity.count * item.entity.resources[resource]);
- if (item.technology)
- totalCosts[resource] += item.technology.resources[resource];
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceUsedCounter(resource, -totalCosts[resource]);
}
@@ -596,9 +433,9 @@
if (item.technology)
{
- let cmpTechnologyManager = QueryPlayerIDInterface(item.player, IID_TechnologyManager);
- if (cmpTechnologyManager)
- cmpTechnologyManager.StoppedResearch(item.technology.template, true);
+ let cmpResearcher = QueryInterface(this.entity, IID_Researcher);
+ if (cmpResearcher)
+ cmpResearcher.StopResearching(item.technology);
}
this.queue.splice(itemIndex, 1);
@@ -623,7 +460,7 @@
return this.queue.map(item => ({
"id": item.id,
"unitTemplate": item.entity?.template,
- "technologyTemplate": item.technology?.template,
+ "technology": item.technology,
"count": item.entity?.count,
"neededSlots": item.entity?.neededSlots,
"progress": 1 - (item.timeRemaining / (item.timeTotal || 1)),
@@ -834,18 +671,12 @@
Engine.PostMessage(this.entity, MT_TrainingStarted, { "entity": this.entity });
}
if (item.technology)
- {
- let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
- if (cmpTechnologyManager)
- cmpTechnologyManager.StartedResearch(item.technology.template, true);
- else
- warn("Failed to start researching " + item.technology.template + ": No TechnologyManager available.");
-
this.SetAnimation("researching");
- }
item.productionStarted = true;
}
+ if (item.technology)
+ time = Engine.QueryInterface(this.entity, IID_Researcher).ProgressTimeout(id, time);
if (item.timeRemaining > time)
{
@@ -887,22 +718,6 @@
return;
}
}
- if (item.technology)
- {
- let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
- if (cmpTechnologyManager)
- cmpTechnologyManager.ResearchTechnology(item.technology.template);
- else
- warn("Failed to finish researching " + item.technology.template + ": No TechnologyManager available.");
-
- const template = TechnologyTemplates.Get(item.technology.template);
- if (template && template.soundComplete)
- {
- let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
- if (cmpSoundManager)
- cmpSoundManager.PlaySoundGroup(template.soundComplete, this.entity);
- }
- }
time -= item.timeRemaining;
this.queue.shift();
Index: binaries/data/mods/public/simulation/components/Researcher.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/Researcher.js
@@ -0,0 +1,361 @@
+function Researcher() {}
+
+Researcher.prototype.Schema =
+ "Allows the entity to research technologies." +
+ "" +
+ "0.7" +
+ "" +
+ "\n units/{civ}/support_female_citizen\n units/{native}/support_trader\n units/athen/infantry_spearman_b\n " +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
+ "" +
+ Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
+ "" +
+ "";
+
+/**
+ * This object represents a technology being researched.
+ */
+Researcher.prototype.Item = function() {}
+
+Researcher.prototype.Item.prototype.Init = function(templateName, researcher, metadata)
+{
+ this.templateName = templateName;
+ this.researcher = researcher;
+ this.metadata = metadata;
+};
+
+Researcher.prototype.Item.prototype.Queue = function(techCostMultiplier)
+{
+ const template = TechnologyTemplates.Get(this.templateName);
+ if (!template)
+ return false;
+
+ this.resources = {};
+
+ if (template.cost)
+ for (const res in template.cost)
+ this.resources[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]);
+
+ const cmpPlayer = QueryOwnerInterface(this.researcher);
+
+ // TrySubtractResources should report error to player (they ran out of resources).
+ if (!cmpPlayer?.TrySubtractResources(this.resources))
+ return false;
+ this.player = cmpPlayer.GetPlayerID();
+
+ const time = (techCostMultiplier.time || 1) * (template.researchTime || 0) * 1000;
+ this.timeRemaining = time;
+ this.timeTotal = time;
+
+ // Tell the technology manager that we have started researching this
+ // such that players can't research the same thing twice.
+ const cmpTechnologyManager = QueryOwnerInterface(this.researcher, IID_TechnologyManager);
+ cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher);
+
+ return true;
+};
+
+Researcher.prototype.Item.prototype.Stop = function()
+{
+ let cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
+ if (cmpTechnologyManager)
+ cmpTechnologyManager.StoppedResearch(this.templateName, true);
+
+ QueryPlayerIDInterface(this.player)?.RefundResources(this.resources);
+ delete this.resources;
+};
+
+Researcher.prototype.Item.prototype.Start = function()
+{
+ const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
+ if (cmpTechnologyManager)
+ cmpTechnologyManager.StartedResearch(this.templateName, true);
+ else
+ warn("Failed to start researching " + this.templateName + ": No TechnologyManager available.");
+ this.started = true;
+};
+
+Researcher.prototype.Item.prototype.Finish = function()
+{
+ const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
+ if (cmpTechnologyManager)
+ cmpTechnologyManager.ResearchTechnology(this.templateName);
+ else
+ warn("Failed to finish researching " + this.templateName + ": No TechnologyManager available.");
+
+ const template = TechnologyTemplates.Get(this.templateName);
+ if (template?.soundComplete)
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher);
+};
+
+/**
+ * @param {number} allocatedTime - The time allocated to this item.
+ * @return {number} - The time not used for this item.
+ */
+Researcher.prototype.Item.prototype.ProgressTimeout = function(allocatedTime)
+{
+ if (!this.started)
+ this.Start();
+
+ if (this.timeRemaining > allocatedTime)
+ {
+ this.timeRemaining -= allocatedTime;
+ return 0;
+ }
+ this.Finish();
+ return allocatedTime - this.timeRemaining;
+};
+
+Researcher.prototype.Item.prototype.Pause = function()
+{
+ this.paused = true;
+};
+
+Researcher.prototype.Item.prototype.Unpause = function()
+{
+ delete this.paused;
+};
+
+Researcher.prototype.Item.prototype.Serialize = function(id)
+{
+ return {
+ "id": id,
+ "metadata": this.metadata,
+ "paused": this.paused,
+ "player": this.player,
+ "researcher": this.researcher,
+ "resource": this.resources,
+ "started": this.started,
+ "templateName": this.templateName,
+ "timeRemaining": this.timeRemaining,
+ "timeTotal": this.timeTotal,
+ };
+};
+
+Researcher.prototype.Item.prototype.Deserialize = function(data)
+{
+ this.Init(data.templateName, data.researcher, data.metadata);
+
+ this.paused = data.paused;
+ this.player = data.player;
+ this.researcher = data.researcher;
+ this.resources = data.resources;
+ this.started = data.started;
+ this.timeRemaining = data.timeRemaining;
+ this.timeTotal = data.timeTotal;
+};
+
+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()
+{
+ if (!this.template.Technologies)
+ return [];
+
+ let string = this.template.Technologies._string;
+ string = ApplyValueModificationsToEntity("Researcher/Technologies/_string", string, this.entity);
+
+ if (!string)
+ return [];
+
+ const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
+ if (!cmpTechnologyManager)
+ return [];
+
+ const cmpPlayer = QueryOwnerInterface(this.entity);
+ if (!cmpPlayer)
+ return [];
+
+ let techs = string.split(/\s+/);
+
+ // Replace the civ specific technologies.
+ for (let i = 0; i < techs.length; ++i)
+ {
+ const tech = techs[i];
+ if (tech.indexOf("{civ}") == -1)
+ continue;
+ const civTech = tech.replace("{civ}", cmpPlayer.GetCiv());
+ 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), cmpPlayer.GetCiv()),
+ true));
+
+ const techList = [];
+ // Stores the tech which supersedes the key.
+ 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;
+};
+
+Researcher.prototype.GetTechCostMultiplier = function()
+{
+ const techCostMultiplier = {};
+ for (const res in this.template.TechCostMultiplier)
+ techCostMultiplier[res] = ApplyValueModificationsToEntity(
+ "ProductionQueue/TechCostMultiplier/" + res,
+ +this.template.TechCostMultiplier[res],
+ 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 queued.
+ */
+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();
+ item.Init(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)
+{
+ const item = this.queue.get(id);
+ item.Stop();
+ this.queue.delete(id);
+};
+
+/**
+ * @param {number} id - The ID of the tech we spent time on.
+ * @param {number} allocatedTime - The time we spent on the given tech.
+ * @return {number} - The time we didn't use (because the tech costed less than the allocated time).
+ */
+Researcher.prototype.ProgressTimeout = function(id, allocatedTime)
+{
+ return this.queue.get(id)?.ProgressTimeout(allocatedTime);
+};
+
+Engine.RegisterComponentType(IID_Researcher, "Researcher", Researcher);
Index: binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/TechnologyManager.js
+++ binaries/data/mods/public/simulation/components/TechnologyManager.js
@@ -282,6 +282,17 @@
TechnologyManager.prototype.QueuedResearch = function(tech, researcher)
{
this.researchQueued.set(tech, researcher);
+
+ const cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
+ if (!cmpPlayer)
+ return;
+ const playerID = cmpPlayer.GetPlayerID();
+
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).CallEvent("OnResearchQueued", {
+ "playerid": playerID,
+ "technologyTemplate": tech,
+ "researcherEntity": researcher
+ });
};
// Marks a technology as actively being researched
Index: binaries/data/mods/public/simulation/components/interfaces/Researcher.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/Researcher.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("Researcher");
Index: binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js
+++ binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js
@@ -1,6 +1,5 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
-Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/BuildRestrictions.js");
Engine.LoadComponentScript("interfaces/EntityLimits.js");
@@ -41,10 +40,7 @@
let cmpProductionQueue = ConstructComponent(productionQueueId, "ProductionQueue", {
"Entities": { "_string": "units/{civ}/cavalry_javelineer_b " +
"units/{civ}/infantry_swordsman_b " +
- "units/{native}/support_female_citizen" },
- "Technologies": { "_string": "gather_fishing_net " +
- "phase_town_{civ} " +
- "phase_city_{civ}" }
+ "units/{native}/support_female_citizen" }
});
cmpProductionQueue.GetUpgradedTemplate = (template) => template;
@@ -54,17 +50,10 @@
AddMock(playerEntityID, IID_Player, {
"GetCiv": () => "iber",
- "GetDisabledTechnologies": () => ({}),
"GetDisabledTemplates": () => ({}),
"GetPlayerID": () => playerId
});
- AddMock(playerEntityID, IID_TechnologyManager, {
- "CheckTechnologyRequirements": () => true,
- "IsInProgress": () => false,
- "IsTechnologyResearched": () => false
- });
-
AddMock(productionQueueId, IID_Ownership, {
"GetOwner": () => playerId
});
@@ -82,10 +71,6 @@
cmpProductionQueue.GetEntitiesList(),
["units/iber/cavalry_javelineer_b", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
);
- TS_ASSERT_UNEVAL_EQUALS(
- cmpProductionQueue.GetTechnologiesList(),
- ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
- );
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
"TemplateExists": name => name == "units/iber/support_female_citizen",
@@ -132,32 +117,6 @@
"GetDisabledTemplates": () => ({ "units/athen/infantry_swordsman_b": true }),
"GetPlayerID": () => playerId
});
-
- cmpProductionQueue.CalculateEntitiesMap();
- TS_ASSERT_UNEVAL_EQUALS(
- cmpProductionQueue.GetEntitiesList(),
- ["units/athen/cavalry_javelineer_b", "units/iber/support_female_citizen"]
- );
- TS_ASSERT_UNEVAL_EQUALS(cmpProductionQueue.GetTechnologiesList(), ["phase_town_athen",
- "phase_city_athen"]
- );
-
- AddMock(playerEntityID, IID_TechnologyManager, {
- "CheckTechnologyRequirements": () => true,
- "IsInProgress": () => false,
- "IsTechnologyResearched": tech => tech == "phase_town_athen"
- });
- TS_ASSERT_UNEVAL_EQUALS(cmpProductionQueue.GetTechnologiesList(), [undefined, "phase_city_athen"]);
-
- AddMock(playerEntityID, IID_Player, {
- "GetCiv": () => "iber",
- "GetDisabledTechnologies": () => ({}),
- "GetPlayerID": () => playerId
- });
- TS_ASSERT_UNEVAL_EQUALS(
- cmpProductionQueue.GetTechnologiesList(),
- ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
- );
}
function regression_test_d1879()
@@ -481,9 +440,6 @@
let cmpProductionQueue = ConstructComponent(10, "ProductionQueue", {
"Entities": { "_string": "units/{civ}/a " +
"units/{civ}/b" },
- "Technologies": { "_string": "a " +
- "b_{civ} " +
- "c_{civ}" },
"BatchTimeModifier": 1
});
cmpProductionQueue.GetUpgradedTemplate = (template) => template;
@@ -493,7 +449,6 @@
// player
"GetCiv": () => "test",
"GetDisabledTemplates": () => [],
- "GetDisabledTechnologies": () => [],
"TryReservePopulationSlots": () => false, // Always have pop space.
"TrySubtractResources": () => true,
"UnBlockTraining": () => {},
@@ -501,11 +456,7 @@
"GetPlayerID": () => 1,
// entitylimits
"ChangeCount": () => {},
- "AllowedToTrain": () => true,
- // techmanager
- "CheckTechnologyRequirements": () => true,
- "IsTechnologyResearched": () => false,
- "IsInProgress": () => false
+ "AllowedToTrain": () => true
}));
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryOwnerInterface);
@@ -518,9 +469,6 @@
TS_ASSERT_UNEVAL_EQUALS(
cmpProductionQueue.GetEntitiesList(), ["units/test/a", "units/test/b"]
);
- TS_ASSERT_UNEVAL_EQUALS(
- cmpProductionQueue.GetTechnologiesList(), ["a", "b_generic", "c_generic"]
- );
// Add a unit of each type to our queue, validate.
cmpProductionQueue.AddItem("units/test/a", "unit", 1, {});
cmpProductionQueue.AddItem("units/test/b", "unit", 1, {});
Index: binaries/data/mods/public/simulation/components/tests/test_Researcher.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_Researcher.js
@@ -0,0 +1,153 @@
+Engine.RegisterGlobal("Resources", {
+ "BuildSchema": (a, b) => {}
+});
+Engine.LoadHelperScript("Player.js");
+Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/Researcher.js");
+Engine.LoadComponentScript("Researcher.js");
+
+Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value);
+
+const playerID = 1;
+const playerEntityID = 11;
+const entityID = 21;
+
+Engine.RegisterGlobal("TechnologyTemplates", {
+ "Has": name => name == "phase_town_athen" || name == "phase_city_athen",
+ "Get": () => ({})
+});
+
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": id => playerEntityID
+});
+
+AddMock(playerEntityID, IID_Player, {
+ "GetCiv": () => "iber",
+ "GetDisabledTechnologies": () => ({})
+});
+
+AddMock(playerEntityID, IID_TechnologyManager, {
+ "CheckTechnologyRequirements": () => true,
+ "IsInProgress": () => false,
+ "IsTechnologyResearched": () => false
+});
+
+AddMock(entityID, IID_Ownership, {
+ "GetOwner": () => playerID
+});
+
+AddMock(entityID, IID_Identity, {
+ "GetCiv": () => "iber"
+});
+
+const cmpResearcher = ConstructComponent(entityID, "Researcher", {
+ "Technologies": { "_string": "gather_fishing_net " +
+ "phase_town_{civ} " +
+ "phase_city_{civ}" }
+});
+
+TS_ASSERT_UNEVAL_EQUALS(
+ cmpResearcher.GetTechnologiesList(),
+ ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
+);
+
+AddMock(playerEntityID, IID_Player, {
+ "GetCiv": () => "athen",
+ "GetDisabledTechnologies": () => ({ "gather_fishing_net": true })
+});
+TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), ["phase_town_athen", "phase_city_athen"]);
+
+AddMock(playerEntityID, IID_TechnologyManager, {
+ "CheckTechnologyRequirements": () => true,
+ "IsInProgress": () => false,
+ "IsTechnologyResearched": tech => tech == "phase_town_athen"
+});
+TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), [undefined, "phase_city_athen"]);
+
+AddMock(playerEntityID, IID_Player, {
+ "GetCiv": () => "iber",
+ "GetDisabledTechnologies": () => ({})
+});
+TS_ASSERT_UNEVAL_EQUALS(
+ cmpResearcher.GetTechnologiesList(),
+ ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
+);
+
+Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value + " some_test");
+TS_ASSERT_UNEVAL_EQUALS(
+ cmpResearcher.GetTechnologiesList(),
+ ["gather_fishing_net", "phase_town_generic", "phase_city_generic", "some_test"]
+);
+
+
+// Test Queuing a tech.
+const queuedTech = "gather_fishing_net";
+const cost = {
+ "food": 10
+};
+Engine.RegisterGlobal("TechnologyTemplates", {
+ "Has": () => true,
+ "Get": () => ({
+ "cost": cost,
+ "researchTime": 1
+ })
+});
+
+const cmpPlayer = AddMock(playerEntityID, IID_Player, {
+ "GetCiv": () => "iber",
+ "GetDisabledTechnologies": () => ({}),
+ "GetPlayerID": () => playerID,
+ "TrySubtractResources": (resources) => {
+ TS_ASSERT_UNEVAL_EQUALS(resources, cost);
+ // Just have enough resources.
+ return true;
+ },
+ "RefundResources": (resources) => {
+ TS_ASSERT_UNEVAL_EQUALS(resources, cost);
+ },
+});
+let spyCmpPlayer = new Spy(cmpPlayer, "TrySubtractResources");
+const techManager = AddMock(playerEntityID, IID_TechnologyManager, {
+ "CheckTechnologyRequirements": () => true,
+ "IsInProgress": () => false,
+ "IsTechnologyResearched": () => false,
+ "QueuedResearch": (templateName, researcher) => {
+ TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
+ TS_ASSERT_UNEVAL_EQUALS(researcher, entityID);
+ },
+ "StoppedResearch": (templateName, _) => {
+ TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
+ },
+ "StartedResearch": (templateName, _) => {
+ TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
+ },
+ "ResearchTechnology": (templateName, _) => {
+ TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
+ }
+});
+let spyTechManager = new Spy(techManager, "QueuedResearch");
+let id = cmpResearcher.QueueTechnology(queuedTech);
+TS_ASSERT_EQUALS(spyTechManager._called, 1);
+TS_ASSERT_EQUALS(spyCmpPlayer._called, 1);
+TS_ASSERT_EQUALS(cmpResearcher.queue.size, 1);
+
+
+// Test removing a queued tech.
+spyCmpPlayer = new Spy(cmpPlayer, "RefundResources");
+spyTechManager = new Spy(techManager, "StoppedResearch");
+cmpResearcher.StopResearching(id);
+TS_ASSERT_EQUALS(spyTechManager._called, 1);
+TS_ASSERT_EQUALS(spyCmpPlayer._called, 1);
+TS_ASSERT_EQUALS(cmpResearcher.queue.size, 0);
+
+
+// Test finishing a queued tech.
+spyTechManager = new Spy(techManager, "StartedResearch");
+id = cmpResearcher.QueueTechnology(queuedTech);
+TS_ASSERT_EQUALS(cmpResearcher.ProgressTimeout(id, 500), 0);
+// Test it has started.
+TS_ASSERT_EQUALS(spyTechManager._called, 1);
+
+spyTechManager = new Spy(techManager, "ResearchTechnology");
+TS_ASSERT_EQUALS(cmpResearcher.ProgressTimeout(id, 1000), 500);
+TS_ASSERT_EQUALS(spyTechManager._called, 1);
Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
@@ -1,5 +1,6 @@
+
FemaleCitizen
120