Handle researching technologies in the TechnologyManager.

Moves the work done from cmpResearcher to cmpTechnologyManager.
No functional changes.

It allows fancy stuff in the future (#6364).

Differential revision: https://code.wildfiregames.com/D4438
This was SVN commit r26252.
This commit is contained in:
Freagarach 2022-01-26 07:42:36 +00:00
parent 27f64b80ed
commit 73f741d266
11 changed files with 529 additions and 290 deletions

View file

@ -212,11 +212,9 @@ m.GameState.prototype.isResearched = function(template)
return this.playerData.researchedTechs.has(template);
};
/** true if started or queued */
m.GameState.prototype.isResearching = function(template)
{
return this.playerData.researchStarted.has(template) ||
this.playerData.researchQueued.has(template);
return this.playerData.researchQueued.has(template);
};
/** this is an "in-absolute" check that doesn't check if we have a building to research from. */
@ -229,9 +227,7 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec
if (!template)
return false;
// researching or already researched: NOO.
if (this.playerData.researchQueued.has(techTemplateName) ||
this.playerData.researchStarted.has(techTemplateName) ||
this.playerData.researchedTechs.has(techTemplateName))
return false;
@ -243,7 +239,6 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec
{
let other = template.pairedWith();
if (this.playerData.researchQueued.has(other) ||
this.playerData.researchStarted.has(other) ||
this.playerData.researchedTechs.has(other))
return false;
}

View file

@ -122,7 +122,6 @@ GuiInterface.prototype.GetSimulationState = function()
"matchEntityCounts": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetMatchCounts() : null,
"entityLimitChangers": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetLimitChangers() : null,
"researchQueued": cmpTechnologyManager ? cmpTechnologyManager.GetQueuedResearch() : null,
"researchStarted": cmpTechnologyManager ? cmpTechnologyManager.GetStartedTechs() : null,
"researchedTechs": cmpTechnologyManager ? cmpTechnologyManager.GetResearchedTechs() : null,
"classCounts": cmpTechnologyManager ? cmpTechnologyManager.GetClassCounts() : null,
"typeCountsByClass": cmpTechnologyManager ? cmpTechnologyManager.GetTypeCountsByClass() : null,
@ -687,30 +686,7 @@ GuiInterface.prototype.CheckTechnologyRequirements = function(player, data)
*/
GuiInterface.prototype.GetStartedResearch = function(player)
{
let cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return {};
let ret = {};
for (let tech of cmpTechnologyManager.GetStartedTechs())
{
ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
const cmpResearcher = Engine.QueryInterface(ret[tech].researcher, IID_Researcher);
if (cmpResearcher)
{
const research = cmpResearcher.GetResearchingTechnologyByName(tech);
ret[tech].progress = research.progress;
ret[tech].timeRemaining = research.timeRemaining;
ret[tech].paused = research.paused;
}
else
{
ret[tech].progress = 0;
ret[tech].timeRemaining = 0;
ret[tech].paused = true;
}
}
return ret;
return QueryPlayerIDInterface(player, IID_TechnologyManager)?.GetBasicInfoOfStartedTechs() || {};
};
/**

View file

@ -120,6 +120,8 @@ ProductionQueue.prototype.Item.prototype.IsFinished = function()
*/
ProductionQueue.prototype.Item.prototype.Progress = function(allocatedTime)
{
if (this.paused)
this.Unpause();
if (this.entity)
{
const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer);
@ -152,10 +154,6 @@ ProductionQueue.prototype.Item.prototype.Pause = function()
ProductionQueue.prototype.Item.prototype.Unpause = function()
{
delete this.paused;
if (this.entity)
Engine.QueryInterface(this.producer, IID_Trainer).UnpauseBatch(this.entity);
if (this.technology)
Engine.QueryInterface(this.producer, IID_Researcher).UnpauseTechnology(this.technology);
};
/**
@ -413,8 +411,6 @@ ProductionQueue.prototype.ProgressTimeout = function(data, lateness)
while (this.queue.length)
{
let item = this.queue[0];
if (item.IsPaused())
item.Unpause();
if (!item.IsStarted())
{
if (item.entity)
@ -472,7 +468,6 @@ ProductionQueue.prototype.PauseProduction = function()
ProductionQueue.prototype.UnpauseProduction = function()
{
this.queue[0]?.Unpause();
delete this.paused;
this.StartTimer();
};

View file

@ -48,43 +48,18 @@ Researcher.prototype.Item = function(templateName, researcher, 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] * 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 * (template.researchTime || 0) * 1000;
this.timeRemaining = time;
this.timeTotal = time;
// Tell the technology manager that we have queued researching this
// such that players can't research the same thing twice.
this.player = QueryOwnerInterface(this.researcher).GetPlayerID();
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher);
if (!cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher, techCostMultiplier))
return false;
return true;
};
Researcher.prototype.Item.prototype.Stop = function()
{
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
if (cmpTechnologyManager)
cmpTechnologyManager.StoppedResearch(this.templateName, true);
QueryPlayerIDInterface(this.player)?.RefundResources(this.resources);
delete this.resources;
QueryPlayerIDInterface(this.player, IID_TechnologyManager).StoppedResearch(this.templateName);
delete this.started;
};
/**
@ -92,19 +67,11 @@ Researcher.prototype.Item.prototype.Stop = function()
*/
Researcher.prototype.Item.prototype.Start = function()
{
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
cmpTechnologyManager.StartedResearch(this.templateName, true);
this.started = true;
};
Researcher.prototype.Item.prototype.Finish = function()
{
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
cmpTechnologyManager.ResearchTechnology(this.templateName);
const template = TechnologyTemplates.Get(this.templateName);
if (template?.soundComplete)
Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher);
this.finished = true;
};
@ -116,18 +83,18 @@ Researcher.prototype.Item.prototype.Progress = function(allocatedTime)
{
if (!this.started)
this.Start();
if (this.timeRemaining > allocatedTime)
{
this.timeRemaining -= allocatedTime;
return allocatedTime;
}
this.Finish();
return this.timeRemaining;
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;
};
@ -141,13 +108,10 @@ Researcher.prototype.Item.prototype.Unpause = function()
*/
Researcher.prototype.Item.prototype.GetBasicInfo = function()
{
return {
"technologyTemplate": this.templateName,
"progress": 1 - (this.timeRemaining / (this.timeTotal || 1)),
"timeRemaining": this.timeRemaining,
"paused": this.paused,
"metadata": this.metadata
};
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 = [
@ -155,11 +119,8 @@ Researcher.prototype.Item.prototype.SerializableAttributes = [
"paused",
"player",
"researcher",
"resources",
"started",
"templateName",
"timeRemaining",
"timeTotal"
"templateName"
];
Researcher.prototype.Item.prototype.Serialize = function(id)
@ -226,6 +187,7 @@ Researcher.prototype.GetTechnologiesList = function()
const cmpPlayer = QueryOwnerInterface(this.entity);
if (!cmpPlayer)
return [];
const civ = cmpPlayer.GetCiv();
let techs = string.split(/\s+/);
@ -235,14 +197,14 @@ Researcher.prototype.GetTechnologiesList = function()
const tech = techs[i];
if (tech.indexOf("{civ}") == -1)
continue;
const civTech = tech.replace("{civ}", cmpPlayer.GetCiv());
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), cmpPlayer.GetCiv()),
DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), civ),
true));
const techList = [];
@ -376,14 +338,6 @@ Researcher.prototype.PauseTechnology = function(id)
this.queue.get(id).Pause();
};
/**
* @param {number} id - The id of the technology.
*/
Researcher.prototype.UnpauseTechnology = function(id)
{
this.queue.get(id).Unpause();
};
/**
* @param {number} id - The ID of the item to check.
* @return {boolean} - Whether we are currently training the item.
@ -402,25 +356,6 @@ Researcher.prototype.GetResearchingTechnology = function(id)
return this.queue.get(id).GetBasicInfo();
};
/**
* @parameter {string} technologyName - The name of the research.
* @return {Object} - Some basic information about the research.
*/
Researcher.prototype.GetResearchingTechnologyByName = function(technologyName)
{
let techID;
for (const [id, value] of this.queue)
if (value.templateName === technologyName)
{
techID = id;
break;
}
if (!techID)
return undefined;
return this.GetResearchingTechnology(techID);
};
/**
* @param {number} id - The ID of the item we spent time on.
* @param {number} allocatedTime - The time we spent on the given item.

View file

@ -3,17 +3,203 @@ function TechnologyManager() {}
TechnologyManager.prototype.Schema =
"<empty/>";
/**
* This object represents a technology under research.
* @param {string} templateName - The name of the template to research.
* @param {number} player - The player ID researching.
* @param {number} researcher - The entity ID researching.
*/
TechnologyManager.prototype.Technology = function(templateName, player, researcher)
{
this.player = player;
this.researcher = researcher;
this.templateName = templateName;
};
/**
* Prepare for the queue.
* @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
* @return {boolean} - Whether the technology was successfully initiated.
*/
TechnologyManager.prototype.Technology.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] * template.cost[res]);
// ToDo: Subtract resources here or in cmpResearcher?
const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
// TrySubtractResources should report error to player (they ran out of resources).
if (!cmpPlayer?.TrySubtractResources(this.resources))
return false;
const time = techCostMultiplier.time * (template.researchTime || 0) * 1000;
this.timeRemaining = time;
this.timeTotal = time;
const playerID = cmpPlayer.GetPlayerID();
Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).CallEvent("OnResearchQueued", {
"playerid": playerID,
"technologyTemplate": this.templateName,
"researcherEntity": this.researcher
});
return true;
};
TechnologyManager.prototype.Technology.prototype.Stop = function()
{
const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
cmpPlayer?.RefundResources(this.resources);
delete this.resources;
if (this.started && this.templateName.startsWith("phase"))
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
"type": "phase",
"players": [cmpPlayer.GetPlayerID()],
"phaseName": this.templateName,
"phaseState": "aborted"
});
};
/**
* Called when the first work is performed.
*/
TechnologyManager.prototype.Technology.prototype.Start = function()
{
this.started = true;
if (!this.templateName.startsWith("phase"))
return;
const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
"type": "phase",
"players": [cmpPlayer.GetPlayerID()],
"phaseName": this.templateName,
"phaseState": "started"
});
};
TechnologyManager.prototype.Technology.prototype.Finish = function()
{
this.finished = true;
const template = TechnologyTemplates.Get(this.templateName);
if (template.soundComplete)
Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher);
if (template.modifications)
{
const cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.AddModifiers("tech/" + this.templateName, DeriveModificationsFromTech(template), this.player);
}
const cmpEntityLimits = Engine.QueryInterface(this.player, IID_EntityLimits);
const cmpTechnologyManager = Engine.QueryInterface(this.player, IID_TechnologyManager);
if (template.replaces && template.replaces.length > 0)
for (const i of template.replaces)
{
cmpTechnologyManager.MarkTechnologyAsResearched(i);
cmpEntityLimits?.UpdateLimitsFromTech(i);
}
cmpTechnologyManager.MarkTechnologyAsResearched(this.templateName);
// ToDo: Move to EntityLimits.js.
cmpEntityLimits?.UpdateLimitsFromTech(this.templateName);
const playerID = Engine.QueryInterface(this.player, IID_Player).GetPlayerID();
Engine.PostMessage(this.player, MT_ResearchFinished, { "player": playerID, "tech": this.templateName });
if (this.templateName.startsWith("phase") && !template.autoResearch)
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
"type": "phase",
"players": [playerID],
"phaseName": this.templateName,
"phaseState": "completed"
});
};
/**
* @param {number} allocatedTime - The time allocated to this item.
* @return {number} - The time used for this item.
*/
TechnologyManager.prototype.Technology.prototype.Progress = function(allocatedTime)
{
if (!this.started)
this.Start();
if (this.paused)
this.Unpause();
if (this.timeRemaining > allocatedTime)
{
this.timeRemaining -= allocatedTime;
return allocatedTime;
}
this.Finish();
return this.timeRemaining;
};
TechnologyManager.prototype.Technology.prototype.Pause = function()
{
this.paused = true;
};
TechnologyManager.prototype.Technology.prototype.Unpause = function()
{
delete this.paused;
};
TechnologyManager.prototype.Technology.prototype.GetBasicInfo = function()
{
return {
"paused": this.paused,
"progress": 1 - (this.timeRemaining / (this.timeTotal || 1)),
"researcher": this.researcher,
"templateName": this.templateName,
"timeRemaining": this.timeRemaining
};
};
TechnologyManager.prototype.Technology.prototype.SerializableAttributes = [
"paused",
"player",
"researcher",
"resources",
"started",
"templateName",
"timeRemaining",
"timeTotal"
];
TechnologyManager.prototype.Technology.prototype.Serialize = function()
{
const result = {};
for (const att of this.SerializableAttributes)
if (this.hasOwnProperty(att))
result[att] = this[att];
return result;
};
TechnologyManager.prototype.Technology.prototype.Deserialize = function(data)
{
for (const att of this.SerializableAttributes)
if (att in data)
this[att] = data[att];
};
TechnologyManager.prototype.Init = function()
{
// Holds names of technologies that have been researched.
this.researchedTechs = new Set();
// Maps from technolgy name to the entityID of the researcher.
// Maps from technolgy name to the technology object.
this.researchQueued = new Map();
// Holds technologies which are being researched currently (non-queued).
this.researchStarted = new Set();
this.classCounts = {}; // stores the number of entities of each Class
this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
// {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
@ -27,12 +213,47 @@ TechnologyManager.prototype.Init = function()
this.unresearchedAutoResearchTechs.add(key);
};
TechnologyManager.prototype.SerializableAttributes = [
"researchedTechs",
"classCounts",
"typeCountsByClass",
"unresearchedAutoResearchTechs"
];
TechnologyManager.prototype.Serialize = function()
{
const result = {};
for (const att of this.SerializableAttributes)
if (this.hasOwnProperty(att))
result[att] = this[att];
result.researchQueued = [];
for (const [techName, techObject] of this.researchQueued)
result.researchQueued.push(techObject.Serialize());
return result;
};
TechnologyManager.prototype.Deserialize = function(data)
{
for (const att of this.SerializableAttributes)
if (att in data)
this[att] = data[att];
this.researchQueued = new Map();
for (const tech of data.researchQueued)
{
const newTech = new this.Technology();
newTech.Deserialize(tech);
this.researchQueued.set(tech.templateName, newTech);
}
};
TechnologyManager.prototype.OnUpdate = function()
{
this.UpdateAutoResearch();
};
// This function checks if the requirements of any autoresearch techs are met and if they are it researches them
TechnologyManager.prototype.UpdateAutoResearch = function()
{
@ -71,11 +292,6 @@ TechnologyManager.prototype.IsTechnologyResearched = function(tech)
return this.researchedTechs.has(tech);
};
TechnologyManager.prototype.IsTechnologyStarted = function(tech)
{
return this.researchStarted.has(tech);
};
// Checks the requirements for a technology to see if it can be researched at the current time
TechnologyManager.prototype.CanResearch = function(tech)
{
@ -208,130 +424,86 @@ TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
};
/**
* Marks a technology as researched.
* Note that this does not verify that the requirements are met.
*
* @param {string} tech - The technology to mark as researched.
* This does neither apply effects nor verify requirements.
* @param {string} tech - The name of the technology to mark as researched.
*/
TechnologyManager.prototype.ResearchTechnology = function(tech)
TechnologyManager.prototype.MarkTechnologyAsResearched = function(tech)
{
this.StoppedResearch(tech, false);
let modifiedComponents = {};
this.researchedTechs.add(tech);
// Store the modifications in an easy to access structure.
let template = TechnologyTemplates.Get(tech);
if (template.modifications)
{
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.AddModifiers("tech/" + tech, DeriveModificationsFromTech(template), this.entity);
}
if (template.replaces && template.replaces.length > 0)
{
for (let i of template.replaces)
{
if (!i || this.IsTechnologyResearched(i))
continue;
this.researchedTechs.add(i);
// Change the EntityLimit if any.
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (cmpPlayer && cmpPlayer.GetPlayerID() !== undefined)
{
let playerID = cmpPlayer.GetPlayerID();
let cmpPlayerEntityLimits = QueryPlayerIDInterface(playerID, IID_EntityLimits);
if (cmpPlayerEntityLimits)
cmpPlayerEntityLimits.UpdateLimitsFromTech(i);
}
}
}
this.UpdateAutoResearch();
};
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (!cmpPlayer || cmpPlayer.GetPlayerID() === undefined)
/**
* Note that this does not verify whether the requirements are met.
* @param {string} tech - The technology to research.
* @param {number} researcher - Optionally the entity to couple with the research.
*/
TechnologyManager.prototype.ResearchTechnology = function(tech, researcher = INVALID_ENTITY)
{
if (this.IsTechnologyQueued(tech) || this.IsTechnologyResearched(tech))
return;
let playerID = cmpPlayer.GetPlayerID();
// Change the EntityLimit if any.
let cmpPlayerEntityLimits = QueryPlayerIDInterface(playerID, IID_EntityLimits);
if (cmpPlayerEntityLimits)
cmpPlayerEntityLimits.UpdateLimitsFromTech(tech);
// Always send research finished message.
Engine.PostMessage(this.entity, MT_ResearchFinished, { "player": playerID, "tech": tech });
if (tech.startsWith("phase") && !template.autoResearch)
{
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "phase",
"players": [playerID],
"phaseName": tech,
"phaseState": "completed"
});
}
const technology = new this.Technology(tech, this.entity, researcher);
technology.Finish();
};
/**
* Marks a technology as being queued for research at the given entityID.
* @param {string} tech - The technology to queue.
* @param {number} researcher - The entity ID of the entity researching this technology.
* @param {Object} techCostMultiplier - The multipliers used when calculating the costs.
*
* @return {boolean} - Whether we successfully have queued the technology.
*/
TechnologyManager.prototype.QueuedResearch = function(tech, researcher)
TechnologyManager.prototype.QueuedResearch = function(tech, researcher, techCostMultiplier)
{
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
TechnologyManager.prototype.StartedResearch = function(tech, notification)
{
this.researchStarted.add(tech);
if (notification && tech.startsWith("phase"))
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
"type": "phase",
"players": [cmpPlayer.GetPlayerID()],
"phaseName": tech,
"phaseState": "started"
});
}
// ToDo: Check whether the technology is researched already?
const technology = new this.Technology(tech, this.entity, researcher);
if (!technology.Queue(techCostMultiplier))
return false;
this.researchQueued.set(tech, technology);
return true;
};
/**
* Marks a technology as not being currently researched and optionally sends a GUI notification.
* Marks a technology as not being currently researched and optionally sends a GUI notification.
* @param {string} tech - The name of the technology to stop.
* @param {boolean} notification - Whether a GUI notification ought to be sent.
*/
TechnologyManager.prototype.StoppedResearch = function(tech, notification)
TechnologyManager.prototype.StoppedResearch = function(tech)
{
if (notification && tech.startsWith("phase") && this.researchStarted.has(tech))
{
let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "phase",
"players": [cmpPlayer.GetPlayerID()],
"phaseName": tech,
"phaseState": "aborted"
});
}
this.researchQueued.get(tech).Stop();
this.researchQueued.delete(tech);
this.researchStarted.delete(tech);
};
/**
* @param {string} tech -
*/
TechnologyManager.prototype.Pause = function(tech)
{
this.researchQueued.get(tech).Pause();
};
/**
* @param {string} tech - The technology to advance.
* @param {number} allocatedTime - The time allocated to the technology.
* @return {number} - The time we've actually used.
*/
TechnologyManager.prototype.Progress = function(techName, allocatedTime)
{
const technology = this.researchQueued.get(techName);
const usedTime = technology.Progress(allocatedTime);
if (technology.finished)
this.researchQueued.delete(techName);
return usedTime;
};
/**
* @param {string} tech - The technology name to retreive some basic information for.
* @return {Object} - Some basic information about the technology under research.
*/
TechnologyManager.prototype.GetBasicInfo = function(tech)
{
return this.researchQueued.get(tech).GetBasicInfo();
};
/**
@ -342,20 +514,13 @@ TechnologyManager.prototype.IsInProgress = function(tech)
return this.researchQueued.has(tech);
};
/**
* Returns the names of technologies that are currently being researched (non-queued).
*/
TechnologyManager.prototype.GetStartedTechs = function()
TechnologyManager.prototype.GetBasicInfoOfStartedTechs = function()
{
return this.researchStarted;
};
/**
* Gets the entity currently researching the technology.
*/
TechnologyManager.prototype.GetResearcher = function(tech)
{
return this.researchQueued.get(tech);
const result = {};
for (const [techName, tech] of this.researchQueued)
if (tech.started)
result[techName] = tech.GetBasicInfo();
return result;
};
/**

View file

@ -333,6 +333,8 @@ Trainer.prototype.Item.prototype.Spawn = function()
*/
Trainer.prototype.Item.prototype.Progress = function(allocatedTime)
{
if (this.paused)
this.Unpause();
// We couldn't start this timeout, try again later.
if (!this.started && !this.Start())
return allocatedTime;
@ -650,14 +652,6 @@ Trainer.prototype.PauseBatch = function(id)
this.queue.get(id).Pause();
};
/**
* @param {number} id - The ID of the training.
*/
Trainer.prototype.UnpauseBatch = function(id)
{
this.queue.get(id).Unpause();
};
/**
* @param {number} id - The ID of the batch to check.
* @return {boolean} - Whether we are currently training the batch.

View file

@ -311,7 +311,6 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
"matchEntityCounts": { "Bar": 0 },
"entityLimitChangers": { "Foo": {} },
"researchQueued": new Map(),
"researchStarted": new Set(),
"researchedTechs": new Set(),
"classCounts": {},
"typeCountsByClass": {},
@ -362,7 +361,6 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
"matchEntityCounts": { "Foo": 0 },
"entityLimitChangers": { "Bar": {} },
"researchQueued": new Map(),
"researchStarted": new Set(),
"researchedTechs": new Set(),
"classCounts": {},
"typeCountsByClass": {},
@ -423,7 +421,6 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
"matchEntityCounts": { "Bar": 0 },
"entityLimitChangers": { "Foo": {} },
"researchQueued": new Map(),
"researchStarted": new Set(),
"researchedTechs": new Set(),
"classCounts": {},
"typeCountsByClass": {},
@ -497,7 +494,6 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
"matchEntityCounts": { "Foo": 0 },
"entityLimitChangers": { "Bar": {} },
"researchQueued": new Map(),
"researchStarted": new Set(),
"researchedTechs": new Set(),
"classCounts": {},
"typeCountsByClass": {},

View file

@ -24,7 +24,7 @@ AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
AddMock(playerEntityID, IID_Player, {
"GetCiv": () => "iber",
"GetDisabledTechnologies": () => ({})
"GetDisabledTechnologies": () => ({}) // ToDo: Should be in the techmanager.
});
AddMock(playerEntityID, IID_TechnologyManager, {
@ -98,23 +98,15 @@ 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) => {
"QueuedResearch": (templateName, researcher, techCostMultiplier) => {
TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
TS_ASSERT_UNEVAL_EQUALS(researcher, entityID);
return true;
},
"StoppedResearch": (templateName, _) => {
TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech);
@ -129,30 +121,26 @@ const techManager = AddMock(playerEntityID, IID_TechnologyManager, {
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.
id = cmpResearcher.QueueTechnology(queuedTech);
TS_ASSERT_EQUALS(cmpResearcher.GetResearchingTechnology(id).progress, 0);
techManager.Progress = () => 500;
techManager.IsTechnologyQueued = () => true;
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 500), 500);
TS_ASSERT_EQUALS(cmpResearcher.GetResearchingTechnology(id).progress, 0.5);
cmpResearcher = SerializationCycle(cmpResearcher);
spyTechManager = new Spy(techManager, "ResearchTechnology");
techManager.IsTechnologyQueued = () => false;
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 1000), 500);
TS_ASSERT_EQUALS(spyTechManager._called, 1);
TS_ASSERT_EQUALS(cmpResearcher.queue.size, 0);

View file

@ -0,0 +1,119 @@
Resources = {
"GetCodes": () => ["food", "metal", "stone", "wood"],
"GetTradableCodes": () => ["food", "metal", "stone", "wood"],
"GetBarterableCodes": () => ["food", "metal", "stone", "wood"],
"BuildSchema": () => {
let schema = "";
for (let res of ["food", "metal"])
{
for (let subtype in ["meat", "grain"])
schema += "<value>" + res + "." + subtype + "</value>";
schema += "<value> treasure." + res + "</value>";
}
return "<choice>" + schema + "</choice>";
},
"BuildChoicesSchema": () => {
let schema = "";
for (let res of ["food", "metal"])
{
for (let subtype in ["meat", "grain"])
schema += "<value>" + res + "." + subtype + "</value>";
schema += "<value> treasure." + res + "</value>";
}
return "<choice>" + schema + "</choice>";
},
"GetResource": (type) => {
return {
"subtypes": {
"meat": "meat",
"grain": "grain"
}
};
}
};
Engine.LoadComponentScript("interfaces/EntityLimits.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Researcher.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("Player.js");
Engine.LoadComponentScript("Researcher.js");
Engine.LoadComponentScript("TechnologyManager.js");
Engine.LoadComponentScript("Timer.js");
Engine.LoadComponentScript("Trigger.js");
Engine.LoadHelperScript("Player.js");
ConstructComponent(SYSTEM_ENTITY, "Trigger");
const cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", null);
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => typeof value === "string" ? value + " some_test": value);
const template = {
"name": "templateName"
};
Engine.RegisterGlobal("TechnologyTemplates", {
"GetAll": () => [],
"Get": (tech) => {
return template;
}
});
const playerID = 1;
const playerEntityID = 11;
const researcherID = 21;
let cmpTechnologyManager = ConstructComponent(playerEntityID, "TechnologyManager", null);
AddMock(researcherID, IID_Ownership, {
"GetOwner": () => playerID
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => playerEntityID
});
template.cost = {
"food": 100
};
template.researchTime = 1.5;
const cmpPlayer = ConstructComponent(playerEntityID, "Player", {
"SpyCostMultiplier": "1",
"BarterMultiplier": {
"Buy": {},
"Sell": {}
}
});
const spyPlayerResSub = new Spy(cmpPlayer, "TrySubtractResources");
const spyPlayerResRefund = new Spy(cmpPlayer, "RefundResources");
let cmpResearcher = ConstructComponent(researcherID, "Researcher", {
"Technologies": {
"_string": template.name
}
});
let id = cmpResearcher.QueueTechnology(template.name);
TS_ASSERT_EQUALS(spyPlayerResSub._called, 1);
TS_ASSERT(!cmpTechnologyManager.IsTechnologyResearched(template.name));
TS_ASSERT(cmpTechnologyManager.IsInProgress(template.name));
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 1000), 1000);
cmpResearcher = SerializationCycle(cmpResearcher);
cmpTechnologyManager = SerializationCycle(cmpTechnologyManager);
cmpResearcher.StopResearching(id);
TS_ASSERT(!cmpTechnologyManager.IsInProgress(template.name));
TS_ASSERT_EQUALS(spyPlayerResRefund._called, 1);
id = cmpResearcher.QueueTechnology(template.name);
TS_ASSERT_EQUALS(spyPlayerResSub._called, 2);
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 1000), 1000);
cmpResearcher = SerializationCycle(cmpResearcher);
cmpTechnologyManager = SerializationCycle(cmpTechnologyManager);
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 1000), 500);
TS_ASSERT(cmpTechnologyManager.IsTechnologyResearched(template.name));
TS_ASSERT_EQUALS(spyPlayerResRefund._called, 1);

View file

@ -1,14 +1,28 @@
Engine.LoadComponentScript("interfaces/EntityLimits.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("TechnologyManager.js");
Engine.LoadComponentScript("Trigger.js");
global.TechnologyTemplates = {
"GetAll": () => []
};
Engine.LoadHelperScript("Player.js");
let cmpTechnologyManager = ConstructComponent(SYSTEM_ENTITY, "TechnologyManager", {});
ConstructComponent(SYSTEM_ENTITY, "Trigger");
const techTemplate = {};
Engine.RegisterGlobal("TechnologyTemplates", {
"GetAll": () => [],
"Get": (tech) => {
return techTemplate;
}
});
const playerID = 1;
const playerEntityID = 11;
let cmpTechnologyManager = ConstructComponent(playerEntityID, "TechnologyManager", null);
// Test CheckTechnologyRequirements
let template = { "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] } };
const template = { "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] } };
cmpTechnologyManager.classCounts.Village = 2;
TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen")), false);
TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen"), true), true);
@ -16,3 +30,66 @@ TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnolo
cmpTechnologyManager.classCounts.Village = 6;
TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen")), true);
TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "maur")), false);
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => playerEntityID
});
const templateName = "template";
techTemplate.cost = {
"food": 100
};
const cmpPlayer = AddMock(playerEntityID, IID_Player, {
"GetPlayerID": () => playerID,
"TrySubtractResources": (resources) => {
TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.cost);
// Just have enough resources.
return true;
},
"RefundResources": (resources) => {
TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.cost);
},
});
const spyCmpPlayerSubtract = new Spy(cmpPlayer, "TrySubtractResources");
const spyCmpPlayerRefund = new Spy(cmpPlayer, "RefundResources");
TS_ASSERT(cmpTechnologyManager.QueuedResearch(templateName, INVALID_ENTITY, { "food": 1 }));
TS_ASSERT(cmpTechnologyManager.IsInProgress(templateName));
TS_ASSERT_EQUALS(spyCmpPlayerSubtract._called, 1);
// Test refunding before start.
cmpTechnologyManager.StoppedResearch(templateName, true);
TS_ASSERT(!cmpTechnologyManager.IsInProgress(templateName));
TS_ASSERT_EQUALS(spyCmpPlayerRefund._called, 1);
techTemplate.researchTime = 2;
TS_ASSERT(cmpTechnologyManager.QueuedResearch(templateName, INVALID_ENTITY, { "food": 1, "time": 1 }));
TS_ASSERT_EQUALS(cmpTechnologyManager.Progress(templateName, 500), 500);
cmpTechnologyManager = SerializationCycle(cmpTechnologyManager);
cmpTechnologyManager.Pause(templateName);
TS_ASSERT_UNEVAL_EQUALS(cmpTechnologyManager.GetBasicInfoOfStartedTechs(), {
[templateName]: {
"paused": true,
"progress": 0.25,
"researcher": INVALID_ENTITY,
"templateName": templateName,
"timeRemaining": 1500
},
});
TS_ASSERT(!cmpTechnologyManager.IsTechnologyResearched(templateName));
TS_ASSERT_EQUALS(cmpTechnologyManager.Progress(templateName, 2000), 1500);
TS_ASSERT(cmpTechnologyManager.IsTechnologyResearched(templateName));
TS_ASSERT(cmpTechnologyManager.QueuedResearch(templateName, INVALID_ENTITY, { "food": 1, "time": 1 }));
TS_ASSERT_EQUALS(cmpTechnologyManager.Progress(templateName, 500), 500);
TS_ASSERT(cmpTechnologyManager.IsInProgress(templateName));
cmpTechnologyManager = SerializationCycle(cmpTechnologyManager);
// Test refunding after start.
cmpTechnologyManager.StoppedResearch(templateName, true);
TS_ASSERT(!cmpTechnologyManager.IsInProgress(templateName));
TS_ASSERT_EQUALS(spyCmpPlayerRefund._called, 2);

View file

@ -164,8 +164,7 @@ function Cheat(input)
}
}
if (TechnologyTemplates.Has(techname) &&
!cmpTechnologyManager.IsTechnologyResearched(techname))
if (TechnologyTemplates.Has(techname))
cmpTechnologyManager.ResearchTechnology(techname);
return;
}