Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js +++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js @@ -471,11 +471,11 @@ } } - 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) Index: ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLister.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLister.js +++ ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLister.js @@ -123,7 +123,7 @@ 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) Index: ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLoader.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLoader.js +++ ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateLoader.js @@ -137,36 +137,36 @@ }; } - 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"); } Index: ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateParser.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateParser.js +++ ps/trunk/binaries/data/mods/public/gui/reference/common/TemplateParser.js @@ -65,7 +65,7 @@ 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); Index: ps/trunk/binaries/data/mods/public/gui/session/input.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/input.js +++ ps/trunk/binaries/data/mods/public/gui/session/input.js @@ -1402,9 +1402,9 @@ 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); }); } Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js @@ -627,10 +627,10 @@ { 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 @@ 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 @@ 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 }))); Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -558,7 +558,7 @@ "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 @@ "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; }) }); Index: ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js +++ ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js @@ -1058,8 +1058,8 @@ // 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 @@ { // 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 @@ "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 @@ "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") + Index: ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js +++ ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js @@ -191,8 +191,8 @@ 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 Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -119,7 +119,7 @@ }, "techCostMultiplier": function(type) { - return +(this.get("ProductionQueue/TechCostMultiplier/"+type) || 1); + return +(this.get("Researcher/TechCostMultiplier/"+type) || 1); }, /** @@ -340,14 +340,14 @@ }, "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 @@ "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 @@ 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 @@ "template": type, "count": count, "metadata": metadata, - "promoted": promotedTypes, "pushFront": pushFront }); return this; Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js @@ -65,7 +65,7 @@ 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 @@ 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; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanTraining.js @@ -124,7 +124,7 @@ 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 @@ 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 { Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -340,6 +340,13 @@ "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 @@ 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 @@ 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.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) Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Player.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js @@ -390,6 +390,16 @@ return true; }; +Player.prototype.RefundResources = function(amounts) +{ + const cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker); + if (cmpStatisticsTracker) + for (const type in amounts) + cmpStatisticsTracker.IncreaseResourceUsedCounter(type, -amounts[type]); + + this.AddResources(amounts); +}; + Player.prototype.GetNextTradingGoods = function() { let value = randFloat(0, 100); Index: ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js +++ ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -1,338 +1,262 @@ function ProductionQueue() {} ProductionQueue.prototype.Schema = - "Allows the building to train new units and research technologies" + - "" + - "0.7" + - "" + - "\n units/{civ}/support_female_citizen\n units/{native}/support_trader\n units/athen/infantry_spearman_b\n " + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "tokens" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "tokens" + - "" + - "" + - "" + - "" + - "" + - Resources.BuildSchema("nonNegativeDecimal", ["time"]) + - ""; + "Helps the building to train new units and research technologies." + + ""; ProductionQueue.prototype.ProgressInterval = 1000; ProductionQueue.prototype.MaxQueueSize = 16; -ProductionQueue.prototype.Init = function() +/** + * This object represents an item in the queue. + */ +ProductionQueue.prototype.Item = function() {}; + +/** + * @param {number} producer - The entity ID of our producer. + * @param {string} metadata - Optionally any metadata attached to us. + */ +ProductionQueue.prototype.Item.prototype.Init = function(producer, metadata) { - this.nextID = 1; + this.producer = producer; + this.metadata = metadata; - this.queue = []; - /** - Queue items are: - { - "id": 1, - "player": 1, // Who paid for this batch; we need this to cope with refunds cleanly. - "productionStarted": false, // true iff production has started (we have reserved population). - "timeTotal": 15000, // msecs - "timeRemaining": 10000, // msecs - "paused": false, // Whether the item is currently paused (e.g. not the first item or parent is garrisoned). - "resources": { "wood": 100, ... }, // Total resources of the item when queued. - "entity": { - "template": "units/example", - "count": 10, - "neededSlots": 3, // Number of population slots missing for production to begin. - "population": 1, // Population per unit, multiply by count to get total. - "resources": { "wood": 100, ... }, // Resources per entity, multiply by count to get total. - "entityCache": [189, ...], // The entities created but not spawned yet. - }, - "technology": { - "template": "example_tech", - "resources": { "wood": 100, ... }, - } - } - */ }; -/* - * Returns list of entities that can be trained by this building. - */ -ProductionQueue.prototype.GetEntitiesList = function() +ProductionQueue.prototype.Item.prototype.QueueEntity = function(templateName, count) +{ + const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer); + if (!cmpTrainer) + return false; + this.entity = cmpTrainer.QueueBatch(templateName, count, this.metadata); + if (this.entity == -1) + return false; + this.originalItem = { + "templateName": templateName, + "count": count, + "metadata": this.metadata + }; + + return true; +}; + +ProductionQueue.prototype.Item.prototype.QueueTechnology = function(templateName) { - return Array.from(this.entitiesMap.values()); + const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher); + if (!cmpResearcher) + return false; + this.technology = cmpResearcher.QueueTechnology(templateName, this.metadata); + if (this.technology == -1) + return false; + + return true; }; /** - * @return {boolean} - Whether we are automatically queuing items. + * @param {number} id - The id of this item. */ -ProductionQueue.prototype.IsAutoQueueing = function() +ProductionQueue.prototype.Item.prototype.SetID = function(id) { - return !!this.autoqueuing; + this.id = id; +}; + +ProductionQueue.prototype.Item.prototype.Stop = function() +{ + if (this.entity) + { + const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer); + if (cmpTrainer) + cmpTrainer.StopBatch(this.entity); + } + + if (this.technology) + { + const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher); + if (cmpResearcher) + cmpResearcher.StopResearching(this.technology); + } }; /** - * Turn on Auto-Queue. + * Called when the first work is performed. */ -ProductionQueue.prototype.EnableAutoQueue = function() +ProductionQueue.prototype.Item.prototype.Start = function() { - this.autoqueuing = true; + this.started = true; +}; + +ProductionQueue.prototype.Item.prototype.IsStarted = function() +{ + return !!this.started; }; /** - * Turn off Auto-Queue. + * @return {boolean} - Whether this item is finished. */ -ProductionQueue.prototype.DisableAutoQueue = function() +ProductionQueue.prototype.Item.prototype.IsFinished = function() { - delete this.autoqueuing; + return !!this.finished; }; /** - * Calculate the new list of producible entities - * and update any entities currently being produced. + * @param {number} allocatedTime - The time allocated to this item. + * @return {number} - The time used for this item. */ -ProductionQueue.prototype.CalculateEntitiesMap = function() +ProductionQueue.prototype.Item.prototype.Progress = function(allocatedTime) { - // Don't reset the map, it's used below to update entities. - if (!this.entitiesMap) - this.entitiesMap = new Map(); - if (!this.template.Entities) - return; - - let string = this.template.Entities._string; - // Tokens can be added -> process an empty list to get them. - let addedTokens = ApplyValueModificationsToEntity("ProductionQueue/Entities/_string", "", this.entity); - if (!addedTokens && !string) - return; - - addedTokens = addedTokens == "" ? [] : addedTokens.split(/\s+/); + if (this.entity) + { + const cmpTrainer = Engine.QueryInterface(this.producer, IID_Trainer); + allocatedTime -= cmpTrainer.Progress(this.entity, allocatedTime); + if (!cmpTrainer.HasBatch(this.entity)) + delete this.entity; + } + if (this.technology) + { + const cmpResearcher = Engine.QueryInterface(this.producer, IID_Researcher); + allocatedTime -= cmpResearcher.Progress(this.technology, allocatedTime); + if (!cmpResearcher.HasItem(this.technology)) + delete this.technology; + } + if (!this.entity && !this.technology) + this.finished = true; - let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - let cmpPlayer = QueryOwnerInterface(this.entity); - let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); - - let 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). - */ - - let removeAllQueuedTemplate = (token) => { - let queue = clone(this.queue); - let template = this.entitiesMap.get(token); - for (let item of queue) - if (item.entity?.template && item.entity.template === template) - this.RemoveItem(item.id); - }; - let updateAllQueuedTemplate = (token, updateTo) => { - let template = this.entitiesMap.get(token); - for (let item of this.queue) - if (item.entity?.template && item.entity.template === template) - item.entity.template = updateTo; - }; + return allocatedTime; +}; - let toks = string.split(/\s+/); - for (let tok of addedTokens) - toks.push(tok); - - let addedDict = addedTokens.reduce((out, token) => { out[token] = true; return out; }, {}); - this.entitiesMap = toks.reduce((entMap, token) => { - let 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("ProductionQueue/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 (cmpIdentity) - token = token.replace(/\{native\}/g, cmpIdentity.GetCiv()); - if (cmpPlayer) - token = token.replace(/\{civ\}/g, cmpPlayer.GetCiv()); +ProductionQueue.prototype.Item.prototype.Pause = function() +{ + this.paused = true; + if (this.entity) + Engine.QueryInterface(this.producer, IID_Trainer).PauseBatch(this.entity); + if (this.technology) + Engine.QueryInterface(this.producer, IID_Researcher).PauseTechnology(this.technology); +}; - // Filter out disabled and invalid entities. - if (disabledEntities[token] || !cmpTemplateManager.TemplateExists(token)) - { - removeAllQueuedTemplate(rawToken); - return entMap; - } +ProductionQueue.prototype.Item.prototype.Unpause = function() +{ + delete this.paused; + if (this.entity) + Engine.QueryInterface(this.producer, IID_Trainer).UnpauseBatch(this.entity); + if (this.technology) + Engine.QueryInterface(this.producer, IID_Researcher).UnpauseTechnology(this.technology); +}; - token = this.GetUpgradedTemplate(token); - entMap.set(rawToken, token); - updateAllQueuedTemplate(rawToken, token); - return entMap; - }, new Map()); +ProductionQueue.prototype.Item.prototype.IsPaused = function() +{ + return !!this.paused; }; -/* - * Returns the upgraded template name if necessary. +/** + * @return {Object} - Some basic information of this item. */ -ProductionQueue.prototype.GetUpgradedTemplate = function(templateName) +ProductionQueue.prototype.Item.prototype.GetBasicInfo = function() { - let cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return templateName; - - let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - let template = cmpTemplateManager.GetTemplate(templateName); - while (template && template.Promotion !== undefined) - { - let requiredXp = ApplyValueModificationsToTemplate( - "Promotion/RequiredXp", - +template.Promotion.RequiredXp, - cmpPlayer.GetPlayerID(), - template); - if (requiredXp > 0) - break; - templateName = template.Promotion.Entity; - template = cmpTemplateManager.GetTemplate(templateName); - } - return templateName; + let result; + if (this.technology) + result = Engine.QueryInterface(this.producer, IID_Researcher).GetResearchingTechnology(this.technology); + else if (this.entity) + result = Engine.QueryInterface(this.producer, IID_Trainer).GetBatch(this.entity); + result.id = this.id; + result.paused = this.paused; + return result; }; -/* - * Returns list of technologies that can be researched by this building. +/** + * @return {Object} - The originally queued item. */ -ProductionQueue.prototype.GetTechnologiesList = function() +ProductionQueue.prototype.Item.prototype.OriginalItem = function() { - if (!this.template.Technologies) - return []; - - let string = this.template.Technologies._string; - string = ApplyValueModificationsToEntity("ProductionQueue/Technologies/_string", string, this.entity); - - if (!string) - return []; - - let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); - if (!cmpTechnologyManager) - return []; - - let cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return []; - - let techs = string.split(/\s+/); - - // Replace the civ specific technologies. - for (let i = 0; i < techs.length; ++i) - { - let tech = techs[i]; - if (tech.indexOf("{civ}") == -1) - continue; - let civTech = tech.replace("{civ}", cmpPlayer.GetCiv()); - techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic"); - } + return this.originalItem; +}; - // Remove any technologies that can't be researched by this civ. - techs = techs.filter(tech => - cmpTechnologyManager.CheckTechnologyRequirements( - DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), cmpPlayer.GetCiv()), - true)); +ProductionQueue.prototype.Item.prototype.Serialize = function() +{ + return { + "id": this.id, + "metadata": this.metadata, + "paused": this.paused, + "producer": this.producer, + "entity": this.entity, + "technology": this.technology, + "started": this.started, + "originalItem": this.originalItem + }; +}; - let techList = []; - // Stores the tech which supersedes the key. - let superseded = {}; +ProductionQueue.prototype.Item.prototype.Deserialize = function(data) +{ + this.Init(data.producer, data.metadata); - let disabledTechnologies = cmpPlayer.GetDisabledTechnologies(); + this.id = data.id; + this.paused = data.paused; + this.entity = data.entity; + this.technology = data.technology; + this.started = data.started; + this.originalItem = data.originalItem; +}; - // Add any top level technologies to an array which corresponds to the displayed icons. - // Also store what technology is superseded in the superseded object { "tech1":"techWhichSupercedesTech1", ... }. - for (let tech of techs) - { - if (disabledTechnologies && disabledTechnologies[tech]) - continue; +ProductionQueue.prototype.Init = function() +{ + this.nextID = 1; - let template = TechnologyTemplates.Get(tech); - if (!template.supersedes || techs.indexOf(template.supersedes) === -1) - techList.push(tech); - else - superseded[template.supersedes] = tech; - } + this.queue = []; +}; - // Now make researched/in progress techs invisible. - for (let i in techList) - { - let tech = techList[i]; - while (this.IsTechnologyResearchedOrInProgress(tech)) - tech = superseded[tech]; +ProductionQueue.prototype.Serialize = function() +{ + const queue = []; + for (const item of this.queue) + queue.push(item.Serialize()); + + return { + "autoqueuing": this.autoqueuing, + "nextID": this.nextID, + "paused": this.paused, + "timer": this.timer, + "queue": queue + }; +}; - techList[i] = tech; - } +ProductionQueue.prototype.Deserialize = function(data) +{ + this.Init(); - let ret = []; + this.autoqueuing = data.autoqueuing; + this.nextID = data.nextID; + this.paused = data.paused; + this.timer = data.timer; - // This inserts the techs into the correct positions to line up the technology pairs. - for (let i = 0; i < techList.length; ++i) + for (const item of data.queue) { - let tech = techList[i]; - if (!tech) - { - ret[i] = undefined; - continue; - } - - let template = TechnologyTemplates.Get(tech); - if (template.top) - ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom }; - else - ret[i] = tech; + const newItem = new this.Item(); + newItem.Deserialize(item); + this.queue.push(newItem); } - - return ret; }; -ProductionQueue.prototype.GetTechCostMultiplier = function() +/** + * @return {boolean} - Whether we are automatically queuing items. + */ +ProductionQueue.prototype.IsAutoQueueing = function() { - let techCostMultiplier = {}; - for (let res in this.template.TechCostMultiplier) - techCostMultiplier[res] = ApplyValueModificationsToEntity( - "ProductionQueue/TechCostMultiplier/" + res, - +this.template.TechCostMultiplier[res], - this.entity); - - return techCostMultiplier; + return !!this.autoqueuing; }; -ProductionQueue.prototype.IsTechnologyResearchedOrInProgress = function(tech) +/** + * Turn on Auto-Queue. + */ +ProductionQueue.prototype.EnableAutoQueue = function() { - if (!tech) - return false; - - let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); - if (!cmpTechnologyManager) - return false; - - let template = TechnologyTemplates.Get(tech); - if (template.top) - return cmpTechnologyManager.IsTechnologyResearched(template.top) || - cmpTechnologyManager.IsInProgress(template.top) || - cmpTechnologyManager.IsTechnologyResearched(template.bottom) || - cmpTechnologyManager.IsInProgress(template.bottom); + this.autoqueuing = true; +}; - return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech); +/** + * Turn off Auto-Queue. + */ +ProductionQueue.prototype.DisableAutoQueue = function() +{ + delete this.autoqueuing; }; /* @@ -349,14 +273,14 @@ { // TODO: there should be a way for the GUI to determine whether it's going // to be possible to add a batch (based on resource costs and length limits). - let cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return false; - let player = cmpPlayer.GetPlayerID(); if (!this.queue.length) { - let cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade); + const cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return false; + const player = cmpPlayer.GetPlayerID(); + const cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade); if (cmpUpgrade && cmpUpgrade.IsUpgrading()) { let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); @@ -370,7 +294,11 @@ } else if (this.queue.length >= this.MaxQueueSize) { - let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + const cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return false; + const player = cmpPlayer.GetPlayerID(); + const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "players": [player], "message": markForTranslation("The production queue is full."), @@ -379,108 +307,17 @@ return false; } - const item = { - "player": player, - "metadata": metadata, - "productionStarted": false, - "resources": {}, // The total resource costs. - "paused": false - }; - - // ToDo: Still some duplication here, some can might be combined, - // but requires some more refactoring. + const item = new this.Item(); + item.Init(this.entity, metadata); if (type == "unit") { - if (!Number.isInteger(count) || count <= 0) - { - error("Invalid batch count " + count); - return false; - } - - let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - let template = cmpTemplateManager.GetTemplate(this.GetUpgradedTemplate(templateName)); - if (!template) + if (!item.QueueEntity(templateName, count)) return false; - - item.entity = { - "template": templateName, - "count": count, - "population": ApplyValueModificationsToTemplate( - "Cost/Population", - +template.Cost.Population, - player, - template), - "resources": {}, // The resource costs per entity. - }; - - for (let res in template.Cost.Resources) - { - item.entity.resources[res] = ApplyValueModificationsToTemplate( - "Cost/Resources/" + res, - +template.Cost.Resources[res], - player, - template); - - item.resources[res] = Math.floor(count * item.entity.resources[res]); - } - - if (template.TrainingRestrictions) - { - let unitCategory = template.TrainingRestrictions.Category; - let cmpPlayerEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits); - if (cmpPlayerEntityLimits) - { - if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, count, templateName, template.TrainingRestrictions.MatchLimit)) - // Already warned, return. - return false; - cmpPlayerEntityLimits.ChangeCount(unitCategory, count); - if (template.TrainingRestrictions.MatchLimit) - cmpPlayerEntityLimits.ChangeMatchCount(templateName, count); - } - } - - const buildTime = ApplyValueModificationsToTemplate( - "Cost/BuildTime", - +template.Cost.BuildTime, - player, - template); - const time = this.GetBatchTime(count) * buildTime * 1000; - item.timeTotal = time; - item.timeRemaining = time; } else if (type == "technology") { - if (!TechnologyTemplates.Has(templateName)) - return false; - - if (!this.GetTechnologiesList().some(tech => - tech && - (tech == templateName || - tech.pair && - (tech.top == templateName || tech.bottom == templateName)))) - { - error("This entity cannot research " + templateName); + if (!item.QueueTechnology(templateName)) return false; - } - - item.technology = { - "template": templateName, - "resources": {} - }; - - let template = TechnologyTemplates.Get(templateName); - let techCostMultiplier = this.GetTechCostMultiplier(); - - if (template.cost) - for (const res in template.cost) - { - item.technology.resources[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]); - item.resources[res] = item.technology.resources[res]; - } - - const time = techCostMultiplier.time * (template.researchTime || 0) * 1000; - item.timeTotal = time; - item.timeRemaining = time; } else { @@ -488,43 +325,15 @@ return false; } - // TrySubtractResources should report error to player (they ran out of resources). - if (!cmpPlayer.TrySubtractResources(item.resources)) - return false; - - item.id = this.nextID++; + item.SetID(this.nextID++); if (pushFront) { - if (this.queue[0]) - this.queue[0].paused = true; + this.queue[0]?.Pause(); this.queue.unshift(item); } else this.queue.push(item); - const cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); - if (item.entity) - cmpTrigger.CallEvent("OnTrainingQueued", { - "playerid": player, - "unitTemplate": item.entity.template, - "count": count, - "metadata": metadata, - "trainerEntity": this.entity - }); - if (item.technology) - { - // Tell the technology manager that we have started researching this - // such that players can't research the same thing twice. - const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); - cmpTechnologyManager.QueuedResearch(templateName, this.entity); - - cmpTrigger.CallEvent("OnResearchQueued", { - "playerid": player, - "technologyTemplate": item.technology.template, - "researcherEntity": this.entity - }); - } - Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null); if (!this.timer) @@ -534,8 +343,6 @@ /* * Removes an item from the queue. - * Refunds resource costs and population reservations. - * item.player is used as this.entity's owner may have changed. */ ProductionQueue.prototype.RemoveItem = function(id) { @@ -543,65 +350,8 @@ if (itemIndex == -1) return; - let item = this.queue[itemIndex]; - - // Destroy any cached entities (those which didn't spawn for some reason). - if (item.entity?.cache?.length) - { - for (const ent of item.entity.cache) - Engine.DestroyEntity(ent); - - delete item.entity.cache; - } - - const cmpPlayer = QueryPlayerIDInterface(item.player); - - if (item.entity) - { - let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - const template = cmpTemplateManager.GetTemplate(item.entity.template); - if (template.TrainingRestrictions) - { - let cmpPlayerEntityLimits = QueryPlayerIDInterface(item.player, IID_EntityLimits); - if (cmpPlayerEntityLimits) - cmpPlayerEntityLimits.ChangeCount(template.TrainingRestrictions.Category, -item.entity.count); - if (template.TrainingRestrictions.MatchLimit) - cmpPlayerEntityLimits.ChangeMatchCount(item.entity.template, -item.entity.count); - } - if (cmpPlayer) - { - if (item.productionStarted) - cmpPlayer.UnReservePopulationSlots(item.entity.population * item.entity.count); - if (itemIndex == 0) - cmpPlayer.UnBlockTraining(); - } - } - - let cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker); - - const totalCosts = {}; - for (let resource in item.resources) - { - totalCosts[resource] = 0; - if (item.entity) - totalCosts[resource] += Math.floor(item.entity.count * item.entity.resources[resource]); - if (item.technology) - totalCosts[resource] += item.technology.resources[resource]; - if (cmpStatisticsTracker) - cmpStatisticsTracker.IncreaseResourceUsedCounter(resource, -totalCosts[resource]); - } - - if (cmpPlayer) - cmpPlayer.AddResources(totalCosts); - - if (item.technology) - { - let cmpTechnologyManager = QueryPlayerIDInterface(item.player, IID_TechnologyManager); - if (cmpTechnologyManager) - cmpTechnologyManager.StoppedResearch(item.technology.template, true); - } + this.queue.splice(itemIndex, 1)[0].Stop(); - this.queue.splice(itemIndex, 1); Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null); if (!this.queue.length) @@ -620,17 +370,7 @@ */ ProductionQueue.prototype.GetQueue = function() { - return this.queue.map(item => ({ - "id": item.id, - "unitTemplate": item.entity?.template, - "technologyTemplate": item.technology?.template, - "count": item.entity?.count, - "neededSlots": item.entity?.neededSlots, - "progress": 1 - (item.timeRemaining / (item.timeTotal || 1)), - "timeRemaining": item.timeRemaining, - "paused": item.paused, - "metadata": item.metadata - })); + return this.queue.map(item => item.GetBasicInfo()); }; /* @@ -645,148 +385,7 @@ }; /* - * Returns batch build time. - */ -ProductionQueue.prototype.GetBatchTime = function(batchSize) -{ - // TODO: work out what equation we should use here. - return Math.pow(batchSize, ApplyValueModificationsToEntity( - "ProductionQueue/BatchTimeModifier", - +this.template.BatchTimeModifier, - this.entity)); -}; - -ProductionQueue.prototype.OnOwnershipChanged = function(msg) -{ - // Reset the production queue whenever the owner changes. - // (This should prevent players getting surprised when they capture - // an enemy building, and then loads of the enemy's civ's soldiers get - // created from it. Also it means we don't have to worry about - // updating the reserved pop slots.) - this.ResetQueue(); - - if (msg.to != INVALID_PLAYER) - this.CalculateEntitiesMap(); -}; - -ProductionQueue.prototype.OnCivChanged = function() -{ - this.CalculateEntitiesMap(); -}; - -/* - * 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). - * @param {Object} item - The item to spawn units for. - * @param {number} item.entity.count - The number of entities to spawn. - * @param {string} item.player - The owner of the item. - * @param {string} item.entity.template - The template to spawn. - * @param {any} - item.metadata - Optionally any metadata to add to the TrainingFinished message. - * - * @return {number} - The number of successfully created entities - */ -ProductionQueue.prototype.SpawnUnits = function(item) -{ - let createdEnts = []; - let 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 (!item.entity.cache) - { - item.entity.cache = []; - for (let i = 0; i < item.entity.count; ++i) - item.entity.cache.push(Engine.AddEntity(item.entity.template)); - } - - let autoGarrison; - let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); - if (cmpRallyPoint) - { - let data = cmpRallyPoint.GetData()[0]; - if (data && data.target && data.target == this.entity && data.command == "garrison") - autoGarrison = true; - } - - let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - let positionSelf = cmpPosition && cmpPosition.GetPosition(); - - let cmpPlayerEntityLimits = QueryPlayerIDInterface(item.player, IID_EntityLimits); - let cmpPlayerStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker); - while (item.entity.cache.length) - { - const ent = item.entity.cache[0]; - let cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership); - let garrisoned = false; - - if (autoGarrison) - { - let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); - if (cmpGarrisonable) - { - // Temporary owner affectation needed for GarrisonHolder checks. - cmpNewOwnership.SetOwnerQuiet(item.player); - garrisoned = cmpGarrisonable.Garrison(this.entity); - cmpNewOwnership.SetOwnerQuiet(INVALID_PLAYER); - } - } - - if (!garrisoned) - { - let pos = cmpFootprint.PickSpawnPoint(ent); - if (pos.y < 0) - break; - - let cmpNewPosition = Engine.QueryInterface(ent, IID_Position); - cmpNewPosition.JumpTo(pos.x, pos.z); - - if (positionSelf) - cmpNewPosition.SetYRotation(positionSelf.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) - { - let cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions); - if (cmpTrainingRestrictions) - cmpPlayerEntityLimits.ChangeCount(cmpTrainingRestrictions.GetCategory(), -1); - } - cmpNewOwnership.SetOwner(item.player); - - if (cmpPlayerStatisticsTracker) - cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter(ent); - - item.entity.cache.shift(); - createdEnts.push(ent); - } - - if (spawnedEnts.length && !autoGarrison && cmpRallyPoint) - for (let com of GetRallyPointCommands(cmpRallyPoint, spawnedEnts)) - ProcessCommand(item.player, com); - - if (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.entity, MT_TrainingFinished, { - "entities": createdEnts, - "owner": item.player, - "metadata": item.metadata - }); - } - - return createdEnts.length; -}; - -/* - * Increments progress on the first item in the production queue and blocks the - * queue if population limit is reached or some units failed to spawn. + * Increments progress on the first item in the production queue. * @param {Object} data - Unused in this case. * @param {number} lateness - The time passed since the expected time to fire the function. */ @@ -795,116 +394,32 @@ if (this.paused) return; - let cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return; - // Allocate available time to as many queue items as it takes // until we've used up all the time (so that we work accurately // with items that take fractions of a second). let time = this.ProgressInterval + lateness; - let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); while (this.queue.length) { let item = this.queue[0]; - if (item.paused) - item.paused = false; - if (!item.productionStarted) + if (item.IsPaused()) + item.Unpause(); + if (!item.IsStarted()) { if (item.entity) - { - const template = cmpTemplateManager.GetTemplate(item.entity.template); - item.entity.population = ApplyValueModificationsToTemplate( - "Cost/Population", - +template.Cost.Population, - item.player, - template); - - item.entity.neededSlots = cmpPlayer.TryReservePopulationSlots(item.entity.population * item.entity.count); - if (item.entity.neededSlots) - { - cmpPlayer.BlockTraining(); - return; - } this.SetAnimation("training"); - - cmpPlayer.UnBlockTraining(); - - Engine.PostMessage(this.entity, MT_TrainingStarted, { "entity": this.entity }); - } if (item.technology) - { - let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); - if (cmpTechnologyManager) - cmpTechnologyManager.StartedResearch(item.technology.template, true); - else - warn("Failed to start researching " + item.technology.template + ": No TechnologyManager available."); - this.SetAnimation("researching"); - } - item.productionStarted = true; + item.Start(); } - - if (item.timeRemaining > time) + time -= item.Progress(time); + if (!item.IsFinished()) { - item.timeRemaining -= time; Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null); return; } - if (item.entity) - { - let numSpawned = this.SpawnUnits(item); - if (numSpawned) - cmpPlayer.UnReservePopulationSlots(item.entity.population * numSpawned); - if (numSpawned == item.entity.count) - { - cmpPlayer.UnBlockTraining(); - delete this.spawnNotified; - } - else - { - if (numSpawned) - { - item.entity.count -= numSpawned; - Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null); - } - - cmpPlayer.BlockTraining(); - - if (!this.spawnNotified) - { - let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGUIInterface.PushNotification({ - "players": [cmpPlayer.GetPlayerID()], - "message": markForTranslation("Can't find free space to spawn trained units"), - "translateMessage": true - }); - this.spawnNotified = true; - } - return; - } - } - if (item.technology) - { - let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); - if (cmpTechnologyManager) - cmpTechnologyManager.ResearchTechnology(item.technology.template); - else - warn("Failed to finish researching " + item.technology.template + ": No TechnologyManager available."); - - const template = TechnologyTemplates.Get(item.technology.template); - if (template && template.soundComplete) - { - let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager); - if (cmpSoundManager) - cmpSoundManager.PlaySoundGroup(template.soundComplete, this.entity); - } - } - - time -= item.timeRemaining; this.queue.shift(); Engine.PostMessage(this.entity, MT_ProductionQueueChanged, null); @@ -913,14 +428,18 @@ // autoqueue slightly worse than regular queuing, and also ensures // that autoqueue doesn't train more than one item per turn, // if the units would take fewer than ProgressInterval ms to train. - if (this.autoqueuing && item.entity) + if (this.autoqueuing) { - if (!this.AddItem(item.entity.template, "unit", item.entity.count, item.metadata)) + const autoqueueData = item.OriginalItem(); + if (!autoqueueData) + continue; + + if (!this.AddItem(autoqueueData.templateName, "unit", autoqueueData.count, autoqueueData.metadata)) { this.DisableAutoQueue(); const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ - "players": [cmpPlayer.GetPlayerID()], + "players": [QueryOwnerInterface(this.entity).GetPlayerID()], "message": markForTranslation("Could not auto-queue unit, de-activating."), "translateMessage": true }); @@ -937,14 +456,13 @@ { this.StopTimer(); this.paused = true; - if (this.queue[0]) - this.queue[0].paused = true; + this.queue[0]?.Pause(); + this.StopTimer(); }; ProductionQueue.prototype.UnpauseProduction = function() { - if (this.queue[0]) - this.queue[0].paused = false; + this.queue[0]?.Unpause(); delete this.paused; this.StartTimer(); }; @@ -974,37 +492,19 @@ delete this.timer; }; -ProductionQueue.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 != "ProductionQueue" || - !msg.valueNames.some(val => val.startsWith("ProductionQueue/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 production queue. - let cmpPlayer = QueryOwnerInterface(this.entity); - if (cmpPlayer) - Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID()); -}; - ProductionQueue.prototype.HasQueuedProduction = function() { return this.queue.length > 0; }; -ProductionQueue.prototype.OnDisabledTemplatesChanged = function(msg) +ProductionQueue.prototype.OnOwnershipChanged = function(msg) { - this.CalculateEntitiesMap(); + // Reset the production queue whenever the owner changes. + // (This should prevent players getting surprised when they capture + // an enemy building, and then loads of the enemy's civ's soldiers get + // created from it. Also it means we don't have to worry about + // updating the reserved pop slots.) + this.ResetQueue(); }; ProductionQueue.prototype.OnGarrisonedStateChanged = function(msg) Index: ps/trunk/binaries/data/mods/public/simulation/components/Researcher.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Researcher.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Researcher.js @@ -0,0 +1,447 @@ +function Researcher() {} + +Researcher.prototype.Schema = + "Allows the entity to research technologies." + + "" + + "" + + "0.5" + + "0.1" + + "0" + + "2" + + "" + + "" + + "" + + "\n phase_town_{civ}\n phase_metropolis_ptol\n unlock_shared_los\n wonder_population_cap\n " + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + "" + + "" + + "" + + Resources.BuildSchema("nonNegativeDecimal", ["time"]) + + "" + + ""; + +/** + * 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); Index: ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js +++ ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -282,6 +282,17 @@ TechnologyManager.prototype.QueuedResearch = function(tech, researcher) { this.researchQueued.set(tech, researcher); + + const cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); + if (!cmpPlayer) + return; + const playerID = cmpPlayer.GetPlayerID(); + + Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).CallEvent("OnResearchQueued", { + "playerid": playerID, + "technologyTemplate": tech, + "researcherEntity": researcher + }); }; // Marks a technology as actively being researched Index: ps/trunk/binaries/data/mods/public/simulation/components/Trainer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Trainer.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Trainer.js @@ -0,0 +1,727 @@ +function Trainer() {} + +Trainer.prototype.Schema = + "Allows the entity to train new units." + + "" + + "0.7" + + "" + + "\n units/{civ}/support_female_citizen\n units/{native}/support_trader\n units/athen/infantry_spearman_b\n " + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + ""; + +/** + * 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); Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js @@ -5,15 +5,3 @@ * 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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Researcher.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Researcher.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Researcher.js @@ -0,0 +1 @@ +Engine.RegisterInterface("Researcher"); Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Trainer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Trainer.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Trainer.js @@ -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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -6,7 +6,6 @@ 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/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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ProductionQueue.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; + +AddMock(SYSTEM_ENTITY, IID_Timer, { + "CancelTimer": (id) => {}, + "SetInterval": (ent, iid, func) => 1 +}); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": id => playerEnt +}); + +AddMock(playerEnt, IID_Player, { + "GetPlayerID": () => playerID +}); + +AddMock(testEntity, IID_Ownership, { + "GetOwner": () => playerID +}); + +AddMock(testEntity, IID_Trainer, { + "GetBatch": (id) => ({}), + "HasBatch": (id) => false, // Assume we've finished. + "Progress": (time) => time, + "QueueBatch": () => 1, + "StopBatch": (id) => {} +}); + +const cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", null); + + +// Test autoqueue. +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); + +cmpProdQueue.RemoveItem(cmpProdQueue.nextID -1); +TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0); + +cmpProdQueue.DisableAutoQueue(); + + +// 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) => {} +}); + +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); + + +// 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); -function testEntitiesList() -{ - Engine.RegisterGlobal("TechnologyTemplates", { - "Has": name => name == "phase_town_athen" || name == "phase_city_athen", - "Get": () => ({}) - }); - - const productionQueueId = 6; - const playerId = 1; - const playerEntityID = 2; - - AddMock(SYSTEM_ENTITY, IID_TemplateManager, { - "TemplateExists": () => true, - "GetTemplate": name => ({}) - }); - - 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(SYSTEM_ENTITY, IID_PlayerManager, { - "GetPlayerByID": id => playerEntityID - }); - - AddMock(playerEntityID, IID_Player, { - "GetCiv": () => "iber", - "GetDisabledTechnologies": () => ({}), - "GetDisabledTemplates": () => ({}), - "GetPlayerID": () => playerId - }); - - AddMock(playerEntityID, IID_TechnologyManager, { - "CheckTechnologyRequirements": () => true, - "IsInProgress": () => false, - "IsTechnologyResearched": () => false - }); - - AddMock(productionQueueId, IID_Ownership, { - "GetOwner": () => playerId - }); - - AddMock(productionQueueId, IID_Identity, { - "GetCiv": () => "iber" - }); - - AddMock(productionQueueId, IID_Upgrade, { - "IsUpgrading": () => false - }); - - 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"] - ); - - 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"]); - - AddMock(SYSTEM_ENTITY, IID_TemplateManager, { - "TemplateExists": () => true, - "GetTemplate": name => ({}) - }); - - 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"] - ); - - 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); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Researcher.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Researcher.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Researcher.js @@ -0,0 +1,153 @@ +Engine.RegisterGlobal("Resources", { + "BuildSchema": (a, b) => {} +}); +Engine.LoadHelperScript("Player.js"); +Engine.LoadComponentScript("interfaces/TechnologyManager.js"); +Engine.LoadComponentScript("interfaces/Researcher.js"); +Engine.LoadComponentScript("Researcher.js"); + +Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value); + +const playerID = 1; +const playerEntityID = 11; +const entityID = 21; + +Engine.RegisterGlobal("TechnologyTemplates", { + "Has": name => name == "phase_town_athen" || name == "phase_city_athen", + "Get": () => ({}) +}); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": id => playerEntityID +}); + +AddMock(playerEntityID, IID_Player, { + "GetCiv": () => "iber", + "GetDisabledTechnologies": () => ({}) +}); + +AddMock(playerEntityID, IID_TechnologyManager, { + "CheckTechnologyRequirements": () => true, + "IsInProgress": () => false, + "IsTechnologyResearched": () => false +}); + +AddMock(entityID, IID_Ownership, { + "GetOwner": () => playerID +}); + +AddMock(entityID, IID_Identity, { + "GetCiv": () => "iber" +}); + +const cmpResearcher = ConstructComponent(entityID, "Researcher", { + "Technologies": { "_string": "gather_fishing_net " + + "phase_town_{civ} " + + "phase_city_{civ}" } +}); + +TS_ASSERT_UNEVAL_EQUALS( + cmpResearcher.GetTechnologiesList(), + ["gather_fishing_net", "phase_town_generic", "phase_city_generic"] +); + +AddMock(playerEntityID, IID_Player, { + "GetCiv": () => "athen", + "GetDisabledTechnologies": () => ({ "gather_fishing_net": true }) +}); +TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), ["phase_town_athen", "phase_city_athen"]); + +AddMock(playerEntityID, IID_TechnologyManager, { + "CheckTechnologyRequirements": () => true, + "IsInProgress": () => false, + "IsTechnologyResearched": tech => tech == "phase_town_athen" +}); +TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), [undefined, "phase_city_athen"]); + +AddMock(playerEntityID, IID_Player, { + "GetCiv": () => "iber", + "GetDisabledTechnologies": () => ({}) +}); +TS_ASSERT_UNEVAL_EQUALS( + cmpResearcher.GetTechnologiesList(), + ["gather_fishing_net", "phase_town_generic", "phase_city_generic"] +); + +Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => value + " some_test"); +TS_ASSERT_UNEVAL_EQUALS( + cmpResearcher.GetTechnologiesList(), + ["gather_fishing_net", "phase_town_generic", "phase_city_generic", "some_test"] +); + + +// Test Queuing a tech. +const queuedTech = "gather_fishing_net"; +const cost = { + "food": 10 +}; +Engine.RegisterGlobal("TechnologyTemplates", { + "Has": () => true, + "Get": () => ({ + "cost": cost, + "researchTime": 1 + }) +}); + +const cmpPlayer = AddMock(playerEntityID, IID_Player, { + "GetCiv": () => "iber", + "GetDisabledTechnologies": () => ({}), + "GetPlayerID": () => playerID, + "TrySubtractResources": (resources) => { + TS_ASSERT_UNEVAL_EQUALS(resources, cost); + // Just have enough resources. + return true; + }, + "RefundResources": (resources) => { + TS_ASSERT_UNEVAL_EQUALS(resources, cost); + }, +}); +let spyCmpPlayer = new Spy(cmpPlayer, "TrySubtractResources"); +const techManager = AddMock(playerEntityID, IID_TechnologyManager, { + "CheckTechnologyRequirements": () => true, + "IsInProgress": () => false, + "IsTechnologyResearched": () => false, + "QueuedResearch": (templateName, researcher) => { + TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech); + TS_ASSERT_UNEVAL_EQUALS(researcher, entityID); + }, + "StoppedResearch": (templateName, _) => { + TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech); + }, + "StartedResearch": (templateName, _) => { + TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech); + }, + "ResearchTechnology": (templateName, _) => { + TS_ASSERT_UNEVAL_EQUALS(templateName, queuedTech); + } +}); +let spyTechManager = new Spy(techManager, "QueuedResearch"); +let id = cmpResearcher.QueueTechnology(queuedTech); +TS_ASSERT_EQUALS(spyTechManager._called, 1); +TS_ASSERT_EQUALS(spyCmpPlayer._called, 1); +TS_ASSERT_EQUALS(cmpResearcher.queue.size, 1); + + +// Test removing a queued tech. +spyCmpPlayer = new Spy(cmpPlayer, "RefundResources"); +spyTechManager = new Spy(techManager, "StoppedResearch"); +cmpResearcher.StopResearching(id); +TS_ASSERT_EQUALS(spyTechManager._called, 1); +TS_ASSERT_EQUALS(spyCmpPlayer._called, 1); +TS_ASSERT_EQUALS(cmpResearcher.queue.size, 0); + + +// Test finishing a queued tech. +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); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Trainer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Trainer.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Trainer.js @@ -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"); Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/structures/library.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/structures/library.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/structures/library.json @@ -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" Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/gaul_player_teambonus.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/gaul_player_teambonus.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/gaul_player_teambonus.json @@ -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." Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/maur_player_teambonus.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/maur_player_teambonus.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/maur_player_teambonus.json @@ -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." Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/athen_catafalque_2.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/athen_catafalque_2.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/athen_catafalque_2.json @@ -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." Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/ptol_catafalque.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/ptol_catafalque.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/ptol_catafalque.json @@ -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." Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/sele_catafalque_1.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/sele_catafalque_1.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/sele_catafalque_1.json @@ -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." Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/athen_hero_themistocles_1.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/athen_hero_themistocles_1.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/athen_hero_themistocles_1.json @@ -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", Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_ashoka.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_ashoka.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_ashoka.json @@ -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", Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_chanakya_1.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_chanakya_1.json +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/heroes/maur_hero_chanakya_1.json @@ -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", Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/barracks_batch_training.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/barracks_batch_training.json +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/barracks_batch_training.json @@ -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" Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/hoplite_tradition.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/hoplite_tradition.json +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/hoplite_tradition.json @@ -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" +} Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/stable_batch_training.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/stable_batch_training.json +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/stable_batch_training.json @@ -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" Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js @@ -58,8 +58,9 @@ 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 @@ } 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 @@ "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 @@ Cheat({ "player": input.player, "action": "researchTechnology", "parameter": parameter, "selected": input.selected }); return; case "researchTechnology": + { if (!input.parameter.length) return; @@ -132,8 +133,8 @@ // 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 @@ 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 @@ !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 }); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js @@ -353,26 +353,19 @@ 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); } }, Index: ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml @@ -26,18 +26,18 @@ - - - -units/{civ}/support_female_citizen - campaigns/army_mace_hero_alexander - campaigns/army_mace_standard - - true 150 35000 + + + -units/{civ}/support_female_citizen + campaigns/army_mace_hero_alexander + campaigns/army_mace_standard + + campaigns/structures/hellenes/settlement_curtainwall.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml @@ -26,19 +26,19 @@ - + + true + 300 + 35000 + + -units/{civ}/support_female_citizen campaigns/army_mace_hero_alexander campaigns/army_mace_standard units/{civ}/support_trader - - - true - 300 - 35000 - + campaigns/structures/hellenes/settlement_curtainwall.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml @@ -16,11 +16,13 @@ + true 100 65535 + structures/hellenes/temple.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/mixins/shrine.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/mixins/shrine.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/mixins/shrine.xml @@ -1,43 +1,45 @@ - - - - own neutral ally - - - - 8.0 - - - - - Shrine - - true - - - - - - - -units/{civ}/support_healer_b - units/{native}/support_healer_e - - - -heal_range - -heal_range_2 - -heal_rate - -heal_rate_2 - -garrison_heal - -health_regen_units - - - - 15.0 - - - - - - structures/fndn_4x4.xml - - + + + + own neutral ally + + + + 8.0 + + + + + Shrine + + true + + + + + + + -heal_range + -heal_range_2 + -heal_rate + -heal_rate_2 + -garrison_heal + -health_regen_units + + + + + 15.0 + + + + + + -units/{civ}/support_healer_b + units/{native}/support_healer_e + + + + structures/fndn_4x4.xml + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/mixins/trading_post.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/mixins/trading_post.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/mixins/trading_post.xml @@ -1,25 +1,25 @@ - - - - 500 - - - - Trading Post - true - - - - -trader_health - -trade_gain_01 - -trade_gain_02 - -trade_commercial_treaty - - - - - - - structures/fndn_5x5.xml - - + + + + 500 + + + + Trading Post + true + + + + -trader_health + -trade_gain_01 + -trade_gain_02 + -trade_commercial_treaty + + + + + + + structures/fndn_5x5.xml + + Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_arsenal.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_arsenal.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_arsenal.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/arsenal + structures/hellenes/workshop.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_barracks.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_barracks.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_barracks.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/barracks + structures/athenians/barracks.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_civil_centre.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/civil_centre + structures/athenians/civil_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_corral.xml @@ -10,9 +10,11 @@ + structures/{civ}/corral + structures/hellenes/corral.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_dock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_dock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_dock.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/dock + structures/athenians/dock.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_fortress.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/fortress + structures/athenians/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_10.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_10.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_10.xml @@ -5,7 +5,9 @@ Changes in a 10-pop house for civilisations with those houses, is deleted for other civs + + structures/hellenes/house.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_5.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_5.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_house_5.xml @@ -11,7 +11,9 @@ + + structures/ptolemies/house.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_market.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_market.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_market.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/market + structures/hellenes/market.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_range.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_range.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_range.xml @@ -10,9 +10,11 @@ + structures/{civ}/range + structures/hellenes/range.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_stable.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_stable.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_stable.xml @@ -10,9 +10,11 @@ + structures/{civ}/stable + structures/hellenes/stable.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_temple.xml @@ -4,9 +4,11 @@ skirm + structures/{civ}/temple + structures/athenians/temple.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_wonder.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/skirmish/structures/default_wonder.xml @@ -10,9 +10,11 @@ + structures/{civ}/wonder + structures/hellenes/temple_epic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/civil_centre.xml @@ -7,13 +7,13 @@ athen Agora - + units/{civ}/infantry_spearman_b units/{civ}/infantry_slinger_b units/{civ}/cavalry_javelineer_b - + structures/athenians/civil_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/dock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/dock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/dock.xml @@ -14,13 +14,13 @@ - + 0.7 units/{civ}/infantry_marine_archer_b units/{civ}/champion_marine - + structures/athenians/dock.xml structures/fndn_6x4_dock.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/gymnasium.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/gymnasium.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/gymnasium.xml @@ -33,19 +33,19 @@ - - 0.7 - - units/{civ}/champion_infantry - units/{civ}/champion_ranged - - interface/complete/building/complete_gymnasium.xml + + 0.7 + + units/{civ}/champion_infantry + units/{civ}/champion_ranged + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/prytaneion.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/prytaneion.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/prytaneion.xml @@ -33,18 +33,12 @@ - - 0.7 - - units/{civ}/hero_themistocles - units/{civ}/hero_pericles - units/{civ}/hero_iphicrates - + long_walls iphicratean_reforms - + @@ -56,6 +50,14 @@ 38 40000 + + 0.7 + + units/{civ}/hero_themistocles + units/{civ}/hero_pericles + units/{civ}/hero_iphicrates + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/bench.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/bench.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/bench.xml @@ -19,9 +19,11 @@ + 6.0 + props/special/eyecandy/bench_1.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/civil_centre.xml @@ -14,13 +14,13 @@ - + units/{civ}/infantry_spearman_b units/{civ}/infantry_slinger_b units/{civ}/cavalry_javelineer_b - + structures/britons/civic_centre.xml structures/fndn_7x8.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/crannog.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/crannog.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/crannog.xml @@ -25,7 +25,16 @@ true 0.0 - + + ship + + + + -phase_town_{civ} + -hellenistic_metropolis + + + units/{civ}/infantry_spearman_b units/{civ}/infantry_slinger_b @@ -35,14 +44,7 @@ units/{civ}/ship_bireme units/{civ}/ship_trireme - - -phase_town_{civ} - -hellenistic_metropolis - - - - ship - + structures/britons/crannog.xml structures/fndn_8x8.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/fortress.xml @@ -15,19 +15,19 @@ - - - units/{civ}/hero_boudicca - units/{civ}/hero_caratacos - units/{civ}/hero_cunobelin - - interface/complete/building/complete_broch.xml + + + units/{civ}/hero_boudicca + units/{civ}/hero_caratacos + units/{civ}/hero_cunobelin + + structures/britons/fortress.xml structures/fndn_9x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/civil_centre.xml @@ -7,16 +7,18 @@ cart Merkāz - + + + colonization + + + units/{civ}/infantry_spearman_b units/{civ}/infantry_archer_b units/{civ}/cavalry_javelineer_b - - colonization - - + structures/carthaginians/civil_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/corral.xml @@ -10,12 +10,12 @@ - + -gaia/fauna_cattle_cow_trainable gaia/fauna_cattle_sanga_trainable - + structures/carthaginians/corral.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy.xml @@ -26,7 +26,7 @@ - + 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 - + structures/carthaginians/embassy.xml structures/fndn_8x8.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_celtic.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_celtic.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_celtic.xml @@ -25,12 +25,12 @@ - + units/{native}/infantry_swordsman_gaul_b units/{native}/cavalry_swordsman_gaul_b - + structures/carthaginians/embassy_celtic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_iberian.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_iberian.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_iberian.xml @@ -18,13 +18,13 @@ - + units/{native}/infantry_javelineer_iber_b units/{native}/infantry_slinger_iber_b units/{native}/cavalry_swordsman_iber_b - + structures/carthaginians/embassy_iberian.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_italic.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_italic.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/embassy_italic.xml @@ -25,12 +25,12 @@ - + units/{native}/infantry_swordsman_ital_b units/{native}/cavalry_spearman_ital_b - + structures/carthaginians/embassy_italic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/fortress.xml @@ -12,13 +12,13 @@ - + units/{civ}/hero_hamilcar units/{civ}/hero_hannibal units/{civ}/hero_maharbal - + structures/carthaginians/fndn_fortress.xml structures/carthaginians/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/super_dock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/super_dock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/super_dock.xml @@ -54,14 +54,6 @@ true 0.0 - - 0.7 - - units/{civ}/ship_bireme - units/{civ}/ship_trireme - units/{civ}/ship_quinquereme - - ship @@ -84,6 +76,14 @@ 200 25000 + + 0.7 + + units/{civ}/ship_bireme + units/{civ}/ship_trireme + units/{civ}/ship_quinquereme + + 100 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/temple.xml @@ -12,11 +12,11 @@ - + units/{civ}/champion_infantry - + structures/carthaginians/temple_big.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_hut.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_hut.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_hut.xml @@ -26,9 +26,11 @@ 2 + 7.0 + structures/celts/hut.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_longhouse.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_longhouse.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/celt_longhouse.xml @@ -26,6 +26,8 @@ 10 + + structures/celts/longhouse.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric.xml @@ -19,9 +19,11 @@ + 8.0 + props/special/eyecandy/column_doric.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen.xml @@ -19,9 +19,11 @@ + 6.0 + props/special/eyecandy/column_doric_fallen.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen_b.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen_b.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/column_doric_fallen_b.xml @@ -19,9 +19,11 @@ + 6.0 + props/special/eyecandy/column_doric_fallen_b.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_long.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_long.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_long.xml @@ -23,10 +23,12 @@ + 6.0 + temp/fence_wood_long_a.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_short.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_short.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_short.xml @@ -20,10 +20,12 @@ + 6.0 + true Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_stone.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_stone.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/fence_stone.xml @@ -20,10 +20,12 @@ + 6.0 + props/special/eyecandy/fence_stone_medit.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/assembly.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/assembly.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/assembly.xml @@ -38,15 +38,6 @@ - - 0.7 - - units/{civ}/champion_infantry_trumpeter - units/{civ}/hero_brennus - units/{civ}/hero_viridomarus - units/{civ}/hero_vercingetorix - - @@ -67,6 +58,15 @@ 40 40000 + + 0.7 + + units/{civ}/champion_infantry_trumpeter + units/{civ}/hero_brennus + units/{civ}/hero_viridomarus + units/{civ}/hero_vercingetorix + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/civil_centre.xml @@ -14,13 +14,13 @@ - + units/{civ}/infantry_spearman_b units/{civ}/infantry_javelineer_b units/{civ}/cavalry_javelineer_b - + structures/gauls/civic_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/temple.xml @@ -24,11 +24,11 @@ - + units/{civ}/champion_fanatic - + structures/celts/temple.xml structures/fndn_7x7.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_epic_temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_epic_temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_epic_temple.xml @@ -32,10 +32,12 @@ + 60 65535 + 80 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_propylaea.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_propylaea.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_propylaea.xml @@ -29,11 +29,13 @@ + false 40 65535 + 20 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_stoa.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_stoa.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/hellenic_stoa.xml @@ -33,11 +33,13 @@ + false 36 65535 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/civil_centre.xml @@ -7,13 +7,13 @@ iber Oppidum - + units/{civ}/infantry_swordsman_b units/{civ}/infantry_javelineer_b units/{civ}/cavalry_javelineer_b - + structures/iberians/civic_center.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/fortress.xml @@ -8,13 +8,13 @@ - + units/{civ}/hero_caros units/{civ}/hero_indibil units/{civ}/hero_viriato - + structures/iberians/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/monument.xml @@ -44,6 +44,7 @@ + @@ -60,6 +61,7 @@ + structures/iberians/sb_1.xml structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ishtar_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ishtar_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ishtar_gate.xml @@ -39,6 +39,7 @@ + @@ -50,6 +51,7 @@ 38 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_blemmye.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_blemmye.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_blemmye.xml @@ -31,15 +31,15 @@ - - - units/{native}/cavalry_javelineer_merc_b - - 1 + + + units/{native}/cavalry_javelineer_merc_b + + structures/mercenaries/camp_blemmye.xml structures/fndn_8x7.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_noba.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_noba.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/camp_noba.xml @@ -31,16 +31,16 @@ - - - units/{native}/infantry_maceman_merc_b - units/{native}/infantry_javelineer_merc_b - - 1 + + + units/{native}/infantry_maceman_merc_b + units/{native}/infantry_javelineer_merc_b + + structures/mercenaries/camp_nuba.xml structures/fndn_8x8.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/civil_centre.xml @@ -11,16 +11,18 @@ - + + + architecture_kush + + + units/{civ}/infantry_spearman_b units/{civ}/infantry_archer_b units/{civ}/cavalry_javelineer_b - - architecture_kush - - + structures/kushites/civic_centre_kush.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/corral.xml @@ -11,12 +11,12 @@ - + -gaia/fauna_cattle_cow_trainable gaia/fauna_cattle_sanga_trainable - + structures/kushites/corral.xml structures/fndn_4x4.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/fortress.xml @@ -12,13 +12,13 @@ - + units/{civ}/hero_nastasen units/{civ}/hero_amanirenas units/{civ}/hero_arakamani - + structures/kushites/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_large.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_large.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_large.xml @@ -38,6 +38,7 @@ + @@ -52,6 +53,7 @@ 40 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_small.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_small.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/pyramid_small.xml @@ -38,6 +38,7 @@ + @@ -49,6 +50,7 @@ 30 30000 + 30 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/shrine.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/shrine.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/shrine.xml @@ -16,6 +16,8 @@ + + structures/kushites/shrine.xml structures/fndn_6x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple.xml @@ -13,11 +13,11 @@ - + units/{civ}/champion_infantry_apedemak - + structures/kushites/temple.xml structures/fndn_6x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple_amun.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple_amun.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/temple_amun.xml @@ -38,16 +38,16 @@ - + + 2 + + -units/{civ}/support_healer_b units/{civ}/support_healer_e units/{civ}/champion_infantry_amun - - - 2 - + structures/kushites/temple_amun.xml structures/fndn_9x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/civil_centre.xml @@ -7,13 +7,13 @@ mace Agora - + units/{civ}/infantry_pikeman_b units/{civ}/infantry_javelineer_b units/{civ}/cavalry_spearman_b - + structures/macedonians/civic_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/fortress.xml @@ -12,16 +12,18 @@ - + + + silvershields + + + units/{civ}/hero_philip_ii units/{civ}/hero_alexander_iii units/{civ}/hero_demetrius_i - - silvershields - - + structures/macedonians/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/civil_centre.xml @@ -14,14 +14,14 @@ - + units/{civ}/infantry_spearman_b units/{civ}/infantry_archer_b units/{civ}/cavalry_javelineer_b units/{civ}/support_elephant - + structures/mauryas/civil_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/corral.xml @@ -10,12 +10,12 @@ - + -gaia/fauna_cattle_cow_trainable gaia/fauna_cattle_zebu_trainable - + structures/mauryas/corral.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/palace.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/palace.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/palace.xml @@ -30,16 +30,6 @@ - - 0.7 - - units/{civ}/champion_maiden - units/{civ}/champion_maiden_archer - units/{civ}/hero_chanakya - units/{civ}/hero_chandragupta - units/{civ}/hero_ashoka - - @@ -51,6 +41,16 @@ 48 40000 + + 0.7 + + units/{civ}/champion_maiden + units/{civ}/champion_maiden_archer + units/{civ}/hero_chanakya + units/{civ}/hero_chandragupta + units/{civ}/hero_ashoka + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/pillar_ashoka.xml @@ -44,6 +44,7 @@ + @@ -51,6 +52,7 @@ + props/structures/mauryas/ashoka_pillar.xml structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml @@ -40,14 +40,6 @@ - - - units/{civ}/infantry_spearman_merc_b - units/{civ}/infantry_swordsman_merc_b - units/{civ}/cavalry_spearman_merc_b - units/{civ}/cavalry_javelineer_merc_b - - @@ -58,6 +50,14 @@ 1 + + + units/{civ}/infantry_spearman_merc_b + units/{civ}/infantry_swordsman_merc_b + units/{civ}/cavalry_spearman_merc_b + units/{civ}/cavalry_javelineer_merc_b + + structures/mercenaries/camp_egyptian.xml structures/fndn_7x7.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/obelisk.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/obelisk.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/obelisk.xml @@ -20,7 +20,9 @@ + + props/special/eyecandy/obelisk.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_fort.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_fort.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/palisades_fort.xml @@ -13,6 +13,8 @@ + + props/special/palisade_rocks_fort.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/apadana.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/apadana.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/apadana.xml @@ -37,18 +37,11 @@ - - 0.8 - - units/{civ}/champion_infantry - units/{civ}/hero_cyrus_ii - units/{civ}/hero_darius_i - units/{civ}/hero_xerxes_i - + immortals - + 1.0 @@ -69,6 +62,15 @@ 48 40000 + + 0.8 + + units/{civ}/champion_infantry + units/{civ}/hero_cyrus_ii + units/{civ}/hero_darius_i + units/{civ}/hero_xerxes_i + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/civil_centre.xml @@ -8,16 +8,18 @@ Provincial Governor Xšaçapāvan - + + + architecture_pers + + + units/{civ}/infantry_spearman_b units/{civ}/infantry_archer_b units/{civ}/cavalry_javelineer_b - - architecture_pers - - + structures/persians/civil_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/tacara.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/tacara.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/tacara.xml @@ -34,6 +34,7 @@ + @@ -45,6 +46,7 @@ 48 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/civil_centre.xml @@ -11,7 +11,7 @@ - + units/{civ}/infantry_pikeman_b units/{civ}/infantry_slinger_b @@ -20,7 +20,7 @@ units/{civ}/hero_ptolemy_iv units/{civ}/hero_cleopatra_vii - + structures/ptolemies/civic_centre.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/corral.xml @@ -10,12 +10,12 @@ - + -gaia/fauna_cattle_cow_trainable gaia/fauna_cattle_sanga_trainable - + structures/ptolemies/corral.xml structures/fndn_4x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/dock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/dock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/dock.xml @@ -11,11 +11,11 @@ - + units/{civ}/champion_juggernaut - + structures/ptolemies/dock.xml structures/fndn_6x4_dock.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/lighthouse.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/lighthouse.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/lighthouse.xml @@ -41,6 +41,7 @@ 0.0 + @@ -48,6 +49,7 @@ + 200 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/mercenary_camp.xml @@ -39,14 +39,6 @@ - - - units/{civ}/infantry_spearman_merc_b - units/{civ}/infantry_swordsman_merc_b - units/{civ}/cavalry_spearman_merc_b - units/{civ}/cavalry_javelineer_merc_b - - @@ -57,6 +49,14 @@ 1 + + + units/{civ}/infantry_spearman_merc_b + units/{civ}/infantry_swordsman_merc_b + units/{civ}/cavalry_spearman_merc_b + units/{civ}/cavalry_javelineer_merc_b + + structures/ptolemies/settlement.xml structures/fndn_7x7.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/military_colony.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/military_colony.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/military_colony.xml @@ -12,7 +12,7 @@ - + units/{civ}/support_female_citizen units/{civ}/infantry_spearman_merc_b @@ -20,7 +20,7 @@ units/{civ}/cavalry_spearman_merc_b units/{civ}/cavalry_javelineer_merc_b - + structures/ptolemies/military_colony.xml structures/fndn_9x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/arch.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/arch.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/arch.xml @@ -33,6 +33,7 @@ + @@ -40,6 +41,7 @@ + structures/romans/triumphal_arch.xml structures/fndn_5x3.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/army_camp.xml @@ -24,12 +24,6 @@ - - 3 - 15 - 1 - Soldier - neutral enemy ArmyCamp @@ -38,6 +32,12 @@ 80 + + 3 + 15 + 1 + Soldier + 3 10.0 @@ -83,16 +83,6 @@ - - 0.7 - - units/{civ}/infantry_axeman_a - units/{civ}/infantry_swordsman_a - units/{civ}/infantry_spearman_a - units/{civ}/infantry_pikeman_a - units/{civ}/siege_ram - - @@ -113,6 +103,16 @@ 2 + + 0.7 + + units/{civ}/infantry_axeman_a + units/{civ}/infantry_swordsman_a + units/{civ}/infantry_spearman_a + units/{civ}/infantry_pikeman_a + units/{civ}/siege_ram + + 90 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/civil_centre.xml @@ -14,13 +14,13 @@ - + units/{civ}/infantry_swordsman_b units/{civ}/infantry_javelineer_b units/{civ}/cavalry_spearman_b - + structures/romans/civic_centre.xml structures/fndn_9x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/fortress.xml @@ -5,13 +5,13 @@ Castellum Train Heroes. Garrison Soldiers for additional arrows. - + units/{civ}/hero_marcellus units/{civ}/hero_maximus units/{civ}/hero_scipio - + structures/romans/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/tent.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/tent.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/tent.xml @@ -33,6 +33,7 @@ + @@ -43,6 +44,7 @@ 1 + props/structures/romans/rome_tent.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/civil_centre.xml @@ -4,7 +4,7 @@ sele Agora - + units/{civ}/infantry_spearman_b units/{civ}/infantry_javelineer_b @@ -13,7 +13,7 @@ units/{civ}/hero_antiochus_iii units/{civ}/hero_antiochus_iv - + structures/seleucids/civic_center.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/fortress.xml @@ -11,11 +11,11 @@ - + parade_of_daphne - + structures/seleucids/fortress.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/military_colony.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/military_colony.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/military_colony.xml @@ -12,13 +12,13 @@ - + units/{civ}/infantry_swordsman_merc_b units/{civ}/infantry_archer_merc_b units/{civ}/cavalry_spearman_merc_b - + structures/seleucids/military_colony.xml structures/fndn_9x9.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele_colonnade.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele_colonnade.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele_colonnade.xml @@ -19,9 +19,11 @@ + 8.0 + props/special/eyecandy/sele_colonnade.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/civil_centre.xml @@ -7,13 +7,13 @@ spart Agora - + units/{civ}/infantry_spearman_b units/{civ}/infantry_javelineer_b units/{civ}/cavalry_javelineer_b - + structures/spartans/civic_center.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/gerousia.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/gerousia.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/gerousia.xml @@ -33,12 +33,6 @@ - - 0.7 - - units/{civ}/hero_leonidas - - @@ -50,6 +44,12 @@ 38 40000 + + 0.7 + + units/{civ}/hero_leonidas + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/syssiton.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/syssiton.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/syssiton.xml @@ -34,18 +34,11 @@ - - 0.7 - - units/{civ}/champion_infantry_spear - units/{civ}/hero_leonidas - units/{civ}/hero_brasidas - units/{civ}/hero_agis - + agoge - + @@ -57,6 +50,15 @@ 38 40000 + + 0.7 + + units/{civ}/champion_infantry_spear + units/{civ}/hero_leonidas + units/{civ}/hero_brasidas + units/{civ}/hero_agis + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_rectangle.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_rectangle.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_rectangle.xml @@ -22,10 +22,12 @@ + 6.0 + props/special/eyecandy/table_rectangle.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_square.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_square.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/table_square.xml @@ -22,10 +22,12 @@ + 6.0 + props/special/eyecandy/table_square.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/uffington_horse.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/uffington_horse.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/uffington_horse.xml @@ -16,9 +16,11 @@ + 8.0 + structures/fndn_stonehenge.xml structures/britons/uffington_horse.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml @@ -77,16 +77,7 @@ 0 6 - - 1.0 - - 1.0 - 1.0 - 1.0 - 1.0 - - - + special/rallypoint @@ -103,6 +94,15 @@ 2.0 + + + 1.0 + 1.0 + 1.0 + 1.0 + + + @@ -161,6 +161,9 @@ 20 neutral enemy + + 1.0 + true false Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -30,11 +30,6 @@ - - 3 - 1 - Soldier - own neutral CivilCentre @@ -43,6 +38,11 @@ 200 + + 3 + 1 + Soldier + 2500 5.0 @@ -90,11 +90,7 @@ 20 - - 0.8 - - units/{civ}/support_female_citizen - + phase_town_{civ} phase_city_{civ} @@ -106,7 +102,7 @@ hoplite_tradition hellenistic_metropolis - + @@ -134,6 +130,12 @@ 140 10000 + + 0.8 + + units/{civ}/support_female_citizen + + 90 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml @@ -1,8 +1,5 @@ - - 1 - own neutral Colony @@ -11,6 +8,9 @@ 120 + + 1 + 300 @@ -35,13 +35,13 @@ 40 40 - + -phase_town_{civ} -phase_city_{civ} -hellenistic_metropolis - + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml @@ -38,17 +38,14 @@ 5 - - - units/{civ}/support_female_citizen_house - + health_females_01 pop_house_01 pop_house_02 unlock_females_house - + @@ -63,6 +60,11 @@ 16 65535 + + + units/{civ}/support_female_citizen_house + + 20 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml @@ -39,11 +39,7 @@ - - 0.8 - - units/{civ}/support_healer_b - + heal_range heal_range_2 @@ -52,7 +48,7 @@ garrison_heal health_regen_units - + @@ -64,6 +60,12 @@ 40 30000 + + 0.8 + + units/{civ}/support_healer_b + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml @@ -37,11 +37,11 @@ - + outpost_vision - + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_palisade.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_palisade.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_palisade.xml @@ -18,6 +18,7 @@ + @@ -33,4 +34,5 @@ interface/complete/building/complete_wall.xml + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml @@ -60,11 +60,11 @@ - + tower_health - + attack/impact/siegeprojectilehit.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml @@ -57,11 +57,11 @@ - + tower_health - + attack/weapon/arrowfly.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml @@ -38,11 +38,11 @@ - + tower_watch - + false 16 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml @@ -37,7 +37,7 @@ - + tower_watch tower_crenellations @@ -45,7 +45,7 @@ tower_murderholes tower_health - + false 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml @@ -23,6 +23,7 @@ 4.5 + @@ -34,4 +35,5 @@ 20 65535 + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml @@ -34,7 +34,8 @@ 20 - + + gather_wicker_baskets gather_farming_plows @@ -42,8 +43,7 @@ gather_farming_fertilizer gather_farming_harvester - - + food true Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml @@ -39,18 +39,14 @@ - - 0.7 + trader_health trade_gain_01 trade_gain_02 trade_commercial_treaty - - units/{civ}/support_trader - - + @@ -64,6 +60,12 @@ 40 30000 + + 0.7 + + units/{civ}/support_trader + + 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml @@ -35,7 +35,8 @@ - + + gather_lumbering_ironaxes gather_lumbering_strongeraxes @@ -50,8 +51,7 @@ gather_capacity_wheelbarrow gather_capacity_carts - - + wood stone metal true Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_arsenal.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_arsenal.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_arsenal.xml @@ -35,18 +35,7 @@ - - 0.7 - - units/{civ}/champion_infantry_crossbowman - units/{civ}/siege_scorpio_packed - units/{civ}/siege_polybolos_packed - units/{civ}/siege_oxybeles_packed - units/{civ}/siege_lithobolos_packed - units/{civ}/siege_ballista_packed - units/{civ}/siege_ram - units/{civ}/siege_tower - + siege_attack siege_cost_time @@ -54,7 +43,7 @@ siege_pack_unpack siege_bolt_accuracy - + interface/complete/building/complete_barracks.xml @@ -64,6 +53,19 @@ 38 + + 0.7 + + units/{civ}/champion_infantry_crossbowman + units/{civ}/siege_scorpio_packed + units/{civ}/siege_polybolos_packed + units/{civ}/siege_oxybeles_packed + units/{civ}/siege_lithobolos_packed + units/{civ}/siege_ballista_packed + units/{civ}/siege_ram + units/{civ}/siege_tower + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml @@ -37,7 +37,21 @@ - + + + barracks_batch_training + infantry_cost_time + unlock_champion_infantry + pair_unlock_champions_sele + + + + + + interface/complete/building/complete_barracks.xml + + + 0.8 units/{civ}/infantry_spearman_b @@ -57,19 +71,7 @@ units/{civ}/champion_infantry_slinger units/{civ}/champion_infantry_archer - - barracks_batch_training - infantry_cost_time - unlock_champion_infantry - pair_unlock_champions_sele - - - - - - interface/complete/building/complete_barracks.xml - - + 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml @@ -41,16 +41,10 @@ true 0.0 - - 0.8 - - units/{civ}/ship_fishing - units/{civ}/ship_merchant - units/{civ}/ship_bireme - units/{civ}/ship_trireme - units/{civ}/ship_quinquereme - units/{civ}/ship_fire - + + ship + + fishing_boat_gather_rate fishing_boat_gather_capacity @@ -59,10 +53,7 @@ ship_movement_speed equine_transports - - - ship - + food wood stone metal true @@ -75,6 +66,17 @@ + + 0.8 + + units/{civ}/ship_fishing + units/{civ}/ship_merchant + units/{civ}/ship_bireme + units/{civ}/ship_trireme + units/{civ}/ship_quinquereme + units/{civ}/ship_fire + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_elephant_stable.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_elephant_stable.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_elephant_stable.xml @@ -37,14 +37,6 @@ - - 0.7 - - units/{civ}/support_elephant - units/{civ}/elephant_archer_b - units/{civ}/champion_elephant - - interface/complete/building/complete_elephant_stable.xml @@ -54,6 +46,14 @@ 38 + + 0.7 + + units/{civ}/support_elephant + units/{civ}/elephant_archer_b + units/{civ}/champion_elephant + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_embassy.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_embassy.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_embassy.xml @@ -27,9 +27,6 @@ - - 0.8 - @@ -46,6 +43,9 @@ 25 + + 0.8 + 24 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_forge.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_forge.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_forge.xml @@ -33,7 +33,7 @@ - + soldier_attack_melee_01 soldier_attack_melee_02 @@ -50,7 +50,7 @@ soldier_resistance_pierce_03 archer_attack_spread - + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml @@ -24,11 +24,6 @@ - - 4 - 1 - Soldier - Fortress @@ -36,6 +31,11 @@ 80 + + 4 + 1 + Soldier + 8 10.0 @@ -77,12 +77,11 @@ - - 0.8 + attack_soldiers_will - + @@ -97,6 +96,9 @@ 80 + + 0.8 + 90 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_range.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_range.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_range.xml @@ -32,20 +32,20 @@ - - 0.8 - - units/{civ}/infantry_javelineer_b - units/{civ}/infantry_slinger_b - units/{civ}/infantry_archer_b - - interface/complete/building/complete_range.xml + + 0.8 + + units/{civ}/infantry_javelineer_b + units/{civ}/infantry_slinger_b + units/{civ}/infantry_archer_b + + 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_stable.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_stable.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_stable.xml @@ -37,18 +37,7 @@ - - 0.8 - - units/{civ}/cavalry_axeman_b - units/{civ}/cavalry_swordsman_b - units/{civ}/cavalry_spearman_b - units/{civ}/cavalry_javelineer_b - units/{civ}/cavalry_archer_b - units/{civ}/champion_cavalry - units/{civ}/champion_chariot - units/{civ}/war_dog - + stable_batch_training cavalry_cost_time @@ -58,13 +47,26 @@ unlock_champion_cavalry unlock_champion_chariots - + interface/complete/building/complete_stable.xml + + 0.8 + + units/{civ}/cavalry_axeman_b + units/{civ}/cavalry_swordsman_b + units/{civ}/cavalry_spearman_b + units/{civ}/cavalry_javelineer_b + units/{civ}/cavalry_archer_b + units/{civ}/champion_cavalry + units/{civ}/champion_chariot + units/{civ}/war_dog + + 32 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml @@ -28,18 +28,11 @@ - - 0.7 - - gaia/fauna_goat_trainable - gaia/fauna_sheep_trainable - gaia/fauna_pig_trainable - gaia/fauna_cattle_cow_trainable - + gather_animals_stockbreeding - + @@ -58,6 +51,15 @@ 20 30000 + + 0.7 + + gaia/fauna_goat_trainable + gaia/fauna_sheep_trainable + gaia/fauna_pig_trainable + gaia/fauna_cattle_cow_trainable + + 20 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml @@ -34,6 +34,7 @@ + @@ -59,6 +60,7 @@ 8.0 + 0 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_amphitheater.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_amphitheater.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_amphitheater.xml @@ -27,13 +27,6 @@ - - 0.8 - - units/{civ}/champion_infantry_spear_gladiator - units/{civ}/champion_infantry_sword_gladiator - - @@ -45,6 +38,13 @@ 100 40000 + + 0.8 + + units/{civ}/champion_infantry_spear_gladiator + units/{civ}/champion_infantry_sword_gladiator + + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_library.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_library.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_library.xml @@ -35,6 +35,7 @@ + @@ -46,6 +47,7 @@ 50 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_rotarymill.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_rotarymill.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_rotarymill.xml @@ -30,6 +30,7 @@ + food true @@ -48,6 +49,7 @@ 32 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_theater.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_theater.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special_theater.xml @@ -36,6 +36,7 @@ + @@ -47,6 +48,7 @@ 100 40000 + 40 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml @@ -50,11 +50,11 @@ - + wonder_population_cap - + Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/ship_trireme.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/ship_trireme.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/athen/ship_trireme.xml @@ -9,20 +9,14 @@ Athenian Trireme units/hele_ship_trireme.png - + + 0.7 units/athen/infantry_marine_archer_b units/athen/champion_marine - - 1.0 - 1.0 - 1.0 - 1.0 - - - + structures/athenians/trireme.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/hero_cyrus_ii.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/hero_cyrus_ii.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/hero_cyrus_ii.xml @@ -10,19 +10,13 @@ Kuruš units/pers_hero_cyrus.png - + + 0.7 units/pers/champion_infantry - - 1.0 - 1.0 - 1.0 - 1.0 - - - + units/persians/hero_cavalry_spearman_cyrus_m.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/ship_trireme.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/ship_trireme.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/pers/ship_trireme.xml @@ -11,20 +11,14 @@ Hamaraniyanava Vazarka units/pers_ship_trireme.png - + + 0.8 units/pers/cavalry_axeman_b_trireme units/pers/cavalry_javelineer_b_trireme - - 1.0 - 1.0 - 1.0 - 1.0 - - - + structures/persians/trireme.xml