mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 22:03:56 -07:00
Split tasks from ProductionQueue.
The task of the production queue should first and foremost be that; a queue for production items. Hence, the specifics of training/researching are delegated to specific components. As a side effect, this improves the test coverage and fixes: - Resource not refunding when hitting the entity limit. Introduced inb8758c8941. - Autoqueue changing when unable to spawn. Introduced in956b3f96db. Modders can change their templates using https://code.wildfiregames.com/P256. Differential revision: https://code.wildfiregames.com/D4333 Fixes: #6363 Comments by: @Silier Refs. #6364 This was SVN commit r26000.
This commit is contained in:
parent
b5d85e279f
commit
0c4f59d0a7
169 changed files with 2755 additions and 2056 deletions
|
|
@ -471,11 +471,11 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
|
|||
}
|
||||
}
|
||||
|
||||
if (template.ProductionQueue)
|
||||
if (template.Researcher)
|
||||
{
|
||||
ret.techCostMultiplier = {};
|
||||
for (let res in template.ProductionQueue.TechCostMultiplier)
|
||||
ret.techCostMultiplier[res] = getEntityValue("ProductionQueue/TechCostMultiplier/" + res);
|
||||
for (const res in template.Researcher.TechCostMultiplier)
|
||||
ret.techCostMultiplier[res] = getEntityValue("Researcher/TechCostMultiplier/" + res);
|
||||
}
|
||||
|
||||
if (template.Trader)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class TemplateLister
|
|||
|
||||
let template = this.TemplateLoader.loadEntityTemplate(templateName, civCode);
|
||||
|
||||
let templateLists = this.TemplateLoader.deriveProductionQueue(template, civCode);
|
||||
const templateLists = this.TemplateLoader.deriveProduction(template, civCode);
|
||||
templateLists.structures = this.TemplateLoader.deriveBuildQueue(template, civCode);
|
||||
|
||||
if (template.WallSet)
|
||||
|
|
|
|||
|
|
@ -137,36 +137,36 @@ class TemplateLoader
|
|||
};
|
||||
}
|
||||
|
||||
deriveProductionQueue(template, civCode)
|
||||
deriveProduction(template, civCode)
|
||||
{
|
||||
let production = {
|
||||
const production = {
|
||||
"techs": [],
|
||||
"units": []
|
||||
};
|
||||
|
||||
if (!template.ProductionQueue)
|
||||
if (!template.Researcher && !template.Trainer)
|
||||
return production;
|
||||
|
||||
if (template.ProductionQueue.Entities && template.ProductionQueue.Entities._string)
|
||||
for (let templateName of template.ProductionQueue.Entities._string.split(" "))
|
||||
if (template.Trainer?.Entities?._string)
|
||||
for (let templateName of template.Trainer.Entities._string.split(" "))
|
||||
{
|
||||
templateName = templateName.replace(/\{(civ|native)\}/g, civCode);
|
||||
if (Engine.TemplateExists(templateName))
|
||||
production.units.push(templateName);
|
||||
}
|
||||
|
||||
let appendTechnology = (technologyName) => {
|
||||
let technology = this.loadTechnologyTemplate(technologyName, civCode);
|
||||
const appendTechnology = (technologyName) => {
|
||||
const technology = this.loadTechnologyTemplate(technologyName, civCode);
|
||||
if (DeriveTechnologyRequirements(technology, civCode))
|
||||
production.techs.push(technologyName);
|
||||
};
|
||||
|
||||
if (template.ProductionQueue.Technologies && template.ProductionQueue.Technologies._string)
|
||||
for (let technologyName of template.ProductionQueue.Technologies._string.split(" "))
|
||||
if (template.Researcher?.Technologies?._string)
|
||||
for (let technologyName of template.Researcher.Technologies._string.split(" "))
|
||||
{
|
||||
if (technologyName.indexOf("{civ}") != -1)
|
||||
{
|
||||
let civTechName = technologyName.replace("{civ}", civCode);
|
||||
const civTechName = technologyName.replace("{civ}", civCode);
|
||||
technologyName = TechnologyTemplateExists(civTechName) ? civTechName : technologyName.replace("{civ}", "generic");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class TemplateParser
|
|||
|
||||
parsed.history = template.Identity.History;
|
||||
|
||||
parsed.production = this.TemplateLoader.deriveProductionQueue(template, civCode);
|
||||
parsed.production = this.TemplateLoader.deriveProduction(template, civCode);
|
||||
if (template.Builder)
|
||||
parsed.builder = this.TemplateLoader.deriveBuildQueue(template, civCode);
|
||||
|
||||
|
|
|
|||
|
|
@ -1402,9 +1402,9 @@ function OnTrainMouseWheel(dir)
|
|||
function getBuildingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
|
||||
{
|
||||
return entitiesToCheck.filter(entity => {
|
||||
let state = GetEntityState(entity);
|
||||
return state && state.production && state.production.entities.length &&
|
||||
state.production.entities.indexOf(trainEntType) != -1 && (!state.upgrade || !state.upgrade.isUpgrading);
|
||||
const state = GetEntityState(entity);
|
||||
return state?.trainer?.entities?.indexOf(trainEntType) != -1 &&
|
||||
(!state.upgrade || !state.upgrade.isUpgrading);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -627,10 +627,10 @@ g_SelectionPanels.Research = {
|
|||
{
|
||||
let ret = [];
|
||||
if (unitEntStates.length == 1)
|
||||
return !unitEntStates[0].production || !unitEntStates[0].production.technologies ? ret :
|
||||
unitEntStates[0].production.technologies.map(tech => ({
|
||||
return !unitEntStates[0].researcher || !unitEntStates[0].researcher.technologies ? ret :
|
||||
unitEntStates[0].researcher.technologies.map(tech => ({
|
||||
"tech": tech,
|
||||
"techCostMultiplier": unitEntStates[0].production.techCostMultiplier,
|
||||
"techCostMultiplier": unitEntStates[0].researcher.techCostMultiplier,
|
||||
"researchFacilityId": unitEntStates[0].id,
|
||||
"isUpgrading": !!unitEntStates[0].upgrade && unitEntStates[0].upgrade.isUpgrading
|
||||
}));
|
||||
|
|
@ -642,11 +642,11 @@ g_SelectionPanels.Research = {
|
|||
|
||||
for (let state of sortedEntStates)
|
||||
{
|
||||
if (!state.production || !state.production.technologies)
|
||||
if (!state.researcher || !state.researcher.technologies)
|
||||
continue;
|
||||
|
||||
// Remove the techs we already have in ret (with the same name and techCostMultiplier)
|
||||
let filteredTechs = state.production.technologies.filter(
|
||||
const filteredTechs = state.researcher.technologies.filter(
|
||||
tech => tech != null && !ret.some(
|
||||
item =>
|
||||
(item.tech == tech ||
|
||||
|
|
@ -655,14 +655,14 @@ g_SelectionPanels.Research = {
|
|||
item.tech.bottom == tech.bottom &&
|
||||
item.tech.top == tech.top) &&
|
||||
Object.keys(item.techCostMultiplier).every(
|
||||
k => item.techCostMultiplier[k] == state.production.techCostMultiplier[k])
|
||||
k => item.techCostMultiplier[k] == state.researcher.techCostMultiplier[k])
|
||||
));
|
||||
|
||||
if (filteredTechs.length + ret.length <= this.getMaxNumberOfItems() &&
|
||||
getNumberOfRightPanelButtons() <= this.getMaxNumberOfItems() * (filteredTechs.some(tech => !!tech.pair) ? 1 : 2))
|
||||
ret = ret.concat(filteredTechs.map(tech => ({
|
||||
"tech": tech,
|
||||
"techCostMultiplier": state.production.techCostMultiplier,
|
||||
"techCostMultiplier": state.researcher.techCostMultiplier,
|
||||
"researchFacilityId": state.id,
|
||||
"isUpgrading": !!state.upgrade && state.upgrade.isUpgrading
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ function turnAutoQueueOn()
|
|||
"type": "autoqueue-on",
|
||||
"entities": g_Selection.filter(ent => {
|
||||
let state = GetEntityState(ent);
|
||||
return !!state?.production?.entities.length &&
|
||||
return !!state?.trainer?.entities?.length &&
|
||||
!state.production.autoqueue;
|
||||
})
|
||||
});
|
||||
|
|
@ -570,7 +570,7 @@ function turnAutoQueueOff()
|
|||
"type": "autoqueue-off",
|
||||
"entities": g_Selection.filter(ent => {
|
||||
let state = GetEntityState(ent);
|
||||
return !!state?.production?.entities.length &&
|
||||
return !!state?.trainer?.entities?.length &&
|
||||
state.production.autoqueue;
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1058,8 +1058,8 @@ var g_UnitActions =
|
|||
// Don't allow the rally point to be set on any of the currently selected entities (used for unset)
|
||||
// except if the autorallypoint hotkey is pressed and the target can produce entities.
|
||||
if (targetState && (!Engine.HotkeyIsPressed("session.autorallypoint") ||
|
||||
!targetState.production ||
|
||||
!targetState.production.entities.length))
|
||||
!targetState.trainer ||
|
||||
!targetState.trainer.entities.length))
|
||||
for (const ent of g_Selection.toList())
|
||||
if (targetState.id == ent)
|
||||
return false;
|
||||
|
|
@ -1154,9 +1154,9 @@ var g_UnitActions =
|
|||
{
|
||||
// Find a trader (if any) that this structure can train.
|
||||
let trader;
|
||||
if (entState.production && entState.production.entities.length)
|
||||
for (let i = 0; i < entState.production.entities.length; ++i)
|
||||
if ((trader = GetTemplateData(entState.production.entities[i]).trader))
|
||||
if (entState.trainer?.entities?.length)
|
||||
for (let i = 0; i < entState.trainer.entities.length; ++i)
|
||||
if ((trader = GetTemplateData(entState.trainer.entities[i]).trader))
|
||||
break;
|
||||
|
||||
let traderData = {
|
||||
|
|
@ -1793,7 +1793,7 @@ var g_EntityCommands =
|
|||
"autoqueue-on": {
|
||||
"getInfo": function(entStates)
|
||||
{
|
||||
if (entStates.every(entState => !entState.production || !entState.production.entities.length || entState.production.autoqueue))
|
||||
if (entStates.every(entState => !entState.trainer || !entState.trainer.entities.length || entState.production.autoqueue))
|
||||
return false;
|
||||
return {
|
||||
"tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.queueunit.autoqueueon") +
|
||||
|
|
@ -1813,7 +1813,7 @@ var g_EntityCommands =
|
|||
"autoqueue-off": {
|
||||
"getInfo": function(entStates)
|
||||
{
|
||||
if (entStates.every(entState => !entState.production || !entState.production.entities.length || !entState.production.autoqueue))
|
||||
if (entStates.every(entState => !entState.trainer || !entState.trainer.entities.length || !entState.production.autoqueue))
|
||||
return false;
|
||||
return {
|
||||
"tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.queueunit.autoqueueoff") +
|
||||
|
|
|
|||
|
|
@ -191,8 +191,8 @@ function getAllTrainableEntities(selection)
|
|||
for (let ent of selection)
|
||||
{
|
||||
let state = GetEntityState(ent);
|
||||
if (state && state.production && state.production.entities.length)
|
||||
trainableEnts = trainableEnts.concat(state.production.entities);
|
||||
if (state?.trainer?.entities?.length)
|
||||
trainableEnts = trainableEnts.concat(state.trainer.entities);
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ m.Template = m.Class({
|
|||
},
|
||||
|
||||
"techCostMultiplier": function(type) {
|
||||
return +(this.get("ProductionQueue/TechCostMultiplier/"+type) || 1);
|
||||
return +(this.get("Researcher/TechCostMultiplier/"+type) || 1);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -340,14 +340,14 @@ m.Template = m.Class({
|
|||
},
|
||||
|
||||
"trainableEntities": function(civ) {
|
||||
let templates = this.get("ProductionQueue/Entities/_string");
|
||||
const templates = this.get("Trainer/Entities/_string");
|
||||
if (!templates)
|
||||
return undefined;
|
||||
return templates.replace(/\{native\}/g, this.civ()).replace(/\{civ\}/g, civ).split(/\s+/);
|
||||
},
|
||||
|
||||
"researchableTechs": function(gameState, civ) {
|
||||
let templates = this.get("ProductionQueue/Technologies/_string");
|
||||
const templates = this.get("Researcher/Technologies/_string");
|
||||
if (!templates)
|
||||
return undefined;
|
||||
let techs = templates.split(/\s+/);
|
||||
|
|
@ -452,10 +452,10 @@ m.Template = m.Class({
|
|||
|
||||
"trainingCategory": function() { return this.get("TrainingRestrictions/Category"); },
|
||||
|
||||
"buildTime": function(productionQueue) {
|
||||
"buildTime": function(researcher) {
|
||||
let time = +this.get("Cost/BuildTime");
|
||||
if (productionQueue)
|
||||
time *= productionQueue.techCostMultiplier("time");
|
||||
if (researcher)
|
||||
time *= researcher.techCostMultiplier("time");
|
||||
return time;
|
||||
},
|
||||
|
||||
|
|
@ -940,7 +940,7 @@ m.Entity = m.Class({
|
|||
return this;
|
||||
},
|
||||
|
||||
"train": function(civ, type, count, metadata, promotedTypes, pushFront = false)
|
||||
"train": function(civ, type, count, metadata, pushFront = false)
|
||||
{
|
||||
let trainable = this.trainableEntities(civ);
|
||||
if (!trainable)
|
||||
|
|
@ -960,7 +960,6 @@ m.Entity = m.Class({
|
|||
"template": type,
|
||||
"count": count,
|
||||
"metadata": metadata,
|
||||
"promoted": promotedTypes,
|
||||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ m.Technology.prototype.pairedWith = function()
|
|||
return this._pairedWith;
|
||||
};
|
||||
|
||||
m.Technology.prototype.cost = function(productionQueue)
|
||||
m.Technology.prototype.cost = function(researcher)
|
||||
{
|
||||
if (!this._template.cost)
|
||||
return undefined;
|
||||
|
|
@ -73,15 +73,15 @@ m.Technology.prototype.cost = function(productionQueue)
|
|||
for (let type in this._template.cost)
|
||||
{
|
||||
cost[type] = +this._template.cost[type];
|
||||
if (productionQueue)
|
||||
cost[type] *= productionQueue.techCostMultiplier(type);
|
||||
if (researcher)
|
||||
cost[type] *= researcher.techCostMultiplier(type);
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
|
||||
m.Technology.prototype.costSum = function(productionQueue)
|
||||
m.Technology.prototype.costSum = function(researcher)
|
||||
{
|
||||
let cost = this.cost(productionQueue);
|
||||
const cost = this.cost(researcher);
|
||||
if (!cost)
|
||||
return 0;
|
||||
let ret = 0;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ PETRA.TrainingPlan.prototype.start = function(gameState)
|
|||
|
||||
if (this.metadata && this.metadata.base !== undefined && this.metadata.base === 0)
|
||||
this.metadata.base = this.trainers[0].getMetadata(PlayerID, "base");
|
||||
this.trainers[0].train(gameState.getPlayerCiv(), this.type, this.number, this.metadata, this.promotedTypes(gameState));
|
||||
this.trainers[0].train(gameState.getPlayerCiv(), this.type, this.number, this.metadata);
|
||||
|
||||
this.onStart(gameState);
|
||||
};
|
||||
|
|
@ -134,36 +134,6 @@ PETRA.TrainingPlan.prototype.addItem = function(amount = 1)
|
|||
this.number += amount;
|
||||
};
|
||||
|
||||
/** Find the promoted types corresponding to this.type */
|
||||
PETRA.TrainingPlan.prototype.promotedTypes = function(gameState)
|
||||
{
|
||||
let types = [];
|
||||
let promotion = this.template.promotion();
|
||||
let previous;
|
||||
let template;
|
||||
while (promotion)
|
||||
{
|
||||
types.push(promotion);
|
||||
previous = promotion;
|
||||
template = gameState.getTemplate(promotion);
|
||||
if (!template)
|
||||
{
|
||||
if (gameState.ai.Config.debug > 0)
|
||||
API3.warn(" promotion template " + promotion + " is not found");
|
||||
promotion = undefined;
|
||||
break;
|
||||
}
|
||||
promotion = template.promotion();
|
||||
if (previous === promotion)
|
||||
{
|
||||
if (gameState.ai.Config.debug > 0)
|
||||
API3.warn(" unit " + promotion + " is its own promoted unit");
|
||||
promotion = undefined;
|
||||
}
|
||||
}
|
||||
return types;
|
||||
};
|
||||
|
||||
PETRA.TrainingPlan.prototype.Serialize = function()
|
||||
{
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -340,6 +340,13 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
"isUpgrading": cmpUpgrade.IsUpgrading()
|
||||
};
|
||||
|
||||
const cmpResearcher = Engine.QueryInterface(ent, IID_Researcher);
|
||||
if (cmpResearcher)
|
||||
ret.researcher = {
|
||||
"technologies": cmpResearcher.GetTechnologiesList(),
|
||||
"techCostMultiplier": cmpResearcher.GetTechCostMultiplier()
|
||||
};
|
||||
|
||||
let cmpStatusEffects = Engine.QueryInterface(ent, IID_StatusEffectsReceiver);
|
||||
if (cmpStatusEffects)
|
||||
ret.statusEffects = cmpStatusEffects.GetActiveStatuses();
|
||||
|
|
@ -347,13 +354,16 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
|
||||
if (cmpProductionQueue)
|
||||
ret.production = {
|
||||
"entities": cmpProductionQueue.GetEntitiesList(),
|
||||
"technologies": cmpProductionQueue.GetTechnologiesList(),
|
||||
"techCostMultiplier": cmpProductionQueue.GetTechCostMultiplier(),
|
||||
"queue": cmpProductionQueue.GetQueue(),
|
||||
"autoqueue": cmpProductionQueue.IsAutoQueueing()
|
||||
};
|
||||
|
||||
const cmpTrainer = Engine.QueryInterface(ent, IID_Trainer);
|
||||
if (cmpTrainer)
|
||||
ret.trainer = {
|
||||
"entities": cmpTrainer.GetEntitiesList()
|
||||
};
|
||||
|
||||
let cmpTrader = Engine.QueryInterface(ent, IID_Trader);
|
||||
if (cmpTrader)
|
||||
ret.trader = {
|
||||
|
|
@ -689,10 +699,10 @@ GuiInterface.prototype.GetStartedResearch = function(player)
|
|||
for (let tech of cmpTechnologyManager.GetStartedTechs())
|
||||
{
|
||||
ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
|
||||
let cmpProductionQueue = Engine.QueryInterface(ret[tech].researcher, IID_ProductionQueue);
|
||||
if (cmpProductionQueue)
|
||||
const cmpResearcher = Engine.QueryInterface(ret[tech].researcher, IID_Researcher);
|
||||
if (cmpResearcher)
|
||||
{
|
||||
const research = cmpProductionQueue.GetQueue().find(item => item.technologyTemplate === tech);
|
||||
const research = cmpResearcher.GetResearchingTechnologyByName(tech);
|
||||
ret[tech].progress = research.progress;
|
||||
ret[tech].timeRemaining = research.timeRemaining;
|
||||
ret[tech].paused = research.paused;
|
||||
|
|
@ -1978,11 +1988,7 @@ GuiInterface.prototype.CanAttack = function(player, data)
|
|||
*/
|
||||
GuiInterface.prototype.GetBatchTime = function(player, data)
|
||||
{
|
||||
let cmpProductionQueue = Engine.QueryInterface(data.entity, IID_ProductionQueue);
|
||||
if (!cmpProductionQueue)
|
||||
return 0;
|
||||
|
||||
return cmpProductionQueue.GetBatchTime(data.batchSize);
|
||||
return Engine.QueryInterface(data.entity, IID_Trainer)?.GetBatchTime(data.batchSize) || 0;
|
||||
};
|
||||
|
||||
GuiInterface.prototype.IsMapRevealed = function(player)
|
||||
|
|
|
|||
|
|
@ -390,6 +390,16 @@ Player.prototype.TrySubtractResources = function(amounts)
|
|||
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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
447
binaries/data/mods/public/simulation/components/Researcher.js
Normal file
447
binaries/data/mods/public/simulation/components/Researcher.js
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
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.
|
||||
*/
|
||||
Researcher.prototype.Item = function() {};
|
||||
|
||||
/**
|
||||
* @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.prototype.Init = 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)
|
||||
{
|
||||
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] === undefined ? 1 : 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 || 1) * (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.
|
||||
const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
|
||||
cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the first work is performed.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.timeRemaining > allocatedTime)
|
||||
{
|
||||
this.timeRemaining -= allocatedTime;
|
||||
return allocatedTime;
|
||||
}
|
||||
this.Finish();
|
||||
return this.timeRemaining;
|
||||
};
|
||||
|
||||
Researcher.prototype.Item.prototype.Pause = function()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return {
|
||||
"technologyTemplate": this.templateName,
|
||||
"progress": 1 - (this.timeRemaining / this.timeTotal),
|
||||
"timeRemaining": this.timeRemaining,
|
||||
"paused": this.paused,
|
||||
"metadata": this.metadata
|
||||
};
|
||||
};
|
||||
|
||||
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 = [];
|
||||
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 in this.template.TechCostMultiplier)
|
||||
techCostMultiplier[res] = ApplyValueModificationsToEntity(
|
||||
"Researcher/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 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();
|
||||
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)
|
||||
{
|
||||
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 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.
|
||||
*/
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @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);
|
||||
|
|
@ -282,6 +282,17 @@ TechnologyManager.prototype.ResearchTechnology = function(tech)
|
|||
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
|
||||
|
|
|
|||
727
binaries/data/mods/public/simulation/components/Trainer.js
Normal file
727
binaries/data/mods/public/simulation/components/Trainer.js
Normal file
|
|
@ -0,0 +1,727 @@
|
|||
function Trainer() {}
|
||||
|
||||
Trainer.prototype.Schema =
|
||||
"<a:help>Allows the entity to train new units.</a:help>" +
|
||||
"<a:example>" +
|
||||
"<BatchTimeModifier>0.7</BatchTimeModifier>" +
|
||||
"<Entities datatype='tokens'>" +
|
||||
"\n units/{civ}/support_female_citizen\n units/{native}/support_trader\n units/athen/infantry_spearman_b\n " +
|
||||
"</Entities>" +
|
||||
"</a:example>" +
|
||||
"<optional>" +
|
||||
"<element name='BatchTimeModifier' a:help='Modifier that influences the time benefit for batch training. Defaults to 1, which means no benefit.'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Entities' a:help='Space-separated list of entity template names that this entity can train. The special string \"{civ}\" will be automatically replaced by the civ code of the entity's owner, while the string \"{native}\" will be automatically replaced by the entity's civ code.'>" +
|
||||
"<attribute name='datatype'>" +
|
||||
"<value>tokens</value>" +
|
||||
"</attribute>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
/**
|
||||
* This object represents a batch of entities being trained.
|
||||
*/
|
||||
Trainer.prototype.Item = function() {};
|
||||
|
||||
/**
|
||||
* @param {string} templateName - The name of the template we ought to train.
|
||||
* @param {number} count - The size of the batch to train.
|
||||
* @param {number} trainer - The entity ID of our trainer.
|
||||
* @param {string} metadata - Optionally any metadata to attach to us.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Init = function(templateName, count, trainer, metadata)
|
||||
{
|
||||
this.count = count;
|
||||
this.templateName = templateName;
|
||||
this.trainer = trainer;
|
||||
this.metadata = metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare for the queue.
|
||||
* @param {Object} trainCostMultiplier - The multipliers to use when calculating costs.
|
||||
* @param {number} batchTimeMultiplier - The factor to use when training this batches.
|
||||
*
|
||||
* @return {boolean} - Whether the item was successfully initiated.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Queue = function(trainCostMultiplier, batchTimeMultiplier)
|
||||
{
|
||||
if (!Number.isInteger(this.count) || this.count <= 0)
|
||||
{
|
||||
error("Invalid batch count " + this.count + ".");
|
||||
return false;
|
||||
}
|
||||
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
const template = cmpTemplateManager.GetTemplate(this.templateName);
|
||||
if (!template)
|
||||
return false;
|
||||
|
||||
const cmpPlayer = QueryOwnerInterface(this.trainer);
|
||||
if (!cmpPlayer)
|
||||
return false;
|
||||
this.player = cmpPlayer.GetPlayerID();
|
||||
|
||||
this.resources = {};
|
||||
const totalResources = {};
|
||||
|
||||
for (const res in template.Cost.Resources)
|
||||
{
|
||||
this.resources[res] = (trainCostMultiplier[res] === undefined ? 1 : trainCostMultiplier[res]) *
|
||||
ApplyValueModificationsToTemplate(
|
||||
"Cost/Resources/" + res,
|
||||
+template.Cost.Resources[res],
|
||||
this.player,
|
||||
template);
|
||||
|
||||
totalResources[res] = Math.floor(this.count * this.resources[res]);
|
||||
}
|
||||
// TrySubtractResources should report error to player (they ran out of resources).
|
||||
if (!cmpPlayer.TrySubtractResources(totalResources))
|
||||
return false;
|
||||
|
||||
this.population = ApplyValueModificationsToTemplate("Cost/Population", +template.Cost.Population, this.player, template);
|
||||
|
||||
if (template.TrainingRestrictions)
|
||||
{
|
||||
const unitCategory = template.TrainingRestrictions.Category;
|
||||
const cmpPlayerEntityLimits = QueryPlayerIDInterface(this.player, IID_EntityLimits);
|
||||
if (cmpPlayerEntityLimits)
|
||||
{
|
||||
if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, this.count, this.templateName, template.TrainingRestrictions.MatchLimit))
|
||||
// Already warned, return.
|
||||
{
|
||||
cmpPlayer.RefundResources(totalResources);
|
||||
return false;
|
||||
}
|
||||
// ToDo: Should warn here v and return?
|
||||
cmpPlayerEntityLimits.ChangeCount(unitCategory, this.count);
|
||||
if (template.TrainingRestrictions.MatchLimit)
|
||||
cmpPlayerEntityLimits.ChangeMatchCount(this.templateName, this.count);
|
||||
}
|
||||
}
|
||||
|
||||
const buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, this.player, template);
|
||||
|
||||
const time = batchTimeMultiplier * (trainCostMultiplier.time || 1) * buildTime * 1000;
|
||||
this.timeRemaining = time;
|
||||
this.timeTotal = time;
|
||||
|
||||
const cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
|
||||
cmpTrigger.CallEvent("OnTrainingQueued", {
|
||||
"playerid": this.player,
|
||||
"unitTemplate": this.templateName,
|
||||
"count": this.count,
|
||||
"metadata": this.metadata,
|
||||
"trainerEntity": this.trainer
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy cached entities, refund resources and free (population) limits.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Stop = function()
|
||||
{
|
||||
// Destroy any cached entities (those which didn't spawn for some reason).
|
||||
if (this.entities?.length)
|
||||
{
|
||||
for (const ent of this.entities)
|
||||
Engine.DestroyEntity(ent);
|
||||
|
||||
delete this.entities;
|
||||
}
|
||||
|
||||
const cmpPlayer = QueryPlayerIDInterface(this.player);
|
||||
|
||||
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
const template = cmpTemplateManager.GetTemplate(this.templateName);
|
||||
if (template.TrainingRestrictions)
|
||||
{
|
||||
const cmpPlayerEntityLimits = QueryPlayerIDInterface(this.player, IID_EntityLimits);
|
||||
if (cmpPlayerEntityLimits)
|
||||
cmpPlayerEntityLimits.ChangeCount(template.TrainingRestrictions.Category, -this.count);
|
||||
if (template.TrainingRestrictions.MatchLimit)
|
||||
cmpPlayerEntityLimits.ChangeMatchCount(this.templateName, -this.count);
|
||||
}
|
||||
|
||||
const cmpStatisticsTracker = QueryPlayerIDInterface(this.player, IID_StatisticsTracker);
|
||||
const totalCosts = {};
|
||||
for (const resource in this.resources)
|
||||
{
|
||||
totalCosts[resource] = Math.floor(this.count * this.resources[resource]);
|
||||
if (cmpStatisticsTracker)
|
||||
cmpStatisticsTracker.IncreaseResourceUsedCounter(resource, -totalCosts[resource]);
|
||||
}
|
||||
|
||||
if (cmpPlayer)
|
||||
{
|
||||
if (this.started)
|
||||
cmpPlayer.UnReservePopulationSlots(this.population * this.count);
|
||||
cmpPlayer.RefundResources(totalCosts);
|
||||
cmpPlayer.UnBlockTraining();
|
||||
}
|
||||
|
||||
delete this.resources;
|
||||
};
|
||||
|
||||
/**
|
||||
* This starts the item, reserving population.
|
||||
* @return {boolean} - Whether the item was started successfully.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Start = function()
|
||||
{
|
||||
const cmpPlayer = QueryPlayerIDInterface(this.player);
|
||||
if (!cmpPlayer)
|
||||
return false;
|
||||
|
||||
const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(this.templateName);
|
||||
this.population = ApplyValueModificationsToTemplate(
|
||||
"Cost/Population",
|
||||
+template.Cost.Population,
|
||||
this.player,
|
||||
template);
|
||||
|
||||
this.missingPopSpace = cmpPlayer.TryReservePopulationSlots(this.population * this.count);
|
||||
if (this.missingPopSpace)
|
||||
{
|
||||
cmpPlayer.BlockTraining();
|
||||
return false;
|
||||
}
|
||||
cmpPlayer.UnBlockTraining();
|
||||
|
||||
Engine.PostMessage(this.trainer, MT_TrainingStarted, { "entity": this.trainer });
|
||||
|
||||
this.started = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
Trainer.prototype.Item.prototype.Finish = function()
|
||||
{
|
||||
this.Spawn();
|
||||
if (!this.count)
|
||||
this.finished = true;
|
||||
};
|
||||
|
||||
/*
|
||||
* This function creates the entities and places them in world if possible
|
||||
* (some of these entities may be garrisoned directly if autogarrison, the others are spawned).
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Spawn = function()
|
||||
{
|
||||
const createdEnts = [];
|
||||
const spawnedEnts = [];
|
||||
|
||||
// We need entities to test spawning, but we don't want to waste resources,
|
||||
// so only create them once and use as needed.
|
||||
if (!this.entities)
|
||||
{
|
||||
this.entities = [];
|
||||
for (let i = 0; i < this.count; ++i)
|
||||
this.entities.push(Engine.AddEntity(this.templateName));
|
||||
}
|
||||
|
||||
let autoGarrison;
|
||||
const cmpRallyPoint = Engine.QueryInterface(this.trainer, IID_RallyPoint);
|
||||
if (cmpRallyPoint)
|
||||
{
|
||||
const data = cmpRallyPoint.GetData()[0];
|
||||
if (data?.target && data.target == this.trainer && data.command == "garrison")
|
||||
autoGarrison = true;
|
||||
}
|
||||
|
||||
const cmpFootprint = Engine.QueryInterface(this.trainer, IID_Footprint);
|
||||
const cmpPosition = Engine.QueryInterface(this.trainer, IID_Position);
|
||||
const positionTrainer = cmpPosition && cmpPosition.GetPosition();
|
||||
|
||||
const cmpPlayerEntityLimits = QueryPlayerIDInterface(this.player, IID_EntityLimits);
|
||||
const cmpPlayerStatisticsTracker = QueryPlayerIDInterface(this.player, IID_StatisticsTracker);
|
||||
while (this.entities.length)
|
||||
{
|
||||
const ent = this.entities[0];
|
||||
const cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
let garrisoned = false;
|
||||
|
||||
if (autoGarrison)
|
||||
{
|
||||
const cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
|
||||
if (cmpGarrisonable)
|
||||
{
|
||||
// Temporary owner affectation needed for GarrisonHolder checks.
|
||||
cmpNewOwnership.SetOwnerQuiet(this.player);
|
||||
garrisoned = cmpGarrisonable.Garrison(this.trainer);
|
||||
cmpNewOwnership.SetOwnerQuiet(INVALID_PLAYER);
|
||||
}
|
||||
}
|
||||
|
||||
if (!garrisoned)
|
||||
{
|
||||
const pos = cmpFootprint.PickSpawnPoint(ent);
|
||||
if (pos.y < 0)
|
||||
break;
|
||||
|
||||
const cmpNewPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
cmpNewPosition.JumpTo(pos.x, pos.z);
|
||||
|
||||
if (positionTrainer)
|
||||
cmpNewPosition.SetYRotation(positionTrainer.horizAngleTo(pos));
|
||||
|
||||
spawnedEnts.push(ent);
|
||||
}
|
||||
|
||||
// Decrement entity count in the EntityLimits component
|
||||
// since it will be increased by EntityLimits.OnGlobalOwnershipChanged,
|
||||
// i.e. we replace a 'trained' entity by 'alive' one.
|
||||
// Must be done after spawn check so EntityLimits decrements only if unit spawns.
|
||||
if (cmpPlayerEntityLimits)
|
||||
{
|
||||
const cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
|
||||
if (cmpTrainingRestrictions)
|
||||
cmpPlayerEntityLimits.ChangeCount(cmpTrainingRestrictions.GetCategory(), -1);
|
||||
}
|
||||
cmpNewOwnership.SetOwner(this.player);
|
||||
|
||||
if (cmpPlayerStatisticsTracker)
|
||||
cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter(ent);
|
||||
|
||||
this.count--;
|
||||
this.entities.shift();
|
||||
createdEnts.push(ent);
|
||||
}
|
||||
|
||||
if (spawnedEnts.length && !autoGarrison && cmpRallyPoint)
|
||||
for (const com of GetRallyPointCommands(cmpRallyPoint, spawnedEnts))
|
||||
ProcessCommand(this.player, com);
|
||||
|
||||
const cmpPlayer = QueryOwnerInterface(this.trainer);
|
||||
if (createdEnts.length)
|
||||
{
|
||||
if (this.population)
|
||||
cmpPlayer.UnReservePopulationSlots(this.population * createdEnts.length);
|
||||
// Play a sound, but only for the first in the batch (to avoid nasty phasing effects).
|
||||
PlaySound("trained", createdEnts[0]);
|
||||
Engine.PostMessage(this.trainer, MT_TrainingFinished, {
|
||||
"entities": createdEnts,
|
||||
"owner": this.player,
|
||||
"metadata": this.metadata
|
||||
});
|
||||
}
|
||||
if (this.count)
|
||||
{
|
||||
cmpPlayer.BlockTraining();
|
||||
|
||||
if (!this.spawnNotified)
|
||||
{
|
||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
|
||||
"players": [cmpPlayer.GetPlayerID()],
|
||||
"message": markForTranslation("Can't find free space to spawn trained units."),
|
||||
"translateMessage": true
|
||||
});
|
||||
this.spawnNotified = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cmpPlayer.UnBlockTraining();
|
||||
delete this.spawnNotified;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} allocatedTime - The time allocated to this item.
|
||||
* @return {number} - The time used for this item.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.Progress = function(allocatedTime)
|
||||
{
|
||||
// We couldn't start this timeout, try again later.
|
||||
if (!this.started && !this.Start())
|
||||
return allocatedTime;
|
||||
|
||||
if (this.timeRemaining > allocatedTime)
|
||||
{
|
||||
this.timeRemaining -= allocatedTime;
|
||||
return allocatedTime;
|
||||
}
|
||||
this.Finish();
|
||||
return this.timeRemaining;
|
||||
};
|
||||
|
||||
Trainer.prototype.Item.prototype.Pause = function()
|
||||
{
|
||||
this.paused = true;
|
||||
};
|
||||
|
||||
Trainer.prototype.Item.prototype.Unpause = function()
|
||||
{
|
||||
delete this.paused;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Object} - Some basic information of this batch.
|
||||
*/
|
||||
Trainer.prototype.Item.prototype.GetBasicInfo = function()
|
||||
{
|
||||
return {
|
||||
"unitTemplate": this.templateName,
|
||||
"count": this.count,
|
||||
"neededSlots": this.missingPopSpace,
|
||||
"progress": 1 - (this.timeRemaining / this.timeTotal),
|
||||
"timeRemaining": this.timeRemaining,
|
||||
"paused": this.paused,
|
||||
"metadata": this.metadata
|
||||
};
|
||||
};
|
||||
|
||||
Trainer.prototype.Item.prototype.Serialize = function(id)
|
||||
{
|
||||
return {
|
||||
"id": id,
|
||||
"count": this.count,
|
||||
"entities": this.entities,
|
||||
"metadata": this.metadata,
|
||||
"missingPopSpace": this.missingPopSpace,
|
||||
"paused": this.paused,
|
||||
"player": this.player,
|
||||
"trainer": this.trainer,
|
||||
"resource": this.resources,
|
||||
"started": this.started,
|
||||
"templateName": this.templateName,
|
||||
"timeRemaining": this.timeRemaining,
|
||||
"timeTotal": this.timeTotal,
|
||||
};
|
||||
};
|
||||
|
||||
Trainer.prototype.Item.prototype.Deserialize = function(data)
|
||||
{
|
||||
this.Init(data.templateName, data.count, data.trainer, data.metadata);
|
||||
|
||||
this.entities = data.entities;
|
||||
this.missingPopSpace = data.missingPopSpace;
|
||||
this.paused = data.paused;
|
||||
this.player = data.player;
|
||||
this.trainer = data.trainer;
|
||||
this.resources = data.resources;
|
||||
this.started = data.started;
|
||||
this.timeRemaining = data.timeRemaining;
|
||||
this.timeTotal = data.timeTotal;
|
||||
};
|
||||
|
||||
Trainer.prototype.Init = function()
|
||||
{
|
||||
this.nextID = 1;
|
||||
this.queue = new Map();
|
||||
};
|
||||
|
||||
Trainer.prototype.Serialize = function()
|
||||
{
|
||||
const queue = [];
|
||||
for (const [id, item] of this.queue)
|
||||
queue.push(item.Serialize(id));
|
||||
|
||||
return {
|
||||
"entitiesMap": this.entitiesMap,
|
||||
"nextID": this.nextID,
|
||||
"queue": queue
|
||||
};
|
||||
};
|
||||
|
||||
Trainer.prototype.Deserialize = function(data)
|
||||
{
|
||||
this.Init();
|
||||
this.entitiesMap = data.entitiesMap;
|
||||
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 entities that can be trained by this entity.
|
||||
*/
|
||||
Trainer.prototype.GetEntitiesList = function()
|
||||
{
|
||||
return Array.from(this.entitiesMap.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the new list of producible entities
|
||||
* and update any entities currently being produced.
|
||||
*/
|
||||
Trainer.prototype.CalculateEntitiesMap = function()
|
||||
{
|
||||
// Don't reset the map, it's used below to update entities.
|
||||
if (!this.entitiesMap)
|
||||
this.entitiesMap = new Map();
|
||||
if (!this.template.Entities)
|
||||
return;
|
||||
|
||||
const string = this.template.Entities._string;
|
||||
// Tokens can be added -> process an empty list to get them.
|
||||
let addedTokens = ApplyValueModificationsToEntity("Trainer/Entities/_string", "", this.entity);
|
||||
if (!addedTokens && !string)
|
||||
return;
|
||||
|
||||
addedTokens = addedTokens == "" ? [] : addedTokens.split(/\s+/);
|
||||
|
||||
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
const cmpPlayer = QueryOwnerInterface(this.entity);
|
||||
|
||||
const disabledEntities = cmpPlayer ? cmpPlayer.GetDisabledTemplates() : {};
|
||||
|
||||
/**
|
||||
* Process tokens:
|
||||
* - process token modifiers (this is a bit tricky).
|
||||
* - replace the "{civ}" and "{native}" codes with the owner's civ ID and entity's civ ID
|
||||
* - remove disabled entities
|
||||
* - upgrade templates where necessary
|
||||
* This also updates currently queued production (it's more convenient to do it here).
|
||||
*/
|
||||
|
||||
const removeAllQueuedTemplate = (token) => {
|
||||
const queue = clone(this.queue);
|
||||
const template = this.entitiesMap.get(token);
|
||||
for (const [id, item] of queue)
|
||||
if (item.templateName == template)
|
||||
this.StopBatch(id);
|
||||
};
|
||||
|
||||
// ToDo: Notice this doesn't account for entity limits changing due to the template change.
|
||||
const updateAllQueuedTemplate = (token, updateTo) => {
|
||||
const template = this.entitiesMap.get(token);
|
||||
for (const [id, item] of this.queue)
|
||||
if (item.templateName === template)
|
||||
item.templateName = updateTo;
|
||||
};
|
||||
|
||||
const toks = string.split(/\s+/);
|
||||
for (const tok of addedTokens)
|
||||
toks.push(tok);
|
||||
|
||||
const nativeCiv = Engine.QueryInterface(this.entity, IID_Identity)?.GetCiv();
|
||||
const playerCiv = cmpPlayer?.GetCiv();
|
||||
|
||||
const addedDict = addedTokens.reduce((out, token) => { out[token] = true; return out; }, {});
|
||||
this.entitiesMap = toks.reduce((entMap, token) => {
|
||||
const rawToken = token;
|
||||
if (!(token in addedDict))
|
||||
{
|
||||
// This is a bit wasteful but I can't think of a simpler/better way.
|
||||
// The list of token is unlikely to be a performance bottleneck anyways.
|
||||
token = ApplyValueModificationsToEntity("Trainer/Entities/_string", token, this.entity);
|
||||
token = token.split(/\s+/);
|
||||
if (token.every(tok => addedTokens.indexOf(tok) !== -1))
|
||||
{
|
||||
removeAllQueuedTemplate(rawToken);
|
||||
return entMap;
|
||||
}
|
||||
token = token[0];
|
||||
}
|
||||
// Replace the "{civ}" and "{native}" codes with the owner's civ ID and entity's civ ID.
|
||||
if (nativeCiv)
|
||||
token = token.replace(/\{native\}/g, nativeCiv);
|
||||
if (playerCiv)
|
||||
token = token.replace(/\{civ\}/g, playerCiv);
|
||||
|
||||
// Filter out disabled and invalid entities.
|
||||
if (disabledEntities[token] || !cmpTemplateManager.TemplateExists(token))
|
||||
{
|
||||
removeAllQueuedTemplate(rawToken);
|
||||
return entMap;
|
||||
}
|
||||
|
||||
token = this.GetUpgradedTemplate(token);
|
||||
entMap.set(rawToken, token);
|
||||
updateAllQueuedTemplate(rawToken, token);
|
||||
return entMap;
|
||||
}, new Map());
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the upgraded template name if necessary.
|
||||
*/
|
||||
Trainer.prototype.GetUpgradedTemplate = function(templateName)
|
||||
{
|
||||
const cmpPlayer = QueryOwnerInterface(this.entity);
|
||||
if (!cmpPlayer)
|
||||
return templateName;
|
||||
|
||||
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
let template = cmpTemplateManager.GetTemplate(templateName);
|
||||
while (template && template.Promotion !== undefined)
|
||||
{
|
||||
const requiredXp = ApplyValueModificationsToTemplate(
|
||||
"Promotion/RequiredXp",
|
||||
+template.Promotion.RequiredXp,
|
||||
cmpPlayer.GetPlayerID(),
|
||||
template);
|
||||
if (requiredXp > 0)
|
||||
break;
|
||||
templateName = template.Promotion.Entity;
|
||||
template = cmpTemplateManager.GetTemplate(templateName);
|
||||
}
|
||||
return templateName;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Object} - The multipliers to change the costs of any training activity with.
|
||||
*/
|
||||
Trainer.prototype.GetTrainCostMultiplier = function()
|
||||
{
|
||||
const trainCostMultiplier = {};
|
||||
for (const res in this.template.TrainCostMultiplier)
|
||||
trainCostMultiplier[res] = ApplyValueModificationsToEntity(
|
||||
"Trainer/TrainCostMultiplier/" + res,
|
||||
+this.template.TrainCostMultiplier[res],
|
||||
this.entity);
|
||||
|
||||
return trainCostMultiplier;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns batch build time.
|
||||
*/
|
||||
Trainer.prototype.GetBatchTime = function(batchSize)
|
||||
{
|
||||
// TODO: work out what equation we should use here.
|
||||
return Math.pow(batchSize, ApplyValueModificationsToEntity(
|
||||
"Trainer/BatchTimeModifier",
|
||||
+(this.template.BatchTimeModifier || 1),
|
||||
this.entity));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} templateName - The template name to check.
|
||||
* @return {boolean} - Whether we can train this template.
|
||||
*/
|
||||
Trainer.prototype.CanTrain = function(templateName)
|
||||
{
|
||||
return this.GetEntitiesList().includes(templateName);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} templateName - The entity to queue.
|
||||
* @param {number} count - The batch size.
|
||||
* @param {string} metadata - Any metadata attached to the item.
|
||||
*
|
||||
* @return {number} - The ID of the item. -1 if the item could not be queued.
|
||||
*/
|
||||
Trainer.prototype.QueueBatch = function(templateName, count, metadata)
|
||||
{
|
||||
const item = new this.Item();
|
||||
item.Init(templateName, count, this.entity, metadata);
|
||||
|
||||
const trainCostMultiplier = this.GetTrainCostMultiplier();
|
||||
const batchTimeMultiplier = this.GetBatchTime(count);
|
||||
if (!item.Queue(trainCostMultiplier, batchTimeMultiplier))
|
||||
return -1;
|
||||
|
||||
const id = this.nextID++;
|
||||
this.queue.set(id, item);
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} id - The ID of the batch being trained here we need to stop.
|
||||
*/
|
||||
Trainer.prototype.StopBatch = function(id)
|
||||
{
|
||||
this.queue.get(id).Stop();
|
||||
this.queue.delete(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} id - The ID of the training.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
Trainer.prototype.HasBatch = function(id)
|
||||
{
|
||||
return this.queue.has(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @parameter {number} id - The id of the training.
|
||||
* @return {Object} - Some basic information about the training.
|
||||
*/
|
||||
Trainer.prototype.GetBatch = function(id)
|
||||
{
|
||||
const item = this.queue.get(id);
|
||||
return item?.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.
|
||||
*/
|
||||
Trainer.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;
|
||||
};
|
||||
|
||||
Trainer.prototype.OnCivChanged = function()
|
||||
{
|
||||
this.CalculateEntitiesMap();
|
||||
};
|
||||
|
||||
Trainer.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
if (msg.to != INVALID_PLAYER)
|
||||
this.CalculateEntitiesMap();
|
||||
};
|
||||
|
||||
Trainer.prototype.OnValueModification = function(msg)
|
||||
{
|
||||
// If the promotion requirements of units is changed,
|
||||
// update the entities list so that automatically promoted units are shown
|
||||
// appropriately in the list.
|
||||
if (msg.component != "Promotion" && (msg.component != "Trainer" ||
|
||||
!msg.valueNames.some(val => val.startsWith("Trainer/Entities/"))))
|
||||
return;
|
||||
|
||||
if (msg.entities.indexOf(this.entity) === -1)
|
||||
return;
|
||||
|
||||
// This also updates the queued production if necessary.
|
||||
this.CalculateEntitiesMap();
|
||||
|
||||
// Inform the GUI that it'll need to recompute the selection panel.
|
||||
// TODO: it would be better to only send the message if something actually changing
|
||||
// for the current training queue.
|
||||
const cmpPlayer = QueryOwnerInterface(this.entity);
|
||||
if (cmpPlayer)
|
||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID());
|
||||
};
|
||||
|
||||
Trainer.prototype.OnDisabledTemplatesChanged = function(msg)
|
||||
{
|
||||
this.CalculateEntitiesMap();
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Trainer, "Trainer", Trainer);
|
||||
|
|
@ -5,15 +5,3 @@ Engine.RegisterInterface("ProductionQueue");
|
|||
* sent from ProductionQueue component to the current entity whenever the training queue changes.
|
||||
*/
|
||||
Engine.RegisterMessageType("ProductionQueueChanged");
|
||||
|
||||
/**
|
||||
* Message of the form { "entity": number }
|
||||
* sent from ProductionQueue component to the current entity whenever a unit is about to be trained.
|
||||
*/
|
||||
Engine.RegisterMessageType("TrainingStarted");
|
||||
|
||||
/**
|
||||
* Message of the form { "entities": number[], "owner": number, "metadata": object }
|
||||
* sent from ProductionQueue component to the current entity whenever a unit has been trained.
|
||||
*/
|
||||
Engine.RegisterMessageType("TrainingFinished");
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("Researcher");
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
Engine.RegisterInterface("Trainer");
|
||||
|
||||
/**
|
||||
* Message of the form { "entity": number }
|
||||
* sent from Trainer component to the current entity whenever a unit is about to be trained.
|
||||
*/
|
||||
Engine.RegisterMessageType("TrainingStarted");
|
||||
|
||||
/**
|
||||
* Message of the form { "entities": number[], "owner": number, "metadata": object }
|
||||
* sent from Trainer component to the current entity whenever a unit has been trained.
|
||||
*/
|
||||
Engine.RegisterMessageType("TrainingFinished");
|
||||
|
|
@ -6,7 +6,6 @@ Engine.LoadComponentScript("interfaces/Barter.js");
|
|||
Engine.LoadComponentScript("interfaces/Builder.js");
|
||||
Engine.LoadComponentScript("interfaces/Capturable.js");
|
||||
Engine.LoadComponentScript("interfaces/CeasefireManager.js");
|
||||
Engine.LoadComponentScript("interfaces/Resistance.js");
|
||||
Engine.LoadComponentScript("interfaces/DeathDamage.js");
|
||||
Engine.LoadComponentScript("interfaces/EndGameManager.js");
|
||||
Engine.LoadComponentScript("interfaces/EntityLimits.js");
|
||||
|
|
@ -25,12 +24,15 @@ Engine.LoadComponentScript("interfaces/Population.js");
|
|||
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
|
||||
Engine.LoadComponentScript("interfaces/Promotion.js");
|
||||
Engine.LoadComponentScript("interfaces/Repairable.js");
|
||||
Engine.LoadComponentScript("interfaces/Researcher.js");
|
||||
Engine.LoadComponentScript("interfaces/Resistance.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceTrickle.js");
|
||||
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
|
||||
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
|
||||
Engine.LoadComponentScript("interfaces/Trader.js");
|
||||
Engine.LoadComponentScript("interfaces/Trainer.js");
|
||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/Treasure.js");
|
||||
|
|
|
|||
|
|
@ -1,626 +1,83 @@
|
|||
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");
|
||||
Engine.LoadComponentScript("interfaces/Foundation.js");
|
||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||
Engine.LoadComponentScript("interfaces/Researcher.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/TrainingRestrictions.js");
|
||||
Engine.LoadComponentScript("interfaces/Trigger.js");
|
||||
Engine.LoadComponentScript("interfaces/Trainer.js");
|
||||
Engine.LoadComponentScript("interfaces/Upgrade.js");
|
||||
Engine.LoadComponentScript("EntityLimits.js");
|
||||
Engine.LoadComponentScript("Timer.js");
|
||||
|
||||
Engine.RegisterGlobal("Resources", {
|
||||
"BuildSchema": (a, b) => {}
|
||||
});
|
||||
Engine.LoadComponentScript("ProductionQueue.js");
|
||||
Engine.LoadComponentScript("TrainingRestrictions.js");
|
||||
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value);
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToTemplate", (_, value) => value);
|
||||
const playerEnt = 2;
|
||||
const playerID = 1;
|
||||
const testEntity = 3;
|
||||
|
||||
function testEntitiesList()
|
||||
{
|
||||
Engine.RegisterGlobal("TechnologyTemplates", {
|
||||
"Has": name => name == "phase_town_athen" || name == "phase_city_athen",
|
||||
"Get": () => ({})
|
||||
});
|
||||
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
||||
"CancelTimer": (id) => {},
|
||||
"SetInterval": (ent, iid, func) => 1
|
||||
});
|
||||
|
||||
const productionQueueId = 6;
|
||||
const playerId = 1;
|
||||
const playerEntityID = 2;
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEnt
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
AddMock(playerEnt, IID_Player, {
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
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}" }
|
||||
});
|
||||
cmpProductionQueue.GetUpgradedTemplate = (template) => template;
|
||||
AddMock(testEntity, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEntityID
|
||||
});
|
||||
AddMock(testEntity, IID_Trainer, {
|
||||
"GetBatch": (id) => ({}),
|
||||
"HasBatch": (id) => false, // Assume we've finished.
|
||||
"Progress": (time) => time,
|
||||
"QueueBatch": () => 1,
|
||||
"StopBatch": (id) => {}
|
||||
});
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTechnologies": () => ({}),
|
||||
"GetDisabledTemplates": () => ({}),
|
||||
"GetPlayerID": () => playerId
|
||||
});
|
||||
const cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", null);
|
||||
|
||||
AddMock(playerEntityID, IID_TechnologyManager, {
|
||||
"CheckTechnologyRequirements": () => true,
|
||||
"IsInProgress": () => false,
|
||||
"IsTechnologyResearched": () => false
|
||||
});
|
||||
|
||||
AddMock(productionQueueId, IID_Ownership, {
|
||||
"GetOwner": () => playerId
|
||||
});
|
||||
// Test autoqueue.
|
||||
cmpProdQueue.EnableAutoQueue();
|
||||
|
||||
AddMock(productionQueueId, IID_Identity, {
|
||||
"GetCiv": () => "iber"
|
||||
});
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
|
||||
AddMock(productionQueueId, IID_Upgrade, {
|
||||
"IsUpgrading": () => false
|
||||
});
|
||||
cmpProdQueue.RemoveItem(cmpProdQueue.nextID -1);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
|
||||
cmpProductionQueue.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
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"]
|
||||
);
|
||||
cmpProdQueue.DisableAutoQueue();
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": name => name == "units/iber/support_female_citizen",
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
|
||||
cmpProductionQueue.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpProductionQueue.GetEntitiesList(), ["units/iber/support_female_citizen"]);
|
||||
// Test items which don't use all the time.
|
||||
AddMock(testEntity, IID_Trainer, {
|
||||
"GetBatch": (id) => ({}),
|
||||
"HasBatch": (id) => false, // Assume we've finished.
|
||||
"PauseBatch": (id) => {},
|
||||
"Progress": (time) => time - 250,
|
||||
"QueueBatch": () => 1,
|
||||
"StopBatch": (id) => {},
|
||||
"UnpauseBatch": (id) => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
cmpProdQueue.AddItem("some_template", "unit", 2);
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 2);
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTechnologies": () => ({}),
|
||||
"GetDisabledTemplates": () => ({ "units/athen/infantry_swordsman_b": true }),
|
||||
"GetPlayerID": () => playerId
|
||||
});
|
||||
|
||||
cmpProductionQueue.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpProductionQueue.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
// Test pushing an item to the front.
|
||||
cmpProdQueue.AddItem("some_template", "unit", 2);
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3, null, true);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 2);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue()[0].id, cmpProdQueue.nextID - 1);
|
||||
TS_ASSERT(cmpProdQueue.GetQueue()[1].paused);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTechnologies": () => ({}),
|
||||
"GetDisabledTemplates": () => ({ "units/iber/infantry_swordsman_b": true }),
|
||||
"GetPlayerID": () => playerId
|
||||
});
|
||||
|
||||
cmpProductionQueue.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpProductionQueue.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "athen",
|
||||
"GetDisabledTechnologies": () => ({ "gather_fishing_net": true }),
|
||||
"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()
|
||||
{
|
||||
// Setup
|
||||
let playerEnt = 2;
|
||||
let playerID = 1;
|
||||
let testEntity = 3;
|
||||
let spawedEntityIDs = [4, 5, 6, 7, 8];
|
||||
let spawned = 0;
|
||||
|
||||
Engine.AddEntity = () => {
|
||||
let id = spawedEntityIDs[spawned++];
|
||||
|
||||
ConstructComponent(id, "TrainingRestrictions", {
|
||||
"Category": "some_limit"
|
||||
});
|
||||
|
||||
AddMock(id, IID_Identity, {
|
||||
"GetClassesList": () => []
|
||||
});
|
||||
|
||||
AddMock(id, IID_Position, {
|
||||
"JumpTo": () => {}
|
||||
});
|
||||
|
||||
AddMock(id, IID_Ownership, {
|
||||
"SetOwner": (pid) => {
|
||||
let cmpEntLimits = QueryOwnerInterface(id, IID_EntityLimits);
|
||||
cmpEntLimits.OnGlobalOwnershipChanged({
|
||||
"entity": id,
|
||||
"from": -1,
|
||||
"to": pid
|
||||
});
|
||||
},
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
ConstructComponent(playerEnt, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 8
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"PushNotification": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Trigger, {
|
||||
"CallEvent": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
||||
"SetInterval": (ent, iid, func) => 1,
|
||||
"CancelTimer": (id) => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({
|
||||
"Cost": {
|
||||
"BuildTime": 0,
|
||||
"Population": 1,
|
||||
"Resources": {}
|
||||
},
|
||||
"TrainingRestrictions": {
|
||||
"Category": "some_limit",
|
||||
"MatchLimit": "7"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEnt
|
||||
});
|
||||
|
||||
AddMock(playerEnt, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetPlayerID": () => playerID,
|
||||
"GetTimeMultiplier": () => 0,
|
||||
"BlockTraining": () => {},
|
||||
"UnBlockTraining": () => {},
|
||||
"UnReservePopulationSlots": () => {},
|
||||
"TrySubtractResources": () => true,
|
||||
"AddResources": () => true,
|
||||
"TryReservePopulationSlots": () => false // Always have pop space.
|
||||
});
|
||||
|
||||
AddMock(testEntity, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
let cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", {
|
||||
"Entities": { "_string": "some_template" },
|
||||
"BatchTimeModifier": 1
|
||||
});
|
||||
|
||||
let cmpEntLimits = QueryOwnerInterface(testEntity, IID_EntityLimits);
|
||||
TS_ASSERT(cmpEntLimits.AllowedToTrain("some_limit", 8));
|
||||
TS_ASSERT(!cmpEntLimits.AllowedToTrain("some_limit", 9));
|
||||
TS_ASSERT(cmpEntLimits.AllowedToTrain("some_limit", 5, "some_template", 8));
|
||||
TS_ASSERT(!cmpEntLimits.AllowedToTrain("some_limit", 10, "some_template", 8));
|
||||
|
||||
// Check that the entity limits do get updated if the spawn succeeds.
|
||||
AddMock(testEntity, IID_Footprint, {
|
||||
"PickSpawnPoint": () => ({ "x": 0, "y": 1, "z": 0 })
|
||||
});
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 3);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts().some_template, 3);
|
||||
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 3);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts().some_template, 3);
|
||||
|
||||
// Now check that it doesn't get updated when the spawn doesn't succeed.
|
||||
AddMock(testEntity, IID_Footprint, {
|
||||
"PickSpawnPoint": () => ({ "x": -1, "y": -1, "z": -1 })
|
||||
});
|
||||
|
||||
AddMock(testEntity, IID_Upgrade, {
|
||||
"IsUpgrading": () => false
|
||||
});
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 6);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts().some_template, 6);
|
||||
|
||||
// Check that when the batch is removed the counts are subtracted again.
|
||||
cmpProdQueue.RemoveItem(cmpProdQueue.GetQueue()[0].id);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 3);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts().some_template, 3);
|
||||
}
|
||||
|
||||
function test_batch_adding()
|
||||
{
|
||||
let playerEnt = 2;
|
||||
let playerID = 1;
|
||||
let testEntity = 3;
|
||||
|
||||
ConstructComponent(playerEnt, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 8
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"PushNotification": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Trigger, {
|
||||
"CallEvent": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
||||
"SetInterval": (ent, iid, func) => 1
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({
|
||||
"Cost": {
|
||||
"BuildTime": 0,
|
||||
"Population": 1,
|
||||
"Resources": {}
|
||||
},
|
||||
"TrainingRestrictions": {
|
||||
"Category": "some_limit"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEnt
|
||||
});
|
||||
|
||||
AddMock(playerEnt, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetPlayerID": () => playerID,
|
||||
"GetTimeMultiplier": () => 0,
|
||||
"BlockTraining": () => {},
|
||||
"UnBlockTraining": () => {},
|
||||
"UnReservePopulationSlots": () => {},
|
||||
"TrySubtractResources": () => true,
|
||||
"TryReservePopulationSlots": () => false // Always have pop space.
|
||||
});
|
||||
|
||||
AddMock(testEntity, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
let cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", {
|
||||
"Entities": { "_string": "some_template" },
|
||||
"BatchTimeModifier": 1
|
||||
});
|
||||
|
||||
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
AddMock(testEntity, IID_Upgrade, {
|
||||
"IsUpgrading": () => true
|
||||
});
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
|
||||
AddMock(testEntity, IID_Upgrade, {
|
||||
"IsUpgrading": () => false
|
||||
});
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
}
|
||||
|
||||
function test_batch_removal()
|
||||
{
|
||||
let playerEnt = 2;
|
||||
let playerID = 1;
|
||||
let testEntity = 3;
|
||||
|
||||
ConstructComponent(playerEnt, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 8
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"PushNotification": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Trigger, {
|
||||
"CallEvent": () => {}
|
||||
});
|
||||
|
||||
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", null);
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({
|
||||
"Cost": {
|
||||
"BuildTime": 0,
|
||||
"Population": 1,
|
||||
"Resources": {}
|
||||
},
|
||||
"TrainingRestrictions": {
|
||||
"Category": "some_limit"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEnt
|
||||
});
|
||||
|
||||
let cmpPlayer = AddMock(playerEnt, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetPlayerID": () => playerID,
|
||||
"GetTimeMultiplier": () => 0,
|
||||
"BlockTraining": () => {},
|
||||
"UnBlockTraining": () => {},
|
||||
"UnReservePopulationSlots": () => {},
|
||||
"TrySubtractResources": () => true,
|
||||
"AddResources": () => {},
|
||||
"TryReservePopulationSlots": () => 1
|
||||
});
|
||||
let cmpPlayerBlockSpy = new Spy(cmpPlayer, "BlockTraining");
|
||||
let cmpPlayerUnblockSpy = new Spy(cmpPlayer, "UnBlockTraining");
|
||||
|
||||
AddMock(testEntity, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
let cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", {
|
||||
"Entities": { "_string": "some_template" },
|
||||
"BatchTimeModifier": 1
|
||||
});
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_EQUALS(cmpPlayerBlockSpy._called, 1);
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 2);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 2);
|
||||
|
||||
cmpProdQueue.RemoveItem(1);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
TS_ASSERT_EQUALS(cmpPlayerUnblockSpy._called, 1);
|
||||
|
||||
cmpProdQueue.RemoveItem(2);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_EQUALS(cmpPlayerUnblockSpy._called, 2);
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
cmpPlayer.TryReservePopulationSlots = () => false;
|
||||
cmpProdQueue.RemoveItem(3);
|
||||
TS_ASSERT_EQUALS(cmpPlayerUnblockSpy._called, 3);
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT_EQUALS(cmpPlayerUnblockSpy._called, 4);
|
||||
}
|
||||
|
||||
function test_token_changes()
|
||||
{
|
||||
const ent = 10;
|
||||
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;
|
||||
|
||||
// Merges interface of multiple components because it's enough here.
|
||||
Engine.RegisterGlobal("QueryOwnerInterface", () => ({
|
||||
// player
|
||||
"GetCiv": () => "test",
|
||||
"GetDisabledTemplates": () => [],
|
||||
"GetDisabledTechnologies": () => [],
|
||||
"TryReservePopulationSlots": () => false, // Always have pop space.
|
||||
"TrySubtractResources": () => true,
|
||||
"UnBlockTraining": () => {},
|
||||
"AddResources": () => {},
|
||||
"GetPlayerID": () => 1,
|
||||
// entitylimits
|
||||
"ChangeCount": () => {},
|
||||
"AllowedToTrain": () => true,
|
||||
// techmanager
|
||||
"CheckTechnologyRequirements": () => true,
|
||||
"IsTechnologyResearched": () => false,
|
||||
"IsInProgress": () => false
|
||||
}));
|
||||
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryOwnerInterface);
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"SetSelectionDirty": () => {}
|
||||
});
|
||||
|
||||
// Test Setup
|
||||
cmpProductionQueue.CalculateEntitiesMap();
|
||||
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, {});
|
||||
TS_ASSERT_EQUALS(cmpProductionQueue.GetQueue()[0].unitTemplate, "units/test/a");
|
||||
TS_ASSERT_EQUALS(cmpProductionQueue.GetQueue()[1].unitTemplate, "units/test/b");
|
||||
|
||||
// Add a modifier that replaces unit A with unit C,
|
||||
// adds a unit D and removes unit B from the roster.
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, val) => {
|
||||
return HandleTokens(val, "units/{civ}/a>units/{civ}/c units/{civ}/d -units/{civ}/b");
|
||||
});
|
||||
|
||||
cmpProductionQueue.OnValueModification({
|
||||
"component": "ProductionQueue",
|
||||
"valueNames": ["ProductionQueue/Entities/_string"],
|
||||
"entities": [ent]
|
||||
});
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpProductionQueue.GetEntitiesList(), ["units/test/c", "units/test/d"]
|
||||
);
|
||||
TS_ASSERT_EQUALS(cmpProductionQueue.GetQueue()[0].unitTemplate, "units/test/c");
|
||||
TS_ASSERT_EQUALS(cmpProductionQueue.GetQueue().length, 1);
|
||||
}
|
||||
|
||||
function test_auto_queue()
|
||||
{
|
||||
let playerEnt = 2;
|
||||
let playerID = 1;
|
||||
let testEntity = 3;
|
||||
|
||||
ConstructComponent(playerEnt, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 8
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"PushNotification": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Trigger, {
|
||||
"CallEvent": () => {}
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_Timer, {
|
||||
"SetInterval": (ent, iid, func) => 1
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({
|
||||
"Cost": {
|
||||
"BuildTime": 0,
|
||||
"Population": 1,
|
||||
"Resources": {}
|
||||
},
|
||||
"TrainingRestrictions": {
|
||||
"Category": "some_limit"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEnt
|
||||
});
|
||||
|
||||
AddMock(playerEnt, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetPlayerID": () => playerID,
|
||||
"GetTimeMultiplier": () => 0,
|
||||
"BlockTraining": () => {},
|
||||
"UnBlockTraining": () => {},
|
||||
"UnReservePopulationSlots": () => {},
|
||||
"TrySubtractResources": () => true,
|
||||
"TryReservePopulationSlots": () => false // Always have pop space.
|
||||
});
|
||||
|
||||
AddMock(testEntity, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
let cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", {
|
||||
"Entities": { "_string": "some_template" },
|
||||
"BatchTimeModifier": 1
|
||||
});
|
||||
|
||||
cmpProdQueue.EnableAutoQueue();
|
||||
|
||||
cmpProdQueue.AddItem("some_template", "unit", 3);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
|
||||
}
|
||||
|
||||
testEntitiesList();
|
||||
regression_test_d1879();
|
||||
test_batch_adding();
|
||||
test_batch_removal();
|
||||
test_auto_queue();
|
||||
test_token_changes();
|
||||
cmpProdQueue.ProgressTimeout(null, 0);
|
||||
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
id = cmpResearcher.QueueTechnology(queuedTech);
|
||||
TS_ASSERT_EQUALS(cmpResearcher.GetResearchingTechnology(id).progress, 0);
|
||||
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 500), 500);
|
||||
TS_ASSERT_EQUALS(cmpResearcher.GetResearchingTechnology(id).progress, 0.5);
|
||||
|
||||
spyTechManager = new Spy(techManager, "ResearchTechnology");
|
||||
TS_ASSERT_EQUALS(cmpResearcher.Progress(id, 1000), 500);
|
||||
TS_ASSERT_EQUALS(spyTechManager._called, 1);
|
||||
TS_ASSERT_EQUALS(cmpResearcher.queue.size, 0);
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
Engine.RegisterGlobal("Resources", {
|
||||
"BuildSchema": (a, b) => {}
|
||||
});
|
||||
Engine.LoadHelperScript("Player.js");
|
||||
Engine.LoadHelperScript("Sound.js");
|
||||
Engine.LoadComponentScript("interfaces/BuildRestrictions.js");
|
||||
Engine.LoadComponentScript("interfaces/EntityLimits.js");
|
||||
Engine.LoadComponentScript("interfaces/Foundation.js");
|
||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||
Engine.LoadComponentScript("interfaces/Trainer.js");
|
||||
Engine.LoadComponentScript("interfaces/TrainingRestrictions.js");
|
||||
Engine.LoadComponentScript("interfaces/Trigger.js");
|
||||
Engine.LoadComponentScript("EntityLimits.js");
|
||||
Engine.LoadComponentScript("Trainer.js");
|
||||
Engine.LoadComponentScript("TrainingRestrictions.js");
|
||||
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value);
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToTemplate", (_, value) => value);
|
||||
|
||||
const playerID = 1;
|
||||
const playerEntityID = 11;
|
||||
const entityID = 21;
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
|
||||
const cmpTrainer = ConstructComponent(entityID, "Trainer", {
|
||||
"Entities": { "_string": "units/{civ}/cavalry_javelineer_b " +
|
||||
"units/{civ}/infantry_swordsman_b " +
|
||||
"units/{native}/support_female_citizen" }
|
||||
});
|
||||
cmpTrainer.GetUpgradedTemplate = (template) => template;
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
"GetPlayerByID": id => playerEntityID
|
||||
});
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTemplates": () => ({}),
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
AddMock(entityID, IID_Ownership, {
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
AddMock(entityID, IID_Identity, {
|
||||
"GetCiv": () => "iber"
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": name => name == "units/iber/support_female_citizen",
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpTrainer.GetEntitiesList(), ["units/iber/support_female_citizen"]);
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({})
|
||||
});
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTemplates": () => ({ "units/athen/infantry_swordsman_b": true }),
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTemplates": () => ({ "units/iber/infantry_swordsman_b": true }),
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "athen",
|
||||
"GetDisabledTemplates": () => ({ "units/athen/infantry_swordsman_b": true }),
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(),
|
||||
["units/athen/cavalry_javelineer_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"GetCiv": () => "iber",
|
||||
"GetDisabledTemplates": () => ({ "units/iber/infantry_swordsman_b": false }),
|
||||
"GetPlayerID": () => playerID
|
||||
});
|
||||
|
||||
cmpTrainer.CalculateEntitiesMap();
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(),
|
||||
["units/iber/cavalry_javelineer_b", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
|
||||
);
|
||||
|
||||
|
||||
// Test Queuing a unit.
|
||||
const queuedUnit = "units/iber/infantry_swordsman_b";
|
||||
const cost = {
|
||||
"food": 10
|
||||
};
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true,
|
||||
"GetTemplate": name => ({
|
||||
"Cost": {
|
||||
"BuildTime": 1,
|
||||
"Population": 1,
|
||||
"Resources": cost
|
||||
},
|
||||
"TrainingRestrictions": {
|
||||
"Category": "some_limit",
|
||||
"MatchLimit": "7"
|
||||
}
|
||||
})
|
||||
});
|
||||
AddMock(SYSTEM_ENTITY, IID_Trigger, {
|
||||
"CallEvent": () => {}
|
||||
});
|
||||
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
|
||||
"PushNotification": () => {},
|
||||
"SetSelectionDirty": () => {}
|
||||
});
|
||||
|
||||
const cmpPlayer = AddMock(playerEntityID, IID_Player, {
|
||||
"BlockTraining": () => {},
|
||||
"GetCiv": () => "iber",
|
||||
"GetPlayerID": () => playerID,
|
||||
"RefundResources": (resources) => {
|
||||
TS_ASSERT_UNEVAL_EQUALS(resources, cost);
|
||||
},
|
||||
"TrySubtractResources": (resources) => {
|
||||
TS_ASSERT_UNEVAL_EQUALS(resources, cost);
|
||||
// Just have enough resources.
|
||||
return true;
|
||||
},
|
||||
"TryReservePopulationSlots": () => false, // Always have pop space.
|
||||
"UnReservePopulationSlots": () => {}, // Always have pop space.
|
||||
"UnBlockTraining": () => {},
|
||||
"GetDisabledTemplates": () => ({})
|
||||
});
|
||||
const spyCmpPlayerSubtract = new Spy(cmpPlayer, "TrySubtractResources");
|
||||
const spyCmpPlayerRefund = new Spy(cmpPlayer, "RefundResources");
|
||||
const spyCmpPlayerPop = new Spy(cmpPlayer, "TryReservePopulationSlots");
|
||||
|
||||
ConstructComponent(playerEntityID, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 0
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
// Test that we can't exceed the entity limit.
|
||||
TS_ASSERT_EQUALS(cmpTrainer.QueueBatch(queuedUnit, 1), -1);
|
||||
// And that in that case, the resources are not lost.
|
||||
// ToDo: This is a bad test, it relies on the order of subtraction in the cmp.
|
||||
// Better would it be to check the states before and after the queue.
|
||||
TS_ASSERT_EQUALS(spyCmpPlayerSubtract._called, spyCmpPlayerRefund._called);
|
||||
|
||||
ConstructComponent(playerEntityID, "EntityLimits", {
|
||||
"Limits": {
|
||||
"some_limit": 5
|
||||
},
|
||||
"LimitChangers": {},
|
||||
"LimitRemovers": {}
|
||||
});
|
||||
let id = cmpTrainer.QueueBatch(queuedUnit, 1);
|
||||
TS_ASSERT_EQUALS(spyCmpPlayerSubtract._called, 2);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.queue.size, 1);
|
||||
|
||||
|
||||
// Test removing a queued batch.
|
||||
cmpTrainer.StopBatch(id);
|
||||
TS_ASSERT_EQUALS(spyCmpPlayerRefund._called, 2);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.queue.size, 0);
|
||||
|
||||
const cmpEntLimits = QueryOwnerInterface(entityID, IID_EntityLimits);
|
||||
TS_ASSERT(cmpEntLimits.AllowedToTrain("some_limit", 5));
|
||||
|
||||
|
||||
// Test finishing a queued batch.
|
||||
id = cmpTrainer.QueueBatch(queuedUnit, 1);
|
||||
TS_ASSERT(cmpEntLimits.AllowedToTrain("some_limit", 4));
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id).progress, 0);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.Progress(id, 500), 500);
|
||||
TS_ASSERT_EQUALS(spyCmpPlayerPop._called, 1);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id).progress, 0.5);
|
||||
|
||||
const spawedEntityIDs = [4, 5, 6, 7, 8];
|
||||
let spawned = 0;
|
||||
|
||||
Engine.AddEntity = () => {
|
||||
const ent = spawedEntityIDs[spawned++];
|
||||
|
||||
ConstructComponent(ent, "TrainingRestrictions", {
|
||||
"Category": "some_limit"
|
||||
});
|
||||
|
||||
AddMock(ent, IID_Identity, {
|
||||
"GetClassesList": () => []
|
||||
});
|
||||
|
||||
AddMock(ent, IID_Position, {
|
||||
"JumpTo": () => {}
|
||||
});
|
||||
|
||||
AddMock(ent, IID_Ownership, {
|
||||
"SetOwner": (pid) => {
|
||||
QueryOwnerInterface(ent, IID_EntityLimits).OnGlobalOwnershipChanged({
|
||||
"entity": ent,
|
||||
"from": -1,
|
||||
"to": pid
|
||||
});
|
||||
},
|
||||
"GetOwner": () => playerID
|
||||
});
|
||||
|
||||
return ent;
|
||||
};
|
||||
AddMock(entityID, IID_Footprint, {
|
||||
"PickSpawnPoint": () => ({ "x": 0, "y": 1, "z": 0 })
|
||||
});
|
||||
|
||||
TS_ASSERT_EQUALS(cmpTrainer.Progress(id, 1000), 500);
|
||||
TS_ASSERT(!cmpTrainer.HasBatch(id));
|
||||
TS_ASSERT(!cmpEntLimits.AllowedToTrain("some_limit", 5));
|
||||
TS_ASSERT(cmpEntLimits.AllowedToTrain("some_limit", 4));
|
||||
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 1);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts()["units/iber/infantry_swordsman_b"], 1);
|
||||
|
||||
|
||||
// Now check that it doesn't get updated when the spawn doesn't succeed. (regression_test_d1879)
|
||||
cmpPlayer.TrySubtractResources = () => true;
|
||||
cmpPlayer.RefundResources = () => {};
|
||||
AddMock(entityID, IID_Footprint, {
|
||||
"PickSpawnPoint": () => ({ "x": -1, "y": -1, "z": -1 })
|
||||
});
|
||||
id = cmpTrainer.QueueBatch(queuedUnit, 2);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.Progress(id, 2000), 2000);
|
||||
TS_ASSERT(cmpTrainer.HasBatch(id));
|
||||
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 3);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts()["units/iber/infantry_swordsman_b"], 3);
|
||||
|
||||
// Check that when the batch is removed the counts are subtracted again.
|
||||
cmpTrainer.StopBatch(id);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 1);
|
||||
TS_ASSERT_EQUALS(cmpEntLimits.GetMatchCounts()["units/iber/infantry_swordsman_b"], 1);
|
||||
|
||||
const queuedSecondUnit = "units/iber/cavalry_javelineer_b";
|
||||
// Check changing the allowed entities has effect.
|
||||
const id1 = cmpTrainer.QueueBatch(queuedUnit, 1);
|
||||
const id2 = cmpTrainer.QueueBatch(queuedSecondUnit, 1);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.queue.size, 2);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id1).unitTemplate, queuedUnit);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id2).unitTemplate, queuedSecondUnit);
|
||||
|
||||
// Add a modifier that replaces unit A with unit C,
|
||||
// adds a unit D and removes unit B from the roster.
|
||||
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, val) => {
|
||||
return HandleTokens(val, "units/{civ}/cavalry_javelineer_b>units/{civ}/c units/{civ}/d -units/{civ}/infantry_swordsman_b");
|
||||
});
|
||||
|
||||
cmpTrainer.OnValueModification({
|
||||
"component": "Trainer",
|
||||
"valueNames": ["Trainer/Entities/_string"],
|
||||
"entities": [entityID]
|
||||
});
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(
|
||||
cmpTrainer.GetEntitiesList(), ["units/iber/c", "units/iber/support_female_citizen", "units/iber/d"]
|
||||
);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.queue.size, 1);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id1), undefined);
|
||||
TS_ASSERT_EQUALS(cmpTrainer.GetBatch(id2).unitTemplate, "units/iber/c");
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
"type": "global",
|
||||
"affects": ["Structure"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/time", "multiply": 0.85 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/time", "multiply": 0.85 }
|
||||
],
|
||||
"auraDescription": "Structures −15% technology resource costs and research time.",
|
||||
"auraName": "Centre of Scholarship"
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
"affects": ["Forge"],
|
||||
"affectedPlayers": ["MutualAlly"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.85 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/time", "multiply": 0.85 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.85 },
|
||||
{ "value": "Researcher/TechCostMultiplier/time", "multiply": 0.85 }
|
||||
],
|
||||
"auraName": "Products from Gaul",
|
||||
"auraDescription": "Forges −15% technology resource costs and research time."
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
{ "value": "Cost/BuildTime", "multiply": 0.5 },
|
||||
{ "value": "Cost/Resources/wood", "multiply": 0.5 },
|
||||
{ "value": "Cost/Resources/stone", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
],
|
||||
"auraName": "Ashoka's Religious Support",
|
||||
"auraDescription": "Temples −50% resource costs and building time; Temple technologies −50% resource costs and research time."
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
"type": "global",
|
||||
"affects": ["Economic"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
],
|
||||
"auraName": "Economic Fortune",
|
||||
"auraDescription": "Solon brought in a new system of weights and measures, fathers were encouraged to find trades for their sons.\nEconomic technologies −10% resource costs."
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
"type": "global",
|
||||
"affects": ["Structure"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
],
|
||||
"auraName": "Great Librarian",
|
||||
"auraDescription": "Continuing his predecessors' work on the Great Library of Alexandria, he seized every book brought to the city, thus leaving to his people a vast amount of hoarded wisdom.\nStructure technologies −10% resource costs."
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
{ "value": "Cost/Resources/wood", "multiply": 0.9 },
|
||||
{ "value": "Cost/Resources/stone", "multiply": 0.9 },
|
||||
{ "value": "Cost/Resources/metal", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.9 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.9 }
|
||||
],
|
||||
"auraName": "Founder of the Ezida Temple",
|
||||
"auraDescription": "Antiochus I laid the foundation for the Ezida Temple in Borsippa.\nTemples −10% resource costs; Temple technologies −10% resource costs."
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"affects": ["Ship"],
|
||||
"affectedPlayers": ["MutualAlly"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/BatchTimeModifier", "multiply": 0.7 },
|
||||
{ "value": "Trainer/BatchTimeModifier", "multiply": 0.7 },
|
||||
{ "value": "UnitMotion/WalkSpeed", "multiply": 1.5 }
|
||||
],
|
||||
"auraName": "Naval Commander",
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
{ "value": "Cost/BuildTime", "multiply": 0.5 },
|
||||
{ "value": "Cost/Resources/wood", "multiply": 0.5 },
|
||||
{ "value": "Cost/Resources/stone", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.5 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.5 },
|
||||
{ "value": "Researcher/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
],
|
||||
"auraDescription": "Temples −50% resource costs and build time. Temple technologies −50% resource costs and research time.",
|
||||
"auraName": "Buddhism",
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
"type": "garrison",
|
||||
"affects": ["Structure"],
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.8 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.8 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/stone", "multiply": 0.8 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/metal", "multiply": 0.8 },
|
||||
{ "value": "ProductionQueue/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
{ "value": "Researcher/TechCostMultiplier/food", "multiply": 0.8 },
|
||||
{ "value": "Researcher/TechCostMultiplier/wood", "multiply": 0.8 },
|
||||
{ "value": "Researcher/TechCostMultiplier/stone", "multiply": 0.8 },
|
||||
{ "value": "Researcher/TechCostMultiplier/metal", "multiply": 0.8 },
|
||||
{ "value": "Researcher/TechCostMultiplier/time", "multiply": 0.5 }
|
||||
],
|
||||
"auraDescription": "When garrisoned, the Structure's technologies have −20% resource cost and −50% research time.",
|
||||
"auraName": "Teacher",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"researchTime": 40,
|
||||
"tooltip": "Barracks −10% batch training time.",
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/BatchTimeModifier", "add": -0.1 }
|
||||
{ "value": "Trainer/BatchTimeModifier", "add": -0.1 }
|
||||
],
|
||||
"affects": ["Barracks"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
{
|
||||
"genericName": "Hoplite Tradition",
|
||||
"description": "Hoplite soldiers constituted most of the armies of Greece.",
|
||||
"cost": {
|
||||
"food": 400,
|
||||
"metal": 300
|
||||
},
|
||||
"requirements": {
|
||||
"all": [
|
||||
{ "tech": "phase_town" },
|
||||
{
|
||||
"any": [
|
||||
{ "civ": "athen" },
|
||||
{ "civ": "spart" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"requirementsTooltip": "Unlocked in Town Phase.",
|
||||
"icon": "armor_corinthian.png",
|
||||
"researchTime": 60,
|
||||
"tooltip": "Hoplites −25% training time and −50% promotion experience.",
|
||||
"modifications": [
|
||||
{ "value": "Cost/BuildTime", "multiply": 0.75 },
|
||||
{ "value": "Promotion/RequiredXp", "multiply": 0.5 }
|
||||
],
|
||||
"affects": ["Infantry Spearman !Hero"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
}
|
||||
{
|
||||
"genericName": "Hoplite Tradition",
|
||||
"description": "Hoplite soldiers constituted most of the armies of Greece.",
|
||||
"cost": {
|
||||
"food": 400,
|
||||
"metal": 300
|
||||
},
|
||||
"requirements": {
|
||||
"all": [
|
||||
{ "tech": "phase_town" },
|
||||
{
|
||||
"any": [
|
||||
{ "civ": "athen" },
|
||||
{ "civ": "spart" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"requirementsTooltip": "Unlocked in Town Phase.",
|
||||
"icon": "armor_corinthian.png",
|
||||
"researchTime": 60,
|
||||
"tooltip": "Hoplites −25% training time and −50% promotion experience.",
|
||||
"modifications": [
|
||||
{ "value": "Cost/BuildTime", "multiply": 0.75 },
|
||||
{ "value": "Promotion/RequiredXp", "multiply": 0.5 }
|
||||
],
|
||||
"affects": ["Infantry Spearman !Hero"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"researchTime": 40,
|
||||
"tooltip": "Stables −10% batch training time.",
|
||||
"modifications": [
|
||||
{ "value": "ProductionQueue/BatchTimeModifier", "add": -0.1 }
|
||||
{ "value": "Trainer/BatchTimeModifier", "add": -0.1 }
|
||||
],
|
||||
"affects": ["Stable"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ function Cheat(input)
|
|||
cmpPlayer.SetState("defeated", markForTranslation("%(player)s has been defeated (cheat)."));
|
||||
return;
|
||||
case "createunits":
|
||||
var cmpProductionQueue = input.selected.length && Engine.QueryInterface(input.selected[0], IID_ProductionQueue);
|
||||
if (!cmpProductionQueue)
|
||||
{
|
||||
const cmpTrainer = input.selected.length && Engine.QueryInterface(input.selected[0], IID_Trainer);
|
||||
if (!cmpTrainer)
|
||||
{
|
||||
cmpGuiInterface.PushNotification({
|
||||
"type": "text",
|
||||
|
|
@ -71,19 +72,18 @@ function Cheat(input)
|
|||
}
|
||||
|
||||
let owner = input.player;
|
||||
let cmpOwnership = Engine.QueryInterface(input.selected[0], IID_Ownership);
|
||||
const cmpOwnership = Engine.QueryInterface(input.selected[0], IID_Ownership);
|
||||
if (cmpOwnership)
|
||||
owner = cmpOwnership.GetOwner();
|
||||
for (let i = 0; i < Math.min(input.parameter, cmpPlayer.GetMaxPopulation() - cmpPlayer.GetPopulationCount()); ++i)
|
||||
cmpProductionQueue.SpawnUnits({
|
||||
"player": owner,
|
||||
"metadata": null,
|
||||
"entity": {
|
||||
"template": input.templates[i % input.templates.length],
|
||||
"count": 1
|
||||
}
|
||||
});
|
||||
{
|
||||
const batch = new cmpTrainer.Item(input.templates[i % input.templates.length], 1, input.selected[0], null);
|
||||
batch.player = owner;
|
||||
batch.Finish();
|
||||
// ToDo: If not able to spawn, cancel the batch.
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "fastactions":
|
||||
{
|
||||
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
|
||||
|
|
@ -95,7 +95,7 @@ function Cheat(input)
|
|||
"ResourceGatherer/BaseSpeed": [{ "affects": [["Structure"], ["Unit"]], "multiply": 1000 }],
|
||||
"Pack/Time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }],
|
||||
"Upgrade/Time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }],
|
||||
"ProductionQueue/TechCostMultiplier/time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }]
|
||||
"Researcher/TechCostMultiplier/time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }]
|
||||
}, playerEnt);
|
||||
return;
|
||||
}
|
||||
|
|
@ -121,6 +121,7 @@ function Cheat(input)
|
|||
Cheat({ "player": input.player, "action": "researchTechnology", "parameter": parameter, "selected": input.selected });
|
||||
return;
|
||||
case "researchTechnology":
|
||||
{
|
||||
if (!input.parameter.length)
|
||||
return;
|
||||
|
||||
|
|
@ -132,8 +133,8 @@ function Cheat(input)
|
|||
// check, if building is selected
|
||||
if (input.selected[0])
|
||||
{
|
||||
var cmpProductionQueue = Engine.QueryInterface(input.selected[0], IID_ProductionQueue);
|
||||
if (cmpProductionQueue)
|
||||
const cmpResearcher = Engine.QueryInterface(input.selected[0], IID_Researcher);
|
||||
if (cmpResearcher)
|
||||
{
|
||||
// try to spilt the input
|
||||
var tmp = input.parameter.split(/\s+/);
|
||||
|
|
@ -144,7 +145,7 @@ function Cheat(input)
|
|||
if (number || number === 0)
|
||||
{
|
||||
// get name of tech
|
||||
var techs = cmpProductionQueue.GetTechnologiesList();
|
||||
const techs = cmpResearcher.GetTechnologiesList();
|
||||
if (number > 0 && number <= techs.length)
|
||||
{
|
||||
var tech = techs[number-1];
|
||||
|
|
@ -167,6 +168,7 @@ function Cheat(input)
|
|||
!cmpTechnologyManager.IsTechnologyResearched(techname))
|
||||
cmpTechnologyManager.ResearchTechnology(techname);
|
||||
return;
|
||||
}
|
||||
case "metaCheat":
|
||||
for (let resource of Resources.GetCodes())
|
||||
Cheat({ "player": input.player, "action": "addresource", "text": resource, "parameter": input.parameter });
|
||||
|
|
|
|||
|
|
@ -353,26 +353,19 @@ var g_Commands = {
|
|||
continue;
|
||||
}
|
||||
|
||||
var queue = Engine.QueryInterface(ent, IID_ProductionQueue);
|
||||
const cmpTrainer = Engine.QueryInterface(ent, IID_Trainer);
|
||||
if (!cmpTrainer)
|
||||
continue;
|
||||
|
||||
let templateName = cmd.template;
|
||||
// Check if the building can train the unit
|
||||
// TODO: the AI API does not take promotion technologies into account for the list
|
||||
// of trainable units (taken directly from the unit template). Here is a temporary fix.
|
||||
if (queue && data.cmpPlayer.IsAI())
|
||||
{
|
||||
var list = queue.GetEntitiesList();
|
||||
if (list.indexOf(cmd.template) === -1 && cmd.promoted)
|
||||
{
|
||||
for (var promoted of cmd.promoted)
|
||||
{
|
||||
if (list.indexOf(promoted) === -1)
|
||||
continue;
|
||||
cmd.template = promoted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (queue && queue.GetEntitiesList().indexOf(cmd.template) != -1)
|
||||
queue.AddItem(cmd.template, "unit", +cmd.count, cmd.metadata, cmd.pushFront);
|
||||
if (data.cmpPlayer.IsAI())
|
||||
templateName = cmpTrainer.GetUpgradedTemplate(cmd.template);
|
||||
|
||||
if (cmpTrainer.CanTrain(templateName))
|
||||
Engine.QueryInterface(ent, IID_ProductionQueue)?.AddItem(templateName, "unit", +cmd.count, cmd.metadata, cmd.pushFront);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -26,18 +26,18 @@
|
|||
<Obstruction>
|
||||
<Static width="48.0" depth="48.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Entities datatype="tokens">
|
||||
-units/{civ}/support_female_citizen
|
||||
campaigns/army_mace_hero_alexander
|
||||
campaigns/army_mace_standard
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<TerritoryInfluence>
|
||||
<Root>true</Root>
|
||||
<Radius>150</Radius>
|
||||
<Weight>35000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
-units/{civ}/support_female_citizen
|
||||
campaigns/army_mace_hero_alexander
|
||||
campaigns/army_mace_standard
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>campaigns/structures/hellenes/settlement_curtainwall.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -26,19 +26,19 @@
|
|||
<Obstruction>
|
||||
<Static width="48.0" depth="48.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<TerritoryInfluence>
|
||||
<Root>true</Root>
|
||||
<Radius>300</Radius>
|
||||
<Weight>35000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
-units/{civ}/support_female_citizen
|
||||
campaigns/army_mace_hero_alexander
|
||||
campaigns/army_mace_standard
|
||||
units/{civ}/support_trader
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<TerritoryInfluence>
|
||||
<Root>true</Root>
|
||||
<Radius>300</Radius>
|
||||
<Weight>35000</Weight>
|
||||
</TerritoryInfluence>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>campaigns/structures/hellenes/settlement_curtainwall.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@
|
|||
<Static width="17.5" depth="30.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<TerritoryInfluence>
|
||||
<Root>true</Root>
|
||||
<Radius>100</Radius>
|
||||
<Weight>65535</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/temple.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -1,43 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity>
|
||||
<BuildRestrictions>
|
||||
<Territory>own neutral ally</Territory>
|
||||
</BuildRestrictions>
|
||||
<Footprint>
|
||||
<Square width="24.0" depth="25.0"/>
|
||||
<Height>8.0</Height>
|
||||
</Footprint>
|
||||
<Health disable=""/>
|
||||
<Identity>
|
||||
<VisibleClasses datatype="tokens">
|
||||
Shrine
|
||||
</VisibleClasses>
|
||||
<Undeletable>true</Undeletable>
|
||||
</Identity>
|
||||
<Obstruction>
|
||||
<Static width="20" depth="21"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Entities datatype="tokens">
|
||||
-units/{civ}/support_healer_b
|
||||
units/{native}/support_healer_e
|
||||
</Entities>
|
||||
<Technologies datatype="tokens">
|
||||
-heal_range
|
||||
-heal_range_2
|
||||
-heal_rate
|
||||
-heal_rate_2
|
||||
-garrison_heal
|
||||
-health_regen_units
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
<StatusBars>
|
||||
<HeightOffset>15.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Resistance replace=""/>
|
||||
<TerritoryDecay disable=""/>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>
|
||||
</VisualActor>
|
||||
</Entity>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity>
|
||||
<BuildRestrictions>
|
||||
<Territory>own neutral ally</Territory>
|
||||
</BuildRestrictions>
|
||||
<Footprint>
|
||||
<Square width="24.0" depth="25.0"/>
|
||||
<Height>8.0</Height>
|
||||
</Footprint>
|
||||
<Health disable=""/>
|
||||
<Identity>
|
||||
<VisibleClasses datatype="tokens">
|
||||
Shrine
|
||||
</VisibleClasses>
|
||||
<Undeletable>true</Undeletable>
|
||||
</Identity>
|
||||
<Obstruction>
|
||||
<Static width="20" depth="21"/>
|
||||
</Obstruction>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
-heal_range
|
||||
-heal_range_2
|
||||
-heal_rate
|
||||
-heal_rate_2
|
||||
-garrison_heal
|
||||
-health_regen_units
|
||||
</Technologies>
|
||||
</Researcher>
|
||||
<Resistance replace=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>15.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay disable=""/>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
-units/{civ}/support_healer_b
|
||||
units/{native}/support_healer_e
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>
|
||||
</VisualActor>
|
||||
</Entity>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity>
|
||||
<Capturable>
|
||||
<CapturePoints>500</CapturePoints>
|
||||
</Capturable>
|
||||
<Health disable=""/>
|
||||
<Identity>
|
||||
<GenericName>Trading Post</GenericName>
|
||||
<Undeletable>true</Undeletable>
|
||||
</Identity>
|
||||
<ProductionQueue>
|
||||
<Technologies datatype="tokens">
|
||||
-trader_health
|
||||
-trade_gain_01
|
||||
-trade_gain_02
|
||||
-trade_commercial_treaty
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
<Resistance replace=""/>
|
||||
<TerritoryDecay disable=""/>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>
|
||||
</VisualActor>
|
||||
</Entity>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity>
|
||||
<Capturable>
|
||||
<CapturePoints>500</CapturePoints>
|
||||
</Capturable>
|
||||
<Health disable=""/>
|
||||
<Identity>
|
||||
<GenericName>Trading Post</GenericName>
|
||||
<Undeletable>true</Undeletable>
|
||||
</Identity>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
-trader_health
|
||||
-trade_gain_01
|
||||
-trade_gain_02
|
||||
-trade_commercial_treaty
|
||||
</Technologies>
|
||||
</Researcher>
|
||||
<Resistance replace=""/>
|
||||
<TerritoryDecay disable=""/>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>
|
||||
</VisualActor>
|
||||
</Entity>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/arsenal</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/workshop.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/barracks</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/barracks.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/civil_centre</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/civil_centre.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
<Static width="18.0" depth="20.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/corral</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/corral.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/dock</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/dock.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/fortress</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/fortress.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
<Tooltip>Changes in a 10-pop house for civilisations with those houses, is deleted for other civs</Tooltip>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/house.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
<Static width="13.0" depth="12.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/ptolemies/house.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/market</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/market.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
<Static width="26.0" depth="26.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/range</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/range.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
<Static width="23.0" depth="23.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/stable</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/stable.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
<Civ>skirm</Civ>
|
||||
</Identity>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/temple</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/temple.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
<Static width="59.0" depth="63.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<SkirmishReplacer>
|
||||
<general>structures/{civ}/wonder</general>
|
||||
</SkirmishReplacer>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/hellenes/temple_epic.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
<Civ>athen</Civ>
|
||||
<SpecificName>Agora</SpecificName>
|
||||
</Identity>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_slinger_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/civil_centre.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
<Obstruction>
|
||||
<Static width="22.0" depth="26.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_marine_archer_b
|
||||
units/{civ}/champion_marine
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/athenians/dock.xml</Actor>
|
||||
<FoundationActor>structures/fndn_6x4_dock.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -33,19 +33,19 @@
|
|||
<Obstruction>
|
||||
<Static width="28.0" depth="28.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_infantry
|
||||
units/{civ}/champion_ranged
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_gymnasium.xml</select>
|
||||
<constructed>interface/complete/building/complete_gymnasium.xml</constructed>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Trainer>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_infantry
|
||||
units/{civ}/champion_ranged
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -33,18 +33,12 @@
|
|||
<Obstruction>
|
||||
<Static width="24.0" depth="30.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_themistocles
|
||||
units/{civ}/hero_pericles
|
||||
units/{civ}/hero_iphicrates
|
||||
</Entities>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
long_walls
|
||||
iphicratean_reforms
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
</Researcher>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_tholos.xml</select>
|
||||
|
|
@ -56,6 +50,14 @@
|
|||
<Radius>38</Radius>
|
||||
<Weight>40000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_themistocles
|
||||
units/{civ}/hero_pericles
|
||||
units/{civ}/hero_iphicrates
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
<Static width="1.5" depth="4.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>props/special/eyecandy/bench_1.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
<Obstruction>
|
||||
<Static width="25.0" depth="25.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_slinger_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/britons/civic_centre.xml</Actor>
|
||||
<FoundationActor>structures/fndn_7x8.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,16 @@
|
|||
<Floating>true</Floating>
|
||||
<FloatDepth>0.0</FloatDepth>
|
||||
</Position>
|
||||
<ProductionQueue>
|
||||
<RallyPointRenderer>
|
||||
<LinePassabilityClass>ship</LinePassabilityClass>
|
||||
</RallyPointRenderer>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
-phase_town_{civ}
|
||||
-hellenistic_metropolis
|
||||
</Technologies>
|
||||
</Researcher>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_slinger_b
|
||||
|
|
@ -35,14 +44,7 @@
|
|||
units/{civ}/ship_bireme
|
||||
units/{civ}/ship_trireme
|
||||
</Entities>
|
||||
<Technologies datatype="tokens">
|
||||
-phase_town_{civ}
|
||||
-hellenistic_metropolis
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
<RallyPointRenderer>
|
||||
<LinePassabilityClass>ship</LinePassabilityClass>
|
||||
</RallyPointRenderer>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/britons/crannog.xml</Actor>
|
||||
<FoundationActor>structures/fndn_8x8.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@
|
|||
<Obstruction>
|
||||
<Static width="29.0" depth="29.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_boudicca
|
||||
units/{civ}/hero_caratacos
|
||||
units/{civ}/hero_cunobelin
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_broch.xml</select>
|
||||
<constructed>interface/complete/building/complete_broch.xml</constructed>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_boudicca
|
||||
units/{civ}/hero_caratacos
|
||||
units/{civ}/hero_cunobelin
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/britons/fortress.xml</Actor>
|
||||
<FoundationActor>structures/fndn_9x9.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@
|
|||
<Civ>cart</Civ>
|
||||
<SpecificName>Merkāz</SpecificName>
|
||||
</Identity>
|
||||
<ProductionQueue>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
colonization
|
||||
</Technologies>
|
||||
</Researcher>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_archer_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
<Technologies datatype="tokens">
|
||||
colonization
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/civil_centre.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
<Obstruction>
|
||||
<Static width="16.0" depth="14.5"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
-gaia/fauna_cattle_cow_trainable
|
||||
gaia/fauna_cattle_sanga_trainable
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/corral.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<Obstruction>
|
||||
<Static width="28.0" depth="28.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_swordsman_gaul_b
|
||||
units/{native}/cavalry_swordsman_gaul_b
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
units/{native}/infantry_swordsman_ital_b
|
||||
units/{native}/cavalry_spearman_ital_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/embassy.xml</Actor>
|
||||
<FoundationActor>structures/fndn_8x8.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@
|
|||
<Obstruction>
|
||||
<Static width="15.0" depth="12.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_swordsman_gaul_b
|
||||
units/{native}/cavalry_swordsman_gaul_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/embassy_celtic.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@
|
|||
<Obstruction>
|
||||
<Static width="16.0" depth="16.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_javelineer_iber_b
|
||||
units/{native}/infantry_slinger_iber_b
|
||||
units/{native}/cavalry_swordsman_iber_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/embassy_iberian.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@
|
|||
<Obstruction>
|
||||
<Static width="11.0" depth="14.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_swordsman_ital_b
|
||||
units/{native}/cavalry_spearman_ital_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/embassy_italic.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
<Obstruction>
|
||||
<Static width="26.0" depth="28.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_hamilcar
|
||||
units/{civ}/hero_hannibal
|
||||
units/{civ}/hero_maharbal
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<FoundationActor>structures/carthaginians/fndn_fortress.xml</FoundationActor>
|
||||
<Actor>structures/carthaginians/fortress.xml</Actor>
|
||||
|
|
|
|||
|
|
@ -54,14 +54,6 @@
|
|||
<Floating>true</Floating>
|
||||
<FloatDepth>0.0</FloatDepth>
|
||||
</Position>
|
||||
<ProductionQueue>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/ship_bireme
|
||||
units/{civ}/ship_trireme
|
||||
units/{civ}/ship_quinquereme
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<RallyPointRenderer>
|
||||
<LinePassabilityClass>ship</LinePassabilityClass>
|
||||
</RallyPointRenderer>
|
||||
|
|
@ -84,6 +76,14 @@
|
|||
<Radius>200</Radius>
|
||||
<Weight>25000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/ship_bireme
|
||||
units/{civ}/ship_trireme
|
||||
units/{civ}/ship_quinquereme
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<Vision>
|
||||
<Range>100</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@
|
|||
<Obstruction>
|
||||
<Static width="17.0" depth="30.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_infantry
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/carthaginians/temple_big.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@
|
|||
<Bonus>2</Bonus>
|
||||
</Population>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>7.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/celts/hut.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
<Bonus>10</Bonus>
|
||||
</Population>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/celts/longhouse.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
<Static width="2.0" depth="2.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>8.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>props/special/eyecandy/column_doric.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
<Static width="2.0" depth="12.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>props/special/eyecandy/column_doric_fallen.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
<Static width="2.0" depth="2.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>props/special/eyecandy/column_doric_fallen_b.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@
|
|||
<Static width="1.5" depth="13.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>temp/fence_wood_long_a.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,12 @@
|
|||
<Static width="1.5" depth="6.5"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<Visibility>
|
||||
<RetainInFog>true</RetainInFog>
|
||||
</Visibility>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,12 @@
|
|||
<Static width="1.5" depth="10.5"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<StatusBars>
|
||||
<HeightOffset>6.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>props/special/eyecandy/fence_stone_medit.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -38,15 +38,6 @@
|
|||
<Obstruction>
|
||||
<Static width="25.0" depth="25.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_infantry_trumpeter
|
||||
units/{civ}/hero_brennus
|
||||
units/{civ}/hero_viridomarus
|
||||
units/{civ}/hero_vercingetorix
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<Resistance>
|
||||
<Entity>
|
||||
<Damage>
|
||||
|
|
@ -67,6 +58,15 @@
|
|||
<Radius>40</Radius>
|
||||
<Weight>40000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer>
|
||||
<BatchTimeModifier>0.7</BatchTimeModifier>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_infantry_trumpeter
|
||||
units/{civ}/hero_brennus
|
||||
units/{civ}/hero_viridomarus
|
||||
units/{civ}/hero_vercingetorix
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
<Obstruction>
|
||||
<Static width="25.0" depth="25.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_javelineer_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/gauls/civic_centre.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@
|
|||
<Obstruction>
|
||||
<Static width="22.5" depth="22.5"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/champion_fanatic
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/celts/temple.xml</Actor>
|
||||
<FoundationActor>structures/fndn_7x7.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -32,10 +32,12 @@
|
|||
<Static width="27.0" depth="57.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<TerritoryInfluence>
|
||||
<Radius>60</Radius>
|
||||
<Weight>65535</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>80</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@
|
|||
<Static width="26.0" depth="30.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<TerritoryInfluence>
|
||||
<Root>false</Root>
|
||||
<Radius>40</Radius>
|
||||
<Weight>65535</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>20</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -33,11 +33,13 @@
|
|||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<RallyPoint disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<TerritoryInfluence>
|
||||
<Root>false</Root>
|
||||
<Radius>36</Radius>
|
||||
<Weight>65535</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
<Civ>iber</Civ>
|
||||
<SpecificName>Oppidum</SpecificName>
|
||||
</Identity>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_swordsman_b
|
||||
units/{civ}/infantry_javelineer_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/iberians/civic_center.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@
|
|||
<Obstruction>
|
||||
<Static width="27.0" depth="27.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_caros
|
||||
units/{civ}/hero_indibil
|
||||
units/{civ}/hero_viriato
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/iberians/fortress.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<RallyPoint disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Resistance>
|
||||
<Entity>
|
||||
<Damage>
|
||||
|
|
@ -60,6 +61,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/iberians/sb_1.xml</Actor>
|
||||
<FoundationActor>structures/fndn_2x2.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
</Obstructions>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_broch.xml</select>
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
<Radius>38</Radius>
|
||||
<Weight>40000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -31,15 +31,15 @@
|
|||
<Obstruction>
|
||||
<Static width="29.0" depth="29.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/cavalry_javelineer_merc_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<TerritoryDecay>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/cavalry_javelineer_merc_b
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/mercenaries/camp_blemmye.xml</Actor>
|
||||
<FoundationActor>structures/fndn_8x7.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -31,16 +31,16 @@
|
|||
<Obstruction>
|
||||
<Static width="35.0" depth="35.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_maceman_merc_b
|
||||
units/{native}/infantry_javelineer_merc_b
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
<TerritoryDecay>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{native}/infantry_maceman_merc_b
|
||||
units/{native}/infantry_javelineer_merc_b
|
||||
</Entities>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/mercenaries/camp_nuba.xml</Actor>
|
||||
<FoundationActor>structures/fndn_8x8.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -11,16 +11,18 @@
|
|||
<Obstruction>
|
||||
<Static width="31.0" depth="31"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Researcher>
|
||||
<Technologies datatype="tokens">
|
||||
architecture_kush
|
||||
</Technologies>
|
||||
</Researcher>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/infantry_spearman_b
|
||||
units/{civ}/infantry_archer_b
|
||||
units/{civ}/cavalry_javelineer_b
|
||||
</Entities>
|
||||
<Technologies datatype="tokens">
|
||||
architecture_kush
|
||||
</Technologies>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/kushites/civic_centre_kush.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@
|
|||
<Obstruction>
|
||||
<Static width="12.0" depth="14.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
-gaia/fauna_cattle_cow_trainable
|
||||
gaia/fauna_cattle_sanga_trainable
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/kushites/corral.xml</Actor>
|
||||
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
<Obstruction>
|
||||
<Static width="28.0" depth="28.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue>
|
||||
<Trainer>
|
||||
<Entities datatype="tokens">
|
||||
units/{civ}/hero_nastasen
|
||||
units/{civ}/hero_amanirenas
|
||||
units/{civ}/hero_arakamani
|
||||
</Entities>
|
||||
</ProductionQueue>
|
||||
</Trainer>
|
||||
<VisualActor>
|
||||
<Actor>structures/kushites/fortress.xml</Actor>
|
||||
</VisualActor>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
<Static width="20.0" depth="24.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_temple_10.xml</select>
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
<Radius>40</Radius>
|
||||
<Weight>40000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>40</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
<Static width="16.0" depth="16.0"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Sound>
|
||||
<SoundGroups>
|
||||
<select>interface/select/building/sel_temple_10.xml</select>
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
<Radius>30</Radius>
|
||||
<Weight>30000</Weight>
|
||||
</TerritoryInfluence>
|
||||
<Trainer disable=""/>
|
||||
<Vision>
|
||||
<Range>30</Range>
|
||||
</Vision>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
<Static width="20" depth="21"/>
|
||||
</Obstruction>
|
||||
<ProductionQueue disable=""/>
|
||||
<Researcher disable=""/>
|
||||
<Trainer disable=""/>
|
||||
<VisualActor>
|
||||
<Actor>structures/kushites/shrine.xml</Actor>
|
||||
<FoundationActor>structures/fndn_6x6.xml</FoundationActor>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue