From 755e407aeba7f402189fb3ed272c9bf54155c47a Mon Sep 17 00:00:00 2001 From: quantumstate Date: Fri, 20 Apr 2012 17:21:04 +0000 Subject: [PATCH] Technologies. Refs #3. Full unlocking technology implementation. Only unit gathering rates can be modified currently because the patch was big enough already. This was SVN commit r11584. --- .../portraits/technologies/city_phase.png | 3 + .../session/portraits/technologies/plough.png | 3 + .../portraits/technologies/town_phase.png | 3 + .../data/mods/public/gui/session/input.js | 20 +- .../data/mods/public/gui/session/session.js | 21 +- .../data/mods/public/gui/session/session.xml | 12 +- .../mods/public/gui/session/unit_commands.js | 112 +++- .../simulation/ai/common-api-v2/base.js | 2 +- .../simulation/ai/common-api-v2/entity.js | 4 +- .../public/simulation/ai/common-api/base.js | 2 +- .../public/simulation/ai/common-api/entity.js | 4 +- .../public/simulation/components/AIProxy.js | 14 +- .../simulation/components/GuiInterface.js | 97 +++- .../public/simulation/components/Identity.js | 7 +- .../public/simulation/components/Player.js | 24 +- .../simulation/components/ProductionQueue.js | 538 ++++++++++++++++++ .../simulation/components/ResourceGatherer.js | 48 +- .../components/TechnologyManager.js | 320 +++++++++++ .../components/TechnologyTemplateManager.js | 39 ++ .../simulation/components/TrainingQueue.js | 400 ------------- .../{TrainingQueue.js => ProductionQueue.js} | 4 +- .../interfaces/TechnologyManager.js | 5 + .../interfaces/TechnologyTemplateManager.js | 1 + .../components/tests/test_GuiInterface.js | 26 +- .../data/technologies/city_phase.json | 13 + .../simulation/data/technologies/plough.json | 12 + .../data/technologies/town_phase.json | 11 + .../data/technologies/village_phase.json | 4 + .../public/simulation/helpers/Commands.js | 60 +- .../campaigns/campaign_city_minor_test.xml | 4 +- .../campaigns/campaign_city_test.xml | 4 +- .../campaigns/campaign_religious_test.xml | 2 +- .../templates/other/cart_tophet.xml | 4 +- .../templates/other/celt_homestead.xml | 4 +- .../templates/other/celt_tavern.xml | 4 +- .../templates/other/hellenic_royal_stoa.xml | 4 +- .../templates/other/pers_apartment_block.xml | 4 +- .../simulation/templates/other/pers_inn.xml | 4 +- .../simulation/templates/special/player.xml | 1 + .../templates/structures/athen_barracks.xml | 4 +- .../structures/athen_civil_centre.xml | 4 +- .../templates/structures/athen_corral.xml | 4 +- .../templates/structures/athen_dock.xml | 4 +- .../templates/structures/athen_fortress.xml | 4 +- .../templates/structures/athen_gymnasion.xml | 4 +- .../templates/structures/athen_prytaneion.xml | 4 +- .../templates/structures/cart_barracks.xml | 4 +- .../structures/cart_civil_centre.xml | 4 +- .../templates/structures/cart_corral.xml | 4 +- .../templates/structures/cart_embassy.xml | 4 +- .../structures/cart_embassy_celtic.xml | 4 +- .../structures/cart_embassy_iberian.xml | 4 +- .../structures/cart_embassy_italiote.xml | 4 +- .../templates/structures/cart_fortress.xml | 4 +- .../templates/structures/cart_super_dock.xml | 4 +- .../templates/structures/cart_temple.xml | 4 +- .../templates/structures/celt_barracks.xml | 4 +- .../structures/celt_civil_centre.xml | 4 +- .../templates/structures/celt_corral.xml | 4 +- .../templates/structures/celt_dock.xml | 4 +- .../templates/structures/celt_fortress_b.xml | 4 +- .../templates/structures/celt_fortress_g.xml | 4 +- .../templates/structures/celt_kennel.xml | 4 +- .../templates/structures/hele_barracks.xml | 4 +- .../structures/hele_civil_centre.xml | 4 +- .../templates/structures/hele_corral.xml | 4 +- .../templates/structures/hele_dock.xml | 4 +- .../templates/structures/hele_fortress.xml | 4 +- .../templates/structures/hele_gymnasion.xml | 4 +- .../templates/structures/hele_prytaneion.xml | 4 +- .../templates/structures/iber_barracks.xml | 4 +- .../structures/iber_civil_centre.xml | 4 +- .../templates/structures/iber_corral.xml | 4 +- .../templates/structures/iber_dock.xml | 4 +- .../templates/structures/iber_fortress.xml | 4 +- .../templates/structures/mace_barracks.xml | 4 +- .../structures/mace_civil_centre.xml | 4 +- .../templates/structures/mace_corral.xml | 4 +- .../templates/structures/mace_dock.xml | 4 +- .../templates/structures/mace_fortress.xml | 4 +- .../templates/structures/pers_apadana.xml | 4 +- .../templates/structures/pers_barracks.xml | 4 +- .../structures/pers_civil_centre.xml | 4 +- .../templates/structures/pers_corral.xml | 4 +- .../templates/structures/pers_dock.xml | 4 +- .../templates/structures/pers_fortress.xml | 4 +- .../templates/structures/pers_sb2.xml | 4 +- .../templates/structures/pers_stables.xml | 4 +- .../templates/structures/rome_army_camp.xml | 6 +- .../templates/structures/rome_barracks.xml | 4 +- .../structures/rome_civil_centre.xml | 4 +- .../templates/structures/rome_corral.xml | 4 +- .../templates/structures/rome_dock.xml | 4 +- .../templates/structures/rome_fortress.xml | 4 +- .../templates/structures/spart_barracks.xml | 4 +- .../structures/spart_civil_centre.xml | 4 +- .../templates/structures/spart_corral.xml | 4 +- .../templates/structures/spart_dock.xml | 4 +- .../templates/structures/spart_fortress.xml | 4 +- .../templates/structures/spart_gerousia.xml | 4 +- .../templates/structures/spart_syssiton.xml | 4 +- .../template_structure_civic_civil_centre.xml | 10 +- .../template_structure_civic_temple.xml | 4 +- .../template_structure_economic_market.xml | 4 +- .../template_structure_military_dock.xml | 4 +- .../templates/units/athen_ship_trireme.xml | 4 +- .../templates/units/pers_hero_cyrus.xml | 4 +- .../units/pers_infantry_spearman_b.xml | 4 +- source/simulation2/Simulation2.cpp | 1 + .../simulation2/system/ComponentManager.cpp | 32 ++ source/simulation2/system/ComponentManager.h | 1 + 111 files changed, 1510 insertions(+), 652 deletions(-) create mode 100644 binaries/data/mods/public/art/textures/ui/session/portraits/technologies/city_phase.png create mode 100644 binaries/data/mods/public/art/textures/ui/session/portraits/technologies/plough.png create mode 100644 binaries/data/mods/public/art/textures/ui/session/portraits/technologies/town_phase.png create mode 100644 binaries/data/mods/public/simulation/components/ProductionQueue.js create mode 100644 binaries/data/mods/public/simulation/components/TechnologyManager.js create mode 100644 binaries/data/mods/public/simulation/components/TechnologyTemplateManager.js delete mode 100644 binaries/data/mods/public/simulation/components/TrainingQueue.js rename binaries/data/mods/public/simulation/components/interfaces/{TrainingQueue.js => ProductionQueue.js} (76%) create mode 100644 binaries/data/mods/public/simulation/components/interfaces/TechnologyManager.js create mode 100644 binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js create mode 100644 binaries/data/mods/public/simulation/data/technologies/city_phase.json create mode 100644 binaries/data/mods/public/simulation/data/technologies/plough.json create mode 100644 binaries/data/mods/public/simulation/data/technologies/town_phase.json create mode 100644 binaries/data/mods/public/simulation/data/technologies/village_phase.json diff --git a/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/city_phase.png b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/city_phase.png new file mode 100644 index 0000000000..fcddffd9fb --- /dev/null +++ b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/city_phase.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7800e081cc635df58bae244a6032c3b68835d94c13a4f3f87f73df772e76df01 +size 26378 diff --git a/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/plough.png b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/plough.png new file mode 100644 index 0000000000..f17cf8fae3 --- /dev/null +++ b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/plough.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15648a192b4b11e3a886b8d88c2a80c21d3125c5a0d94c32f8ad7f975340d20d +size 32497 diff --git a/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/town_phase.png b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/town_phase.png new file mode 100644 index 0000000000..5dafffba5f --- /dev/null +++ b/binaries/data/mods/public/art/textures/ui/session/portraits/technologies/town_phase.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da042927a68c6ecd23daac4c716e9cd19305248d686c559dbc201e8b6945e0e1 +size 28558 diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index a723edbd7d..12f2a07303 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -764,7 +764,7 @@ function handleInputBeforeGui(ev, hoveredObject) case "hotkeyup": if (ev.hotkey == "session.batchtrain") { - flushTrainingQueueBatch(); + flushTrainingBatch(); inputState = INPUT_NORMAL; } break; @@ -1202,13 +1202,13 @@ var batchTrainingType; var batchTrainingCount; const batchIncrementSize = 5; -function flushTrainingQueueBatch() +function flushTrainingBatch() { Engine.PostNetworkCommand({"type": "train", "entity": batchTrainingEntity, "template": batchTrainingType, "count": batchTrainingCount}); } // Called by GUI when user clicks training button -function addToTrainingQueue(entity, trainEntType) +function addTrainingToQueue(entity, trainEntType) { if (Engine.HotkeyIsPressed("session.batchtrain")) { @@ -1223,7 +1223,7 @@ function addToTrainingQueue(entity, trainEntType) // Otherwise start a new one else { - flushTrainingQueueBatch(); + flushTrainingBatch(); // fall through to create the new batch } } @@ -1239,9 +1239,15 @@ function addToTrainingQueue(entity, trainEntType) } } +// Called by GUI when user clicks research button +function addResearchToQueue(entity, researchType) +{ + Engine.PostNetworkCommand({"type": "research", "entity": entity, "template": researchType}); +} + // Returns the number of units that will be present in a batch if the user clicks // the training button with shift down -function getTrainingQueueBatchStatus(entity, trainEntType) +function getTrainingBatchStatus(entity, trainEntType) { if (inputState == INPUT_BATCHTRAINING && batchTrainingEntity == entity && batchTrainingType == trainEntType) return [batchTrainingCount, batchIncrementSize]; @@ -1250,9 +1256,9 @@ function getTrainingQueueBatchStatus(entity, trainEntType) } // Called by GUI when user clicks production queue item -function removeFromTrainingQueue(entity, id) +function removeFromProductionQueue(entity, id) { - Engine.PostNetworkCommand({"type": "stop-train", "entity": entity, "id": id}); + Engine.PostNetworkCommand({"type": "stop-production", "entity": entity, "id": id}); } // Called by unit selection buttons diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index a32b36931d..d43fb59f85 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -15,7 +15,7 @@ var g_DevSettings = { // Indicate when one of the current player's training queues is blocked // (this is used to support population counter blinking) -var g_IsTrainingQueueBlocked = false; +var g_IsTrainingBlocked = false; // Cache EntityStates var g_EntityStates = {}; // {id:entState} @@ -53,6 +53,20 @@ function GetTemplateData(templateName) return g_TemplateData[templateName]; } +// Cache TechnologyData +var g_TechnologyData = {}; // {id:template} + +function GetTechnologyData(technologyName) +{ + if (!(technologyName in g_TechnologyData)) + { + var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName); + g_TechnologyData[technologyName] = template; + } + + return g_TechnologyData[technologyName]; +} + // Init function init(initData, hotloadData) { @@ -210,7 +224,7 @@ function onTick() global.music.updateTimer(); // When training is blocked, flash population (alternates colour every 500msec) - if (g_IsTrainingQueueBlocked && (Date.now() % 1000) < 500) + if (g_IsTrainingBlocked && (Date.now() % 1000) < 500) getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR; else getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR; @@ -283,6 +297,7 @@ function onSimulationUpdate() g_Selection.dirty = false; g_EntityStates = {}; g_TemplateData = {}; + g_TechnologyData = {}; var simState = Engine.GuiInterfaceCall("GetSimulationState"); @@ -371,7 +386,7 @@ function updatePlayerDisplay(simState) getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal; getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit; - g_IsTrainingQueueBlocked = playerState.trainingQueueBlocked; + g_IsTrainingBlocked = playerState.trainingBlocked; } function updateTimeElapsedCounter(simState) diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 5de7e2368c..84ccb5e44d 100644 --- a/binaries/data/mods/public/gui/session/session.xml +++ b/binaries/data/mods/public/gui/session/session.xml @@ -727,11 +727,15 @@ - + + + [research commands] diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js index 8e8e2da0bb..f8bcb31cfb 100644 --- a/binaries/data/mods/public/gui/session/unit_commands.js +++ b/binaries/data/mods/public/gui/session/unit_commands.js @@ -4,6 +4,7 @@ const QUEUE = "Queue"; const GARRISON = "Garrison"; const FORMATION = "Formation"; const TRAINING = "Training"; +const RESEARCH = "Research"; const CONSTRUCTION = "Construction"; const COMMAND = "Command"; const STANCE = "Stance"; @@ -23,7 +24,7 @@ const BARTER_RESOURCES = ["food", "wood", "stone", "metal"]; const BARTER_ACTIONS = ["Sell", "Buy"]; // The number of currently visible buttons (used to optimise showing/hiding) -var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0}; +var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0}; // Unit panels are panels with row(s) of buttons var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command"]; @@ -163,6 +164,11 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) if (numberOfItems > 24) numberOfItems = 24; break; + + case RESEARCH: + if (numberOfItems > 8) + numberOfItems = 8; + break; case CONSTRUCTION: if (numberOfItems > 24) @@ -183,13 +189,54 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) for (i = 0; i < numberOfItems; i++) { var item = items[i]; - var entType = ((guiName == "Queue")? item.template : item); - var template; - if (guiName != "Formation" && guiName != "Command" && guiName != "Stance") + + // If a tech has been researched it leaves an empty slot + if (guiName == RESEARCH && !item) { - template = GetTemplateData(entType); - if (!template) - continue; // ignore attempts to use invalid templates (an error should have been reported already) + getGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true; + continue; + } + + // Get the entity type and load the template for that type if necessary + var entType; + var template; + switch (guiName) + { + case QUEUE: + // The queue can hold both technologies and units so we need to use the correct code for + // loading the templates + if (item.unitTemplate) + { + entType = item.unitTemplate; + template = GetTemplateData(entType); + } + else if (item.technologyTemplate) + { + entType = item.technologyTemplate; + template = GetTechnologyData(entType); + } + + if (!template) + continue; // ignore attempts to use invalid templates (an error should have been + // reported already) + break; + case RESEARCH: + entType = item; + template = GetTechnologyData(entType); + if (!template) + continue; // ignore attempts to use invalid templates (an error should have been + // reported already) + break; + case SELECTION: + case GARRISON: + case TRAINING: + case CONSTRUCTION: + entType = item; + template = GetTemplateData(entType); + if (!template) + continue; // ignore attempts to use invalid templates (an error should have been + // reported already) + break; } switch (guiName) @@ -233,7 +280,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) if (template.tooltip) tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]"; - var [batchSize, batchIncrement] = getTrainingQueueBatchStatus(unitEntState.id, entType); + var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType); var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement; tooltip += "\n" + getEntityCost(template); @@ -250,6 +297,15 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) tooltip += "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train " + trainNum + ".[/font]"; break; + + case RESEARCH: + var tooltip = getEntityNameWithGenericType(template); + + if (template.tooltip) + tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]"; + + tooltip += "\n" + getEntityCost(template); + break; case CONSTRUCTION: var tooltip = getEntityNameWithGenericType(template); @@ -352,7 +408,25 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) } else if (template.icon) { - icon.sprite = "stretched:session/portraits/" + template.icon; + var grayscale = ""; + button.enabled = true; + + if (template.requiredTechnology && !Engine.GuiInterfaceCall("IsTechnologyResearched", template.requiredTechnology)) + { + button.enabled = false; + var techName = getEntityName(GetTechnologyData(template.requiredTechnology)); + button.tooltip += "\nRequires " + techName; + grayscale = "grayscale:"; + } + + if (guiName == RESEARCH && !Engine.GuiInterfaceCall("CheckTechnologyRequirements", entType)) + { + button.enabled = false; + button.tooltip += "\n" + GetTechnologyData(entType).requirementsTooltip; + grayscale = "grayscale:"; + } + + icon.sprite = "stretched:" + grayscale + "session/portraits/" + template.icon; } else { @@ -537,16 +611,22 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s { setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement); } - - if (entState.training && entState.training.entities.length) + + if (entState.production && entState.production.entities.length) { - setupUnitPanel("Training", usedPanels, entState, entState.training.entities, - function (trainEntType) { addToTrainingQueue(entState.id, trainEntType); } ); + setupUnitPanel("Training", usedPanels, entState, entState.production.entities, + function (trainEntType) { addTrainingToQueue(entState.id, trainEntType); } ); + } + + if (entState.production && entState.production.technologies.length) + { + setupUnitPanel("Research", usedPanels, entState, entState.production.technologies, + function (researchType) { addResearchToQueue(entState.id, researchType); } ); } - if (entState.training && entState.training.queue.length) - setupUnitPanel("Queue", usedPanels, entState, entState.training.queue, - function (item) { removeFromTrainingQueue(entState.id, item.id); } ); + if (entState.production && entState.production.queue.length) + setupUnitPanel("Queue", usedPanels, entState, entState.production.queue, + function (item) { removeFromProductionQueue(entState.id, item.id); } ); if (entState.trader) { diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/base.js b/binaries/data/mods/public/simulation/ai/common-api-v2/base.js index 6937c95a32..67f30de616 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/base.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v2/base.js @@ -44,7 +44,7 @@ BaseAI.prototype.Deserialize = function(data) // CCmpTemplateManager::CopyFoundationSubset and only includes components // that our EntityTemplate class currently uses.) var g_FoundationForbiddenComponents = { - "TrainingQueue": 1, + "ProductionQueue": 1, "ResourceSupply": 1, "ResourceDropsite": 1, "GarrisonHolder": 1, diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js b/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js index 527e531c69..3025cf4b3e 100644 --- a/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js @@ -127,10 +127,10 @@ var EntityTemplate = Class({ }, trainableEntities: function() { - if (!this._template.TrainingQueue) + if (!this._template.ProductionQueue) return undefined; var civ = this.civ(); - var templates = this._template.TrainingQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); + var templates = this._template.ProductionQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); return templates; }, diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js index 08031f09a1..481db6367a 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/base.js +++ b/binaries/data/mods/public/simulation/ai/common-api/base.js @@ -43,7 +43,7 @@ BaseAI.prototype.Deserialize = function(data) // CCmpTemplateManager::CopyFoundationSubset and only includes components // that our EntityTemplate class currently uses.) var g_FoundationForbiddenComponents = { - "TrainingQueue": 1, + "ProductionQueue": 1, "ResourceSupply": 1, "ResourceDropsite": 1, "GarrisonHolder": 1, diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js index af3784c001..29524f1a52 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -126,10 +126,10 @@ var EntityTemplate = Class({ }, trainableEntities: function() { - if (!this._template.TrainingQueue) + if (!this._template.ProductionQueue) return undefined; var civ = this.civ(); - var templates = this._template.TrainingQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); + var templates = this._template.ProductionQueue.Entities._string.replace(/\{civ\}/g, civ).split(/\s+/); return templates; }, diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js index 83824a97c2..aa73f4fb55 100644 --- a/binaries/data/mods/public/simulation/components/AIProxy.js +++ b/binaries/data/mods/public/simulation/components/AIProxy.js @@ -119,12 +119,12 @@ AIProxy.prototype.OnUnitAIOrderDataChanged = function(msg) this.changes.unitAIOrderData = msg.to; }; -AIProxy.prototype.OnTrainingQueueChanged = function(msg) +AIProxy.prototype.OnProductionQueueChanged = function(msg) { this.NotifyChange(); - var cmpTrainingQueue = Engine.QueryInterface(this.entity, IID_TrainingQueue); - this.changes.trainingQueue = cmpTrainingQueue.GetQueue(); + var cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue); + this.changes.trainingQueue = cmpProductionQueue.GetQueue(); }; AIProxy.prototype.OnGarrisonedUnitsChanged = function(msg) @@ -206,11 +206,11 @@ AIProxy.prototype.GetFullRepresentation = function() ret.unitAIOrderData = cmpUnitAI.GetOrderData(); } - var cmpTrainingQueue = Engine.QueryInterface(this.entity, IID_TrainingQueue); - if (cmpTrainingQueue) + var cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue); + if (cmpProductionQueue) { - // Updated by OnTrainingQueueChanged - ret.trainingQueue = cmpTrainingQueue.GetQueue(); + // Updated by OnProductionQueueChanged + ret.trainingQueue = cmpProductionQueue.GetQueue(); } var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation); diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 03228c0882..737bb7a380 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -50,6 +50,16 @@ GuiInterface.prototype.GetSimulationState = function(player) var cmpPlayerBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits); var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player); + // Work out what phase we are in + var cmpTechMan = Engine.QueryInterface(playerEnt, IID_TechnologyManager); + var phase = ""; + if (cmpTechMan.IsTechnologyResearched("city")) + phase = "city"; + else if (cmpTechMan.IsTechnologyResearched("town")) + phase = "town"; + else if (cmpTechMan.IsTechnologyResearched("village")) + phase = "village"; + // store player ally/enemy data as arrays var allies = []; var enemies = []; @@ -66,10 +76,10 @@ GuiInterface.prototype.GetSimulationState = function(player) "popLimit": cmpPlayer.GetPopulationLimit(), "popMax": cmpPlayer.GetMaxPopulation(), "resourceCounts": cmpPlayer.GetResourceCounts(), - "trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(), + "trainingBlocked": cmpPlayer.IsTrainingBlocked(), "state": cmpPlayer.GetState(), "team": cmpPlayer.GetTeam(), - "phase": cmpPlayer.GetPhase(), + "phase": phase, "isAlly": allies, "isEnemy": enemies, "buildLimits": cmpPlayerBuildLimits.GetLimits(), @@ -177,12 +187,13 @@ GuiInterface.prototype.GetEntityState = function(player, ent) ret.buildEntities = cmpBuilder.GetEntitiesList(); } - var cmpTrainingQueue = Engine.QueryInterface(ent, IID_TrainingQueue); - if (cmpTrainingQueue) + var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue); + if (cmpProductionQueue) { - ret.training = { - "entities": cmpTrainingQueue.GetEntitiesList(), - "queue": cmpTrainingQueue.GetQueue(), + ret.production = { + "entities": cmpProductionQueue.GetEntitiesList(), + "technologies": cmpProductionQueue.GetTechnologiesList(), + "queue": cmpProductionQueue.GetQueue(), }; } @@ -346,6 +357,7 @@ GuiInterface.prototype.GetTemplateData = function(player, name) }; ret.icon = template.Identity.Icon; ret.tooltip = template.Identity.Tooltip; + ret.requiredTechnology = template.Identity.RequiredTechnology; } if (template.UnitMotion) @@ -359,6 +371,74 @@ GuiInterface.prototype.GetTemplateData = function(player, name) return ret; }; +GuiInterface.prototype.GetTechnologyData = function(player, name) +{ + var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager); + var template = cmpTechTempMan.GetTemplate(name); + + if (!template) + { + warn("Tried to get data for invalid technology: " + name); + return null; + } + + var ret = {}; + + // Get specific name for this civ or else the generic specific name + var cmpPlayer = QueryPlayerIDInterface(player, IID_Player); + var specific = undefined; + if (template.specificName) + { + if (template.specificName[cmpPlayer.GetCiv()]) + specific = template.specificName[cmpPlayer.GetCiv()]; + else + specific = template.specificName['generic']; + } + + ret.name = { + "specific": specific, + "generic": template.genericName, + }; + ret.icon = "technologies/" + template.icon; + ret.cost = { + "food": +template.cost.food, + "wood": +template.cost.wood, + "metal": +template.cost.metal, + "stone": +template.cost.stone, + } + ret.tooltip = template.tooltip; + + if (template.requirementsTooltip) + ret.requirementsTooltip = template.requirementsTooltip; + else + ret.requirementsTooltip = ""; + + ret.description = template.description; + + return ret; +}; + +GuiInterface.prototype.IsTechnologyResearched = function(player, tech) +{ + var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); + + if (!cmpTechMan) + return false; + + return cmpTechMan.IsTechnologyResearched(tech); +}; + +// Checks whether the requirements for this technology have been met +GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech) +{ + var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); + + if (!cmpTechMan) + return false; + + return cmpTechMan.CanResearch(tech); +}; + GuiInterface.prototype.PushNotification = function(notification) { this.notifications.push(notification); @@ -812,6 +892,9 @@ var exposedFunctions = { "ClearRenamedEntities": 1, "GetEntityState": 1, "GetTemplateData": 1, + "GetTechnologyData": 1, + "IsTechnologyResearched": 1, + "CheckTechnologyRequirements": 1, "GetNextNotification": 1, "GetFormationRequirements": 1, diff --git a/binaries/data/mods/public/simulation/components/Identity.js b/binaries/data/mods/public/simulation/components/Identity.js index 5f2fdc9e3c..b2c905995a 100644 --- a/binaries/data/mods/public/simulation/components/Identity.js +++ b/binaries/data/mods/public/simulation/components/Identity.js @@ -68,6 +68,11 @@ Identity.prototype.Schema = "" + "" + "" + + "" + + "" + + "" + + "" + + "" + ""; @@ -126,6 +131,6 @@ Identity.prototype.CanUseFormation = function(name) Identity.prototype.GetSelectionGroupName = function() { return (this.template.SelectionGroupName || ""); -} +}; Engine.RegisterComponentType(IID_Identity, "Identity", Identity); diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js index 27041b2cf7..ca01b9866a 100644 --- a/binaries/data/mods/public/simulation/components/Player.js +++ b/binaries/data/mods/public/simulation/components/Player.js @@ -12,7 +12,7 @@ Player.prototype.Init = function() this.popUsed = 0; // population of units owned or trained by this player this.popBonuses = 0; // sum of population bonuses of player's entities this.maxPop = 300; // maximum population - this.trainingQueueBlocked = false; // indicates whether any training queue is currently blocked + this.trainingBlocked = false; // indicates whether any training queue is currently blocked this.resourceCount = { "food": 1000, "wood": 1000, @@ -104,19 +104,19 @@ Player.prototype.GetMaxPopulation = function() return this.maxPop; }; -Player.prototype.IsTrainingQueueBlocked = function() +Player.prototype.IsTrainingBlocked = function() { - return this.trainingQueueBlocked; + return this.trainingBlocked; }; -Player.prototype.BlockTrainingQueue = function() +Player.prototype.BlockTraining = function() { - this.trainingQueueBlocked = true; + this.trainingBlocked = true; }; -Player.prototype.UnBlockTrainingQueue = function() +Player.prototype.UnBlockTraining = function() { - this.trainingQueueBlocked = false; + this.trainingBlocked = false; }; Player.prototype.SetResourceCounts = function(resources) @@ -223,16 +223,6 @@ Player.prototype.SetDiplomacy = function(dipl) this.diplomacy = dipl; }; -Player.prototype.GetPhase = function() -{ - return this.phase; -}; - -Player.prototype.SetPhase = function(p) -{ - this.phase = p; -}; - Player.prototype.GetStartingCameraPos = function() { return this.startCam.position; diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js new file mode 100644 index 0000000000..5b6ee0edda --- /dev/null +++ b/binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -0,0 +1,538 @@ +var g_ProgressInterval = 1000; +const MAX_QUEUE_SIZE = 16; + +function ProductionQueue() {} + +ProductionQueue.prototype.Schema = + "Allows the building to train new units and research technologies" + + "" + + "" + + "\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + ""; + +ProductionQueue.prototype.Init = function() +{ + this.nextID = 1; + + this.queue = []; + // Queue items are: + // { + // "id": 1, + // "player": 1, // who paid for this batch; we need this to cope with refunds cleanly + // "unitTemplate": "units/example", + // "count": 10, + // "resources": { "wood": 100, ... }, // resources per unit, multiply by count to get total + // "population": 1, // population per unit, multiply by count to get total + // "productionStarted": false, // true iff we have reserved population + // "timeTotal": 15000, // msecs + // "timeRemaining": 10000, // msecs + // } + // + // { + // "id": 1, + // "player": 1, // who paid for this research; we need this to cope with refunds cleanly + // "technologyTemplate": "example_tech", + // "resources": { "wood": 100, ... }, // resources needed for research + // "productionStarted": false, // true iff production has started + // "timeTotal": 15000, // msecs + // "timeRemaining": 10000, // msecs + // } + + this.timer = undefined; // g_ProgressInterval msec timer, active while the queue is non-empty + + this.entityCache = []; + this.spawnNotified = false; +}; + +/* + * Returns list of entities that can be trained by this building. + */ +ProductionQueue.prototype.GetEntitiesList = function() +{ + if (!this.template.Entities) + return []; + + var string = this.template.Entities._string; + + // Replace the "{civ}" codes with this entity's civ ID + var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); + if (cmpIdentity) + string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv()); + + return string.split(/\s+/); +}; + +/* + * Returns list of technologies that can be researched by this building. + */ +ProductionQueue.prototype.GetTechnologiesList = function() +{ + if (!this.template.Technologies) + return []; + + var string = this.template.Technologies._string; + + var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager); + if (!cmpTechMan) + return []; + + var techs = string.split(/\s+/); + var ret = []; + var superseded = {}; // Stores the tech which supersedes the key + + // Add any top level technologies to an array which corresponds to the displayed icons + // Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...} + for (var i in techs) + { + var tech = techs[i]; + var template = cmpTechMan.GetTechnologyTemplate(tech); + if (!template.supersedes || techs.indexOf(template.supersedes) === -1) + ret.push(tech); + else + superseded[template.supersedes] = tech; + } + + // Now make researched/in progress techs invisible + for (var i in ret) + { + var tech = ret[i]; + while (cmpTechMan.IsTechnologyResearched(tech) || cmpTechMan.IsInProgress(tech)) + { + tech = superseded[tech]; + } + + + ret[i] = tech; + } + + return ret; +}; + +/* + * Adds a new batch of identical units to train or a technology to research to the production queue. + */ +ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadata) +{ + // TODO: there should probably be a limit on the number of queued batches + // 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) + var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + + if (this.queue.length < MAX_QUEUE_SIZE) + { + + if (type == "unit") + { + // Find the template data so we can determine the build costs + var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + var template = cmpTempMan.GetTemplate(templateName); + if (!template) + return; + + // Apply a time discount to larger batches. + // TODO: work out what equation we should use here. + var timeMult = Math.pow(count, 0.7); + + var time = timeMult * template.Cost.BuildTime; + + var totalCosts = {}; + for each (var r in ["food", "wood", "stone", "metal"]) + totalCosts[r] = Math.floor(count * template.Cost.Resources[r]); + + var population = template.Cost.Population; + + // TrySubtractResources should report error to player (they ran out of resources) + if (!cmpPlayer.TrySubtractResources(totalCosts)) + return; + + this.queue.push({ + "id": this.nextID++, + "player": cmpPlayer.GetPlayerID(), + "unitTemplate": templateName, + "count": count, + "metadata": metadata, + "resources": deepcopy(template.Cost.Resources), // need to copy to avoid serialization problems + "population": population, + "productionStarted": false, + "timeTotal": time*1000, + "timeRemaining": time*1000, + }); + } + else if (type == "technology") + { + // Load the technology template + var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager); + var template = cmpTechTempMan.GetTemplate(templateName); + if (!template) + return; + + var time = template.researchTime; + + var cost = {}; + for each (var r in ["food", "wood", "stone", "metal"]) + cost[r] = Math.floor(template.cost[r]); + + // TrySubtractResources should report error to player (they ran out of resources) + if (!cmpPlayer.TrySubtractResources(cost)) + return; + + // Tell the technology manager that we have started researching this so that people can't research the same + // thing twice. + var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager); + cmpTechMan.StartedResearch(templateName); + + this.queue.push({ + "id": this.nextID++, + "player": cmpPlayer.GetPlayerID(), + "count": 1, + "technologyTemplate": templateName, + "resources": deepcopy(template.cost), // need to copy to avoid serialization problems + "productionStarted": false, + "timeTotal": time*1000, + "timeRemaining": time*1000, + }); + } + else + { + warn("Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue"); + return; + } + + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); + + // If this is the first item in the queue, start the timer + if (!this.timer) + { + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, {}); + } + } + else + { + var notification = {"player": cmpPlayer.GetPlayerID(), "message": "The production queue is full."}; + var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + cmpGUIInterface.PushNotification(notification); + } +}; + +/* + * Removes an existing batch of units from the production queue. + * Refunds resource costs and population reservations. + */ +ProductionQueue.prototype.RemoveBatch = function(id) +{ + // Destroy any cached entities (those which didn't spawn for some reason) + for (var i = 0; i < this.entityCache.length; ++i) + { + Engine.DestroyEntity(this.entityCache[i]); + } + this.entityCache = []; + + for (var i = 0; i < this.queue.length; ++i) + { + var item = this.queue[i]; + if (item.id != id) + continue; + + // Now we've found the item to remove + + var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player); + + // Refund the resource cost for this batch + var totalCosts = {}; + for each (var r in ["food", "wood", "stone", "metal"]) + totalCosts[r] = Math.floor(item.count * item.resources[r]); + + cmpPlayer.AddResources(totalCosts); + + // Remove reserved population slots if necessary + if (item.productionStarted && item.unitTemplate) + cmpPlayer.UnReservePopulationSlots(item.population * item.count); + + // Mark the research as stopped if we cancel it + if (item.technologyTemplate) + { + var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager); + cmpTechMan.StoppedResearch(item.technologyTemplate); + } + + // Remove from the queue + // (We don't need to remove the timer - it'll expire if it discovers the queue is empty) + this.queue.splice(i, 1); + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); + + return; + } +}; + +/* + * Returns basic data from all batches in the production queue. + */ +ProductionQueue.prototype.GetQueue = function() +{ + var out = []; + for each (var item in this.queue) + { + out.push({ + "id": item.id, + "unitTemplate": item.unitTemplate, + "technologyTemplate": item.technologyTemplate, + "count": item.count, + "progress": 1-(item.timeRemaining/item.timeTotal), + "metadata": item.metadata, + }); + } + return out; +}; + +/* + * Removes all existing batches from the queue. + */ +ProductionQueue.prototype.ResetQueue = function() +{ + // Empty the production queue and refund all the resource costs + // to the player. (This is to avoid players having to micromanage their + // buildings' queues when they're about to be destroyed or captured.) + + while (this.queue.length) + this.RemoveBatch(this.queue[0].id); +}; + +ProductionQueue.prototype.OnOwnershipChanged = function(msg) +{ + if (msg.from != -1) + { + // Unset flag that previous owner's training may be blocked + var cmpPlayer = QueryPlayerIDInterface(msg.from, IID_Player); + if (cmpPlayer && this.queue.length > 0) + cmpPlayer.UnBlockTraining(); + } + + // 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.OnDestroy = function() +{ + // Reset the queue to refund any resources + this.ResetQueue(); + + if (this.timer) + { + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + } +}; + +/* + * This function creates the entities and places them in world if possible. + * returns the number of successfully spawned entities. + */ +ProductionQueue.prototype.SpawnUnits = function(templateName, count, metadata) +{ + var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); + var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); + + var spawnedEnts = []; + + if (this.entityCache.length == 0) + { + // We need entities to test spawning, but we don't want to waste resources, + // so only create them once and use as needed + for (var i = 0; i < count; ++i) + { + this.entityCache.push(Engine.AddEntity(templateName)); + } + } + + for (var i = 0; i < count; ++i) + { + var ent = this.entityCache[0]; + var pos = cmpFootprint.PickSpawnPoint(ent); + if (pos.y < 0) + { + // Fail: there wasn't any space to spawn the unit + break; + } + else + { + // Successfully spawned + var cmpNewPosition = Engine.QueryInterface(ent, IID_Position); + cmpNewPosition.JumpTo(pos.x, pos.z); + // TODO: what direction should they face in? + + var cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership); + cmpNewOwnership.SetOwner(cmpOwnership.GetOwner()); + + var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); + cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter(); + + // Play a sound, but only for the first in the batch (to avoid nasty phasing effects) + if (spawnedEnts.length == 0) + PlaySound("trained", ent); + + this.entityCache.shift(); + spawnedEnts.push(ent); + } + } + + if (spawnedEnts.length > 0) + { + // If a rally point is set, walk towards it (in formation) using a suitable command based on where the + // rally point is placed. + if (cmpRallyPoint) + { + var rallyPos = cmpRallyPoint.GetPosition(); + if (rallyPos) + { + ProcessCommand(cmpOwnership.GetOwner(), GetRallyPointCommand(cmpRallyPoint, spawnedEnts)); + } + } + + Engine.PostMessage(this.entity, MT_TrainingFinished, { + "entities": spawnedEnts, + "owner": cmpOwnership.GetOwner(), + "metadata": metadata, + }); + } + + return spawnedEnts.length; +}; + +/* + * Increments progress on the first batch in the production queue, and blocks the + * queue if population limit is reached or some units failed to spawn. + */ +ProductionQueue.prototype.ProgressTimeout = function(data) +{ + // Allocate the 1000msecs 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) + var time = g_ProgressInterval; + var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + + while (time > 0 && this.queue.length) + { + var item = this.queue[0]; + if (!item.productionStarted) + { + // If the item is a unit then do population checks + if (item.unitTemplate) + { + // Batch's training hasn't started yet. + // Try to reserve the necessary population slots + if (item.unitTemplate && !cmpPlayer.TryReservePopulationSlots(item.population * item.count)) + { + // No slots available - don't train this batch now + // (we'll try again on the next timeout) + + // Set flag that training is blocked + cmpPlayer.BlockTraining(); + break; + } + + // Unset flag that training is blocked + cmpPlayer.UnBlockTraining(); + } + + item.productionStarted = true; + } + + // If we won't finish the batch now, just update its timer + if (item.timeRemaining > time) + { + item.timeRemaining -= time; + break; + } + + if (item.unitTemplate) + { + var numSpawned = this.SpawnUnits(item.unitTemplate, item.count, item.metadata); + if (numSpawned == item.count) + { + // All entities spawned, this batch finished + cmpPlayer.UnReservePopulationSlots(item.population * numSpawned); + time -= item.timeRemaining; + this.queue.shift(); + // Unset flag that training is blocked + cmpPlayer.UnBlockTraining(); + this.spawnNotified = false; + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); + } + else + { + if (numSpawned > 0) + { + // Only partially finished + cmpPlayer.UnReservePopulationSlots(item.population * numSpawned); + item.count -= numSpawned; + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); + } + + // Some entities failed to spawn + // Set flag that training is blocked + cmpPlayer.BlockTraining(); + + if (!this.spawnNotified) + { + var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to spawn trained units" }; + var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + cmpGUIInterface.PushNotification(notification); + this.spawnNotified = true; + } + break; + } + } + else if (item.technologyTemplate) + { + var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager); + cmpTechnologyManager.ResearchTechnology(item.technologyTemplate); + + time -= item.timeRemaining; + + this.queue.shift(); + Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { }); + } + } + + // If the queue's empty, delete the timer, else repeat it + if (this.queue.length == 0) + { + this.timer = undefined; + + // Unset flag that training is blocked + // (This might happen when the player unqueues all batches) + cmpPlayer.UnBlockTraining(); + } + else + { + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, data); + } +} + +Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue); diff --git a/binaries/data/mods/public/simulation/components/ResourceGatherer.js b/binaries/data/mods/public/simulation/components/ResourceGatherer.js index 630c84a0b3..57af88cc04 100644 --- a/binaries/data/mods/public/simulation/components/ResourceGatherer.js +++ b/binaries/data/mods/public/simulation/components/ResourceGatherer.js @@ -125,19 +125,47 @@ ResourceGatherer.prototype.GetLastCarriedType = function() return undefined; }; +// Remove any cached template data which is based on technology data +ResourceGatherer.prototype.OnTechnologyModificationChange = function(msg) +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + + var player = cmpOwnership.GetOwner(); + + if (msg.component === "ResourceGatherer" && msg.player === player) + delete this.gatherRatesCache; +}; + +// Remove any cached template data which is based on technology data +ResourceGatherer.prototype.OnOwnershipChanged = function(msg) +{ + delete this.gatherRatesCache; +}; + ResourceGatherer.prototype.GetGatherRates = function() { - var ret = {}; - for (var r in this.template.Rates) - ret[r] = this.template.Rates[r] * this.template.BaseSpeed; - return ret; + if (!this.gatherRatesCache) + { + this.gatherRatesCache = {}; + var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager); + var baseSpeed = cmpTechMan.ApplyModifications("ResourceGatherer/BaseSpeed", this.template.BaseSpeed, this.entity); + for (var r in this.template.Rates) + { + var rate = cmpTechMan.ApplyModifications("ResourceGatherer/Rates/" + r, this.template.Rates[r], this.entity); + this.gatherRatesCache[r] = rate * baseSpeed; + } + } + + return this.gatherRatesCache; }; ResourceGatherer.prototype.GetRange = function() { return { "max": +this.template.MaxDistance, "min": 0 }; // maybe this should depend on the unit or target or something? -} +}; /** * Try to gather treasure @@ -223,13 +251,13 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target) var type = cmpResourceSupply.GetType(); var rate; - if (type.specific && this.template.Rates[type.generic+"."+type.specific]) - rate = this.template.Rates[type.generic+"."+type.specific]; + if (type.specific && this.GetGatherRates()[type.generic+"."+type.specific]) + rate = this.GetGatherRates()[type.generic+"."+type.specific]; else - rate = this.template.Rates[type.generic]; + rate = this.GetGatherRates()[type.generic]; - return (rate || 0) * this.template.BaseSpeed; -} + return (rate || 0); +}; /** * Returns whether this unit can carry more of the given type of resource. diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js new file mode 100644 index 0000000000..f3a4d0694e --- /dev/null +++ b/binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -0,0 +1,320 @@ +function TechnologyManager() {} + +TechnologyManager.prototype.Schema = + ""; + +TechnologyManager.prototype.Init = function () +{ + var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager); + this.allTechs = cmpTechTempMan.GetAllTechs(); + this.researchedTechs = {}; // technologies which have been researched + this.inProgressTechs = {}; // technologies which are being researched currently + + // This stores the modifications to unit stats from researched technologies + // Example data: {"ResourceGatherer/Rates/food.grain": [ + // {"multiplier": 1.15, "affects": ["Female", "Infantry Swordsman"]}, + // {"add": 2} + // ]} + this.modifications = {}; + + this.typeCounts = {}; // stores the number of entities of each type + this.classCounts = {}; // stores the number of entities of each Class + this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e. + // {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...} + + // Some technologies are automatically researched when their conditions are met. They have no cost and are + // researched instantly. This allows civ bonuses and more complicated technologies. + this.autoResearchTech = {}; + for (var key in this.allTechs) + { + if (this.allTechs[key].autoResearch) + this.autoResearchTech[key] = this.allTechs[key]; + } + + this.UpdateAutoResearch(); +}; + +// This function checks if the requirements of any autoresearch techs are met and if they are it researches them +TechnologyManager.prototype.UpdateAutoResearch = function () +{ + for (var key in this.autoResearchTech) + { + if (this.CanResearch(key)) + { + delete this.autoResearchTech[key]; + this.ResearchTechnology(key); + return; // We will have recursively handled any knock-on effects so can just return + } + } +} + +TechnologyManager.prototype.GetTechnologyTemplate = function (tech) +{ + return this.allTechs[tech]; +}; + +// Checks an entity template to see if its technology requirements have been met +TechnologyManager.prototype.CanProduce = function (templateName) +{ + var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + var template = cmpTempManager.GetTemplate(templateName); + + if (template.Identity && template.Identity.RequiredTechnology) + return this.IsTechnologyResearched(template.Identity.RequiredTechnology); + else + return true; // If there is no required technology then this entity can be produced +}; + +TechnologyManager.prototype.IsTechnologyResearched = function (tech) +{ + return (this.researchedTechs[tech] !== undefined); +}; + +// Checks the requirements for a technology to see if it can be researched at the current time +TechnologyManager.prototype.CanResearch = function (tech) +{ + var template = this.GetTechnologyTemplate(tech); + if (!template) + { + warn("Technology \"" + tech + "\" does not exist"); + return false; + } + + // The technology which this technology supersedes is required + if (template.supersedes && !this.IsTechnologyResearched(template.supersedes)) + return false; + + return this.CheckTechnologyRequirements(template.requirements); +}; + +TechnologyManager.prototype.CheckTechnologyRequirements = function (reqs) +{ + // If there are no requirements then all requirements are met + if (!reqs) + return true; + + if (reqs.tech) + { + return this.IsTechnologyResearched(reqs.tech); + } + else if (reqs.all) + { + for (var i = 0; i < reqs.all.length; i++) + { + if (!this.CheckTechnologyRequirements(reqs.all[i])) + return false; + } + return true; + } + else if (reqs.any) + { + for (var i = 0; i < reqs.any.length; i++) + { + if (this.CheckTechnologyRequirements(reqs.any[i])) + return true; + } + return false; + } + else if (reqs.class) + { + return (reqs.numberOfTypes <= Object.keys(this.typeCountsByClass[reqs.class]).length); + } + + // The technologies requirements are not a recognised format + error("Bad requirements " + uneval(reqs)); + return false; +}; + +TechnologyManager.prototype.OnGlobalOwnershipChanged = function (msg) +{ + // This automatically updates typeCounts, classCounts and typeCountsByClass + var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID(); + if (msg.to == playerID) + { + var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity); + + this.typeCounts[template] = this.typeCounts[template] || 0; + this.typeCounts[template] += 1; + + // don't use foundations for the class counts + if (Engine.QueryInterface(msg.entity, IID_Foundation)) + return; + + var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity); + if (cmpIdentity) + { + var classes = cmpIdentity.GetClassesList(); + for (var i in classes) + { + this.classCounts[classes[i]] = this.classCounts[classes[i]] || 0; + this.classCounts[classes[i]] += 1; + + this.typeCountsByClass[classes[i]] = this.typeCountsByClass[classes[i]] || {}; + this.typeCountsByClass[classes[i]][template] = this.typeCountsByClass[classes[i]][template] || 0; + this.typeCountsByClass[classes[i]][template] += 1; + } + } + } + if (msg.from == playerID) + { + var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity); + + this.typeCounts[template] -= 1; + if (this.typeCounts[template] <= 0) + delete this.typeCounts[template]; + + // don't use foundations for the class counts + if (Engine.QueryInterface(msg.entity, IID_Foundation)) + return; + + var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity); + if (cmpIdentity) + { + var classes = cmpIdentity.GetClassesList(); + for (var i in classes) + { + this.classCounts[classes[i]] -= 1; + if (this.classCounts[classes[i]] <= 0) + delete this.classCounts[classes[i]]; + + this.typeCountsByClass[classes[i]][template] -= 1; + if (this.typeCountsByClass[classes[i]][template] <= 0) + delete this.typeCountsByClass[classes[i]][template]; + } + } + } +}; + +// Marks a technology as researched. Note that this does not verify that the requirements are met. +TechnologyManager.prototype.ResearchTechnology = function (tech) +{ + this.StoppedResearch(tech); // The tech is no longer being currently researched + + var template = this.GetTechnologyTemplate(tech); + + if (!template) + { + error("Tried to research invalid techonology: " + uneval(tech)); + return; + } + + var modifiedComponents = {}; + this.researchedTechs[tech] = template; + // store the modifications in an easy to access structure + if (template.modifications) + { + var affects = []; + if (template.affects && template.affects.length > 0) + { + for (var i in template.affects) + { + // Put the list of classes into an array for convenient access + affects.push(template.affects[i].split(/\s+/)); + } + } + else + { + affects.push([]); + } + + // We add an item to this.modifications for every modification in the template.modifications array + for (var i in template.modifications) + { + var modification = template.modifications[i]; + if (!this.modifications[modification.value]) + this.modifications[modification.value] = []; + + var mod = {"affects": affects}; + // copy the modification data into our new data structure + for (var j in modification) + if (j !== "value") + mod[j] = modification[j]; + + this.modifications[modification.value].push(mod); + modifiedComponents[modification.value.split("/")[0]] = true; + } + } + + var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); + var player = cmpPlayer.GetPlayerID(); + + for (var component in modifiedComponents) + Engine.BroadcastMessage(MT_TechnologyModificationChange, { "component": component, "player": player }); + + this.UpdateAutoResearch(); +}; + +TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, ent) +{ + // Get all modifications to this value + var modifications = this.modifications[valueName]; + if (!modifications) // no modifications so return the original value + return curValue; + + // Get the classes which this entity belongs to + var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + var classes = cmpIdentity.GetClassesList(); + + var retValue = +curValue; + + for (var i in modifications) + { + var modification = modifications[i]; + var applies = false; + // See if any of the lists of classes matches this entity + for (var j in modification.affects) + { + var hasAllClasses = true; + // Check each class in affects is present for the entity + for (var k in modification.affects[j]) + hasAllClasses = hasAllClasses && (classes.indexOf(modification.affects[j][k]) !== -1); + + if (hasAllClasses) + { + applies = true; + break; + } + } + + // We found a match, apply the modification + if (applies) + { + // Nothing is cumulative so that ordering doesn't matter as much as possible + if (modification.multiplier) + retValue += (modification.multiplier - 1) * +curValue; + else if (modification.add) + retValue += modification.add; + else if (modification.replace) // This will depend on ordering because there is no choice + retValue = modification.replace; + else + warn("modification format not recognised (modifying " + valueName + "): " + uneval(modification)); + } + } + + return retValue; +}; + +// Marks a technology as being currently researched +TechnologyManager.prototype.StartedResearch = function (tech) +{ + this.inProgressTechs[tech] = true; +}; + +// Marks a technology as not being currently researched +TechnologyManager.prototype.StoppedResearch = function (tech) +{ + delete this.inProgressTechs[tech]; +}; + +// Checks whether a technology is being currently researched +TechnologyManager.prototype.IsInProgress = function(tech) +{ + if (this.inProgressTechs[tech]) + return true; + else + return false; +}; + +Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager); diff --git a/binaries/data/mods/public/simulation/components/TechnologyTemplateManager.js b/binaries/data/mods/public/simulation/components/TechnologyTemplateManager.js new file mode 100644 index 0000000000..9adca373e5 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/TechnologyTemplateManager.js @@ -0,0 +1,39 @@ +/** + * System component which loads the technology data files + */ +function TechnologyTemplateManager() {} + +TechnologyTemplateManager.prototype.Schema = + ""; + +TechnologyTemplateManager.prototype.Init = function() +{ + this.allTechs = {}; + var techNames = this.ListAllTechs(); + for (i in techNames) + this.GetTemplate(techNames[i]); +}; + +TechnologyTemplateManager.prototype.GetTemplate = function(template) +{ + if (!this.allTechs[template]) + { + this.allTechs[template] = Engine.ReadJSONFile("technologies/" + template + ".json"); + if (! this.allTechs[template]) + error("Failed to load technology \"" + template + "\""); + } + + return this.allTechs[template]; +}; + +TechnologyTemplateManager.prototype.ListAllTechs = function() +{ + return Engine.FindJSONFiles("technologies"); +} + +TechnologyTemplateManager.prototype.GetAllTechs = function() +{ + return this.allTechs; +} + +Engine.RegisterComponentType(IID_TechnologyTemplateManager, "TechnologyTemplateManager", TechnologyTemplateManager); diff --git a/binaries/data/mods/public/simulation/components/TrainingQueue.js b/binaries/data/mods/public/simulation/components/TrainingQueue.js deleted file mode 100644 index 04249d3c0b..0000000000 --- a/binaries/data/mods/public/simulation/components/TrainingQueue.js +++ /dev/null @@ -1,400 +0,0 @@ -var g_ProgressInterval = 1000; -const MAX_QUEUE_SIZE = 16; - -function TrainingQueue() {} - -TrainingQueue.prototype.Schema = - "Allows the building to train new units." + - "" + - "" + - "\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " + - "" + - "" + - "" + - "" + - "tokens" + - "" + - "" + - ""; - -TrainingQueue.prototype.Init = function() -{ - this.nextID = 1; - - this.queue = []; - // Queue items are: - // { - // "id": 1, - // "player": 1, // who paid for this batch; we need this to cope with refunds cleanly - // "template": "units/example", - // "count": 10, - // "resources": { "wood": 100, ... }, // resources per unit, multiply by count to get total - // "population": 1, // population per unit, multiply by count to get total - // "trainingStarted": false, // true iff we have reserved population - // "timeTotal": 15000, // msecs - // "timeRemaining": 10000, // msecs - // } - - this.timer = undefined; // g_ProgressInterval msec timer, active while the queue is non-empty - - this.entityCache = []; - this.spawnNotified = false; -}; - -/* - * Returns list of entities that can be trained by this building. - */ -TrainingQueue.prototype.GetEntitiesList = function() -{ - var string = this.template.Entities._string; - - // Replace the "{civ}" codes with this entity's civ ID - var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); - if (cmpIdentity) - string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv()); - - return string.split(/\s+/); -}; - -/* - * Adds a new batch of identical units to the training queue. - */ -TrainingQueue.prototype.AddBatch = function(templateName, count, metadata) -{ - // TODO: there should probably be a limit on the number of queued batches - // 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) - var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); - - if (this.queue.length < MAX_QUEUE_SIZE) - { - // Find the template data so we can determine the build costs - var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); - var template = cmpTempMan.GetTemplate(templateName); - if (!template) - return; - - // Apply a time discount to larger batches. - // TODO: work out what equation we should use here. - var timeMult = Math.pow(count, 0.7); - - var time = timeMult * template.Cost.BuildTime; - - var totalCosts = {}; - for each (var r in ["food", "wood", "stone", "metal"]) - totalCosts[r] = Math.floor(count * template.Cost.Resources[r]); - - var population = template.Cost.Population; - - // TrySubtractResources should report error to player (they ran out of resources) - if (!cmpPlayer.TrySubtractResources(totalCosts)) - return; - - this.queue.push({ - "id": this.nextID++, - "player": cmpPlayer.GetPlayerID(), - "template": templateName, - "count": count, - "metadata": metadata, - "resources": deepcopy(template.Cost.Resources), // need to copy to avoid serialization problems - "population": population, - "trainingStarted": false, - "timeTotal": time*1000, - "timeRemaining": time*1000, - }); - Engine.PostMessage(this.entity, MT_TrainingQueueChanged, { }); - - // If this is the first item in the queue, start the timer - if (!this.timer) - { - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - this.timer = cmpTimer.SetTimeout(this.entity, IID_TrainingQueue, "ProgressTimeout", g_ProgressInterval, {}); - } - } - else - { - var notification = {"player": cmpPlayer.GetPlayerID(), "message": "The training queue is full."}; - var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGUIInterface.PushNotification(notification); - } -}; - -/* - * Removes an existing batch of units from the training queue. - * Refunds resource costs and population reservations. - */ -TrainingQueue.prototype.RemoveBatch = function(id) -{ - // Destroy any cached entities (those which didn't spawn for some reason) - for (var i = 0; i < this.entityCache.length; ++i) - { - Engine.DestroyEntity(this.entityCache[i]); - } - this.entityCache = []; - - for (var i = 0; i < this.queue.length; ++i) - { - var item = this.queue[i]; - if (item.id != id) - continue; - - // Now we've found the item to remove - - var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player); - - // Refund the resource cost for this batch - var totalCosts = {}; - for each (var r in ["food", "wood", "stone", "metal"]) - totalCosts[r] = Math.floor(item.count * item.resources[r]); - - cmpPlayer.AddResources(totalCosts); - - // Remove reserved population slots if necessary - if (item.trainingStarted) - cmpPlayer.UnReservePopulationSlots(item.population * item.count); - - // Remove from the queue - // (We don't need to remove the timer - it'll expire if it discovers the queue is empty) - this.queue.splice(i, 1); - Engine.PostMessage(this.entity, MT_TrainingQueueChanged, { }); - - return; - } -}; - -/* - * Returns basic data from all batches in the training queue. - */ -TrainingQueue.prototype.GetQueue = function() -{ - var out = []; - for each (var item in this.queue) - { - out.push({ - "id": item.id, - "template": item.template, - "count": item.count, - "progress": 1-(item.timeRemaining/item.timeTotal), - "metadata": item.metadata, - }); - } - return out; -}; - -/* - * Removes all existing batches from the queue. - */ -TrainingQueue.prototype.ResetQueue = function() -{ - // Empty the training queue and refund all the resource costs - // to the player. (This is to avoid players having to micromanage their - // buildings' queues when they're about to be destroyed or captured.) - - while (this.queue.length) - this.RemoveBatch(this.queue[0].id); -}; - -TrainingQueue.prototype.OnOwnershipChanged = function(msg) -{ - if (msg.from != -1) - { - // Unset flag that previous owner's training queue may be blocked - var cmpPlayer = QueryPlayerIDInterface(msg.from, IID_Player); - if (cmpPlayer && this.queue.length > 0) - cmpPlayer.UnBlockTrainingQueue(); - } - - // Reset the training 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(); -}; - -TrainingQueue.prototype.OnDestroy = function() -{ - // Reset the queue to refund any resources - this.ResetQueue(); - - if (this.timer) - { - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.timer); - } -}; - -/* - * This function creates the entities and places them in world if possible. - * returns the number of successfully spawned entities. - */ -TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata) -{ - var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); - var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); - - var spawnedEnts = []; - - if (this.entityCache.length == 0) - { - // We need entities to test spawning, but we don't want to waste resources, - // so only create them once and use as needed - for (var i = 0; i < count; ++i) - { - this.entityCache.push(Engine.AddEntity(templateName)); - } - } - - for (var i = 0; i < count; ++i) - { - var ent = this.entityCache[0]; - var pos = cmpFootprint.PickSpawnPoint(ent); - if (pos.y < 0) - { - // Fail: there wasn't any space to spawn the unit - break; - } - else - { - // Successfully spawned - var cmpNewPosition = Engine.QueryInterface(ent, IID_Position); - cmpNewPosition.JumpTo(pos.x, pos.z); - // TODO: what direction should they face in? - - var cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership); - cmpNewOwnership.SetOwner(cmpOwnership.GetOwner()); - - var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); - cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter(); - - // Play a sound, but only for the first in the batch (to avoid nasty phasing effects) - if (spawnedEnts.length == 0) - PlaySound("trained", ent); - - this.entityCache.shift(); - spawnedEnts.push(ent); - } - } - - if (spawnedEnts.length > 0) - { - // If a rally point is set, walk towards it (in formation) using a suitable command based on where the - // rally point is placed. - if (cmpRallyPoint) - { - var rallyPos = cmpRallyPoint.GetPosition(); - if (rallyPos) - { - ProcessCommand(cmpOwnership.GetOwner(), GetRallyPointCommand(cmpRallyPoint, spawnedEnts)); - } - } - - Engine.PostMessage(this.entity, MT_TrainingFinished, { - "entities": spawnedEnts, - "owner": cmpOwnership.GetOwner(), - "metadata": metadata, - }); - } - - return spawnedEnts.length; -}; - -/* - * Increments progress on the first batch in the training queue, and blocks the - * queue if population limit is reached or some units failed to spawn. - */ -TrainingQueue.prototype.ProgressTimeout = function(data) -{ - // Allocate the 1000msecs 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) - var time = g_ProgressInterval; - var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); - - while (time > 0 && this.queue.length) - { - var item = this.queue[0]; - if (!item.trainingStarted) - { - // Batch's training hasn't started yet. - // Try to reserve the necessary population slots - if (!cmpPlayer.TryReservePopulationSlots(item.population * item.count)) - { - // No slots available - don't train this batch now - // (we'll try again on the next timeout) - - // Set flag that training queue is blocked - cmpPlayer.BlockTrainingQueue(); - break; - } - - // Unset flag that training queue is blocked - cmpPlayer.UnBlockTrainingQueue(); - - item.trainingStarted = true; - } - - // If we won't finish the batch now, just update its timer - if (item.timeRemaining > time) - { - item.timeRemaining -= time; - break; - } - - var numSpawned = this.SpawnUnits(item.template, item.count, item.metadata); - if (numSpawned == item.count) - { - // All entities spawned, this batch finished - cmpPlayer.UnReservePopulationSlots(item.population * numSpawned); - time -= item.timeRemaining; - this.queue.shift(); - // Unset flag that training queue is blocked - cmpPlayer.UnBlockTrainingQueue(); - this.spawnNotified = false; - Engine.PostMessage(this.entity, MT_TrainingQueueChanged, { }); - } - else - { - if (numSpawned > 0) - { - // Only partially finished - cmpPlayer.UnReservePopulationSlots(item.population * numSpawned); - item.count -= numSpawned; - Engine.PostMessage(this.entity, MT_TrainingQueueChanged, { }); - } - - // Some entities failed to spawn - // Set flag that training queue is blocked - cmpPlayer.BlockTrainingQueue(); - - if (!this.spawnNotified) - { - var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); - var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to spawn trained units" }; - var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGUIInterface.PushNotification(notification); - this.spawnNotified = true; - } - break; - } - } - - // If the queue's empty, delete the timer, else repeat it - if (this.queue.length == 0) - { - this.timer = undefined; - - // Unset flag that training queue is blocked - // (This might happen when the player unqueues all batches) - cmpPlayer.UnBlockTrainingQueue(); - } - else - { - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - this.timer = cmpTimer.SetTimeout(this.entity, IID_TrainingQueue, "ProgressTimeout", g_ProgressInterval, data); - } -} - -Engine.RegisterComponentType(IID_TrainingQueue, "TrainingQueue", TrainingQueue); diff --git a/binaries/data/mods/public/simulation/components/interfaces/TrainingQueue.js b/binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js similarity index 76% rename from binaries/data/mods/public/simulation/components/interfaces/TrainingQueue.js rename to binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js index 04958203ae..d5953ce7ae 100644 --- a/binaries/data/mods/public/simulation/components/interfaces/TrainingQueue.js +++ b/binaries/data/mods/public/simulation/components/interfaces/ProductionQueue.js @@ -1,8 +1,8 @@ -Engine.RegisterInterface("TrainingQueue"); +Engine.RegisterInterface("ProductionQueue"); // Message of the form { } (use GetQueue if you want the current details), // sent to the current entity whenever the training queue changes. -Engine.RegisterMessageType("TrainingQueueChanged"); +Engine.RegisterMessageType("ProductionQueueChanged"); // Message of the form { entities: [id, ...], metadata: ... } // sent to the current entity whenever a unit has been trained. diff --git a/binaries/data/mods/public/simulation/components/interfaces/TechnologyManager.js b/binaries/data/mods/public/simulation/components/interfaces/TechnologyManager.js new file mode 100644 index 0000000000..cf92838300 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/interfaces/TechnologyManager.js @@ -0,0 +1,5 @@ +Engine.RegisterInterface("TechnologyManager"); + +// Message of the form { "component": "Attack", "player": 3 } +// Sent when a new technology is researched which modifies a component +Engine.RegisterMessageType("TechnologyModificationChange"); diff --git a/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js b/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js new file mode 100644 index 0000000000..71c166e421 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/interfaces/TechnologyTemplateManager.js @@ -0,0 +1 @@ +Engine.RegisterInterface("TechnologyTemplateManager"); \ No newline at end of file diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index ac7c1ef299..6d257a49a8 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -7,12 +7,13 @@ Engine.LoadComponentScript("interfaces/Foundation.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Heal.js"); Engine.LoadComponentScript("interfaces/Health.js"); +Engine.LoadComponentScript("interfaces/ProductionQueue.js"); Engine.LoadComponentScript("interfaces/Promotion.js"); Engine.LoadComponentScript("interfaces/RallyPoint.js"); Engine.LoadComponentScript("interfaces/ResourceDropsite.js"); Engine.LoadComponentScript("interfaces/ResourceGatherer.js"); Engine.LoadComponentScript("interfaces/ResourceSupply.js"); -Engine.LoadComponentScript("interfaces/TrainingQueue.js"); +Engine.LoadComponentScript("interfaces/TechnologyManager.js") Engine.LoadComponentScript("interfaces/Trader.js") Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); @@ -49,7 +50,6 @@ AddMock(SYSTEM_ENTITY, IID_Timer, { SetTimeout: function(ent, iid, funcname, time, data) { return 0; }, }); - AddMock(100, IID_Player, { GetName: function() { return "Player 1"; }, GetCiv: function() { return "gaia"; }, @@ -58,11 +58,10 @@ AddMock(100, IID_Player, { GetPopulationLimit: function() { return 20; }, GetMaxPopulation: function() { return 200; }, GetResourceCounts: function() { return { food: 100 }; }, - IsTrainingQueueBlocked: function() { return false; }, + IsTrainingBlocked: function() { return false; }, GetState: function() { return "active"; }, GetTeam: function() { return -1; }, GetDiplomacy: function() { return [-1, 1]; }, - GetPhase: function() { return ""; }, GetConquestCriticalEntitiesCount: function() { return 1; }, IsAlly: function() { return false; }, IsEnemy: function() { return true; }, @@ -73,6 +72,10 @@ AddMock(100, IID_BuildLimits, { GetCounts: function() { return {"Foo": 5}; }, }); +AddMock(100, IID_TechnologyManager, { + IsTechnologyResearched: function(tech) { return false; }, +}); + AddMock(100, IID_StatisticsTracker, { GetStatistics: function() { return { @@ -105,11 +108,10 @@ AddMock(101, IID_Player, { GetPopulationLimit: function() { return 30; }, GetMaxPopulation: function() { return 300; }, GetResourceCounts: function() { return { food: 200 }; }, - IsTrainingQueueBlocked: function() { return false; }, + IsTrainingBlocked: function() { return false; }, GetState: function() { return "active"; }, GetTeam: function() { return -1; }, GetDiplomacy: function() { return [-1, 1]; }, - GetPhase: function() { return "village"; }, GetConquestCriticalEntitiesCount: function() { return 1; }, IsAlly: function() { return true; }, IsEnemy: function() { return false; }, @@ -120,6 +122,10 @@ AddMock(101, IID_BuildLimits, { GetCounts: function() { return {"Bar": 0}; }, }); +AddMock(101, IID_TechnologyManager, { + IsTechnologyResearched: function(tech) { if (tech == "village") return true; else return false; }, +}); + AddMock(101, IID_StatisticsTracker, { GetStatistics: function() { return { @@ -157,7 +163,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { popLimit: 20, popMax: 200, resourceCounts: { food: 100 }, - trainingQueueBlocked: false, + trainingBlocked: false, state: "active", team: -1, phase: "", @@ -174,7 +180,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { popLimit: 30, popMax: 300, resourceCounts: { food: 200 }, - trainingQueueBlocked: false, + trainingBlocked: false, state: "active", team: -1, phase: "village", @@ -198,7 +204,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { popLimit: 20, popMax: 200, resourceCounts: { food: 100 }, - trainingQueueBlocked: false, + trainingBlocked: false, state: "active", team: -1, phase: "", @@ -231,7 +237,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { popLimit: 30, popMax: 300, resourceCounts: { food: 200 }, - trainingQueueBlocked: false, + trainingBlocked: false, state: "active", team: -1, phase: "village", diff --git a/binaries/data/mods/public/simulation/data/technologies/city_phase.json b/binaries/data/mods/public/simulation/data/technologies/city_phase.json new file mode 100644 index 0000000000..1fa175b4db --- /dev/null +++ b/binaries/data/mods/public/simulation/data/technologies/city_phase.json @@ -0,0 +1,13 @@ +{ + "genericName": "City Phase", + "specificName": { + "generic": "Generic Specific City Name", + "hele": "Greek City" + }, + "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology.", + "cost": {"food": 0, "wood": 0, "stone": 50, "metal": 0}, + "supersedes": "town_phase", + "icon": "city_phase.png", + "researchTime": 10, + "tooltip": "Advance to City Phase" +} diff --git a/binaries/data/mods/public/simulation/data/technologies/plough.json b/binaries/data/mods/public/simulation/data/technologies/plough.json new file mode 100644 index 0000000000..03cec6828e --- /dev/null +++ b/binaries/data/mods/public/simulation/data/technologies/plough.json @@ -0,0 +1,12 @@ +{ + "genericName": "Plough", + "description": "A horse drawn instrument to turn the sod.", + "cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0}, + "requirements": {"tech": "town_phase"}, + "requirementsTooltip": "Requires Town Phase", + "icon": "plough.png", + "researchTime": 10, + "tooltip": "Discover the Plough", + "modifications": [{"value": "ResourceGatherer/Rates/food.grain", "multiplier": 15}], + "affects": ["Female"] +} diff --git a/binaries/data/mods/public/simulation/data/technologies/town_phase.json b/binaries/data/mods/public/simulation/data/technologies/town_phase.json new file mode 100644 index 0000000000..4db246ad29 --- /dev/null +++ b/binaries/data/mods/public/simulation/data/technologies/town_phase.json @@ -0,0 +1,11 @@ +{ + "genericName": "Town Phase", + "description": "Advances from a small village to a bustling town, ready to expand rapidly.", + "cost": { "food": 100, "wood": 100, "stone": 0, "metal": 50 }, + "requirements": { "class": "Village", "numberOfTypes": 2 }, + "requirementsTooltip": "Requires two village structures", + "supersedes": "village_phase", + "icon": "town_phase.png", + "researchTime": 10, + "tooltip": "Advance to Town Phase" +} diff --git a/binaries/data/mods/public/simulation/data/technologies/village_phase.json b/binaries/data/mods/public/simulation/data/technologies/village_phase.json new file mode 100644 index 0000000000..b5682fc81b --- /dev/null +++ b/binaries/data/mods/public/simulation/data/technologies/village_phase.json @@ -0,0 +1,4 @@ +{ + "genericName": "Village Phase", + "autoResearch": true +} diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 78cacff275..c135854c30 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -135,9 +135,18 @@ function ProcessCommand(player, cmd) // Verify that the building can be controlled by the player if (CanControlUnit(cmd.entity, player, controlAllUnits)) { - var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue); - if (queue) - queue.AddBatch(cmd.template, +cmd.count, cmd.metadata); + var cmpTechMan = QueryOwnerInterface(cmd.entity, IID_TechnologyManager); + // TODO: Enable this check once the AI gets technology support + if (cmpTechMan.CanProduce(cmd.template) || true) + { + var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue); + if (queue) + queue.AddBatch(cmd.template, "unit", +cmd.count, cmd.metadata); + } + else if (g_DebugCommands) + { + warn("Invalid command: training requires unresearched technology: " + uneval(cmd)); + } } else if (g_DebugCommands) { @@ -145,17 +154,40 @@ function ProcessCommand(player, cmd) } break; - case "stop-train": + case "research": // Verify that the building can be controlled by the player if (CanControlUnit(cmd.entity, player, controlAllUnits)) { - var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue); + var cmpTechMan = QueryOwnerInterface(cmd.entity, IID_TechnologyManager); + // TODO: Enable this check once the AI gets technology support + if (cmpTechMan.CanResearch(cmd.template) || true) + { + var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue); + if (queue) + queue.AddBatch(cmd.template, "technology"); + } + else if (g_DebugCommands) + { + warn("Invalid command: Requirements to research technology are not met: " + uneval(cmd)); + } + } + else if (g_DebugCommands) + { + warn("Invalid command: research building cannot be controlled by player "+player+": "+uneval(cmd)); + } + break; + + case "stop-production": + // Verify that the building can be controlled by the player + if (CanControlUnit(cmd.entity, player, controlAllUnits)) + { + var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue); if (queue) queue.RemoveBatch(cmd.id); } else if (g_DebugCommands) { - warn("Invalid command: training building cannot be controlled by player "+player+": "+uneval(cmd)); + warn("Invalid command: production building cannot be controlled by player "+player+": "+uneval(cmd)); } break; @@ -233,6 +265,22 @@ function ProcessCommand(player, cmd) Engine.DestroyEntity(ent); break; } + + var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); + // TODO: Enable this check once the AI gets technology support + if (!cmpTechMan.CanProduce(cmd.template) && false) + { + if (g_DebugCommands) + { + warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd)); + } + + var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + cmpGuiInterface.PushNotification({ "player": player, "message": "Building's technology requirements are not met." }); + + // Remove the foundation because the construction was aborted + Engine.DestroyEntity(ent); + } // TODO: AI has no visibility info if (!cmpPlayer.IsAI()) diff --git a/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml b/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml index 732c6cff25..660862e849 100644 --- a/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml +++ b/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml @@ -35,13 +35,13 @@ 150 35000 - + -units/{civ}_support_female_citizen campaigns/army_mace_hero_alexander campaigns/army_mace_standard - + campaigns/structures/hellenes/settlement_curtainwall.xml diff --git a/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml b/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml index 757a3879e7..e9969f51c4 100644 --- a/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml +++ b/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml @@ -35,14 +35,14 @@ 300 35000 - + -units/{civ}_support_female_citizen campaigns/army_mace_hero_alexander campaigns/army_mace_standard units/{civ}_support_trader - + campaigns/structures/hellenes/settlement_curtainwall.xml diff --git a/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml b/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml index 2a973c1788..d640d63aa8 100644 --- a/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml +++ b/binaries/data/mods/public/simulation/templates/campaigns/campaign_religious_test.xml @@ -20,7 +20,7 @@ 100 65536 - + structures/hellenes/temple_new.xml diff --git a/binaries/data/mods/public/simulation/templates/other/cart_tophet.xml b/binaries/data/mods/public/simulation/templates/other/cart_tophet.xml index 5f6b141a0b..8f191de83f 100644 --- a/binaries/data/mods/public/simulation/templates/other/cart_tophet.xml +++ b/binaries/data/mods/public/simulation/templates/other/cart_tophet.xml @@ -34,11 +34,11 @@ - + units/cart_sacred_band_cavalry - + 40 diff --git a/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml b/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml index 7aa38cd734..9ad6ba0e58 100644 --- a/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml +++ b/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml @@ -35,13 +35,13 @@ food wood stone metal - + units/celt_cavalry_javelinist_b units/celt_infantry_javelinist_b units/celt_infantry_spearman_b - + 100 65536 diff --git a/binaries/data/mods/public/simulation/templates/other/celt_tavern.xml b/binaries/data/mods/public/simulation/templates/other/celt_tavern.xml index 567ba352f8..91fe3843ce 100644 --- a/binaries/data/mods/public/simulation/templates/other/celt_tavern.xml +++ b/binaries/data/mods/public/simulation/templates/other/celt_tavern.xml @@ -39,11 +39,11 @@ 32 65536 - + units/celt_fanatic - + structures/celts/tavern.xml structures/fndn_4x4.xml diff --git a/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml b/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml index 6195e32978..efac821924 100644 --- a/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml +++ b/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml @@ -48,13 +48,13 @@ 40 65536 - + units/thrace_black_cloak units/mace_thorakites units/mace_thureophoros - + 40 true diff --git a/binaries/data/mods/public/simulation/templates/other/pers_apartment_block.xml b/binaries/data/mods/public/simulation/templates/other/pers_apartment_block.xml index 34657c206c..f95ab4567b 100644 --- a/binaries/data/mods/public/simulation/templates/other/pers_apartment_block.xml +++ b/binaries/data/mods/public/simulation/templates/other/pers_apartment_block.xml @@ -21,11 +21,11 @@ 25 - + units/{civ}_support_female_citizen - + props/structures/persians/alt_building_03.xml diff --git a/binaries/data/mods/public/simulation/templates/other/pers_inn.xml b/binaries/data/mods/public/simulation/templates/other/pers_inn.xml index 3ac57f1d96..6226a9aa48 100644 --- a/binaries/data/mods/public/simulation/templates/other/pers_inn.xml +++ b/binaries/data/mods/public/simulation/templates/other/pers_inn.xml @@ -21,11 +21,11 @@ 26 - + units/{civ}_support_female_citizen - + props/structures/persians/alt_building_04.xml diff --git a/binaries/data/mods/public/simulation/templates/special/player.xml b/binaries/data/mods/public/simulation/templates/special/player.xml index e6fb9ecf4a..725f1c884a 100644 --- a/binaries/data/mods/public/simulation/templates/special/player.xml +++ b/binaries/data/mods/public/simulation/templates/special/player.xml @@ -10,4 +10,5 @@ + diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/athen_barracks.xml index 4be059ae7e..9a668a91a6 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_barracks.xml @@ -15,7 +15,7 @@ Stratēgeîon The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi". - + units/athen_infantry_spearman_b units/athen_infantry_javelinist_b @@ -23,7 +23,7 @@ units/athen_cavalry_swordsman_b units/athen_cavalry_javelinist_b - + structures/athenians/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml index 3192a150a3..3c1768a18b 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml @@ -11,13 +11,13 @@ Agorā́ The most important place in most Classical Greek poleis, the Agora served many purposes; it was a place for public speeches and was the stage for civic life and commercial interests. - + units/athen_infantry_spearman_b units/athen_infantry_slinger_b units/athen_cavalry_javelinist_b - + structures/athenians/civic_centre_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_corral.xml b/binaries/data/mods/public/simulation/templates/structures/athen_corral.xml index f87d62e119..a89dbae7f0 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_corral.xml @@ -18,11 +18,11 @@ - + gaia/fauna_goat - + structures/athenians/corral.xml structures/fndn_3x3.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_dock.xml b/binaries/data/mods/public/simulation/templates/structures/athen_dock.xml index ba61b63a18..b85fe43502 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_dock.xml @@ -11,12 +11,12 @@ Limḗn Greece is a sea country, which is why some of the greatest Hellenic and Hellenistic cities like Ephesus, Corinth, Alexandria and Antioch were built by the sea. It should also be noted that all colonies during the Great Colonisation were thriving port centres, which traded with the local population. - + units/athen_ship_bireme units/athen_ship_trireme - + structures/athenians/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/athen_fortress.xml index 6301ef79f0..08cbb61647 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_fortress.xml @@ -19,12 +19,12 @@ - + units/athen_mechanical_siege_oxybeles units/athen_mechanical_siege_lithobolos - + structures/athenians/fortress.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_gymnasion.xml b/binaries/data/mods/public/simulation/templates/structures/athen_gymnasion.xml index 4f9d541cd7..9aa8515859 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_gymnasion.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_gymnasion.xml @@ -33,12 +33,12 @@ attack/destruction/building_collapse_large.xml - + units/athen_champion_infantry units/athen_champion_ranged - + structures/athenians/gymnasion.xml structures/fndn_6x6.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/athen_prytaneion.xml b/binaries/data/mods/public/simulation/templates/structures/athen_prytaneion.xml index 8e25ea8282..b4ed55d522 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen_prytaneion.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen_prytaneion.xml @@ -34,13 +34,13 @@ attack/destruction/building_collapse_large.xml - + units/athen_hero_themistocles units/athen_hero_pericles units/athen_hero_xenophon - + structures/athenians/prytaneion.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/cart_barracks.xml index e86dd27435..33f3d3b784 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_barracks.xml @@ -19,13 +19,13 @@ - + units/cart_infantry_spearman_b units/cart_infantry_archer_b units/cart_cavalry_javelinist_b - + structures/carthaginians/barracks.xml structures/fndn_5x5.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/cart_civil_centre.xml index be21f3742d..29f6229ba5 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_civil_centre.xml @@ -5,13 +5,13 @@ Merkāz Carthiginian's History - + units/cart_infantry_spearman_b units/cart_infantry_archer_b units/cart_cavalry_javelinist_b - + structures/carthaginians/civil_centre.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_corral.xml b/binaries/data/mods/public/simulation/templates/structures/cart_corral.xml index 47d37a26a9..bde1b42378 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_corral.xml @@ -12,11 +12,11 @@ - + gaia/fauna_goat - + structures/carthaginians/corral.xml structures/fndn_3x3.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_embassy.xml b/binaries/data/mods/public/simulation/templates/structures/cart_embassy.xml index 8d71a780ef..d14337b537 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_embassy.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_embassy.xml @@ -21,7 +21,7 @@ - + units/cart_infantry_swordsman_b units/cart_infantry_javelinist_b @@ -29,7 +29,7 @@ units/cart_cavalry_swordsman_b units/cart_cavalry_spearman_b - + structures/carthaginians/embassy.xml structures/fndn_5x5.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_celtic.xml b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_celtic.xml index 657b134891..bb3908d28a 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_celtic.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_celtic.xml @@ -24,12 +24,12 @@ - + units/cart_infantry_swordsman_b units/cart_cavalry_swordsman_2_b - + structures/carthaginians/embassy_celtic.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_iberian.xml b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_iberian.xml index 15a1fe0ebf..9820b6ec7d 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_iberian.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_iberian.xml @@ -17,13 +17,13 @@ - + units/cart_infantry_javelinist_b units/cart_infantry_slinger_b units/cart_cavalry_swordsman_b - + structures/carthaginians/embassy_iberian.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_italiote.xml b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_italiote.xml index 49dafa99f0..b8885de1f1 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_embassy_italiote.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_embassy_italiote.xml @@ -20,12 +20,12 @@ - + units/cart_infantry_swordsman_2_b units/cart_cavalry_spearman_b - + structures/carthaginians/embassy_italiote.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/cart_fortress.xml index 0e63ceb9ed..01953af648 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_fortress.xml @@ -13,7 +13,7 @@ - + units/cart_hero_hamilcar units/cart_hero_hannibal @@ -22,7 +22,7 @@ units/cart_mechanical_siege_ballista units/cart_mechanical_siege_oxybeles - + structures/carthaginians/fortress.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml b/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml index f5b40efd16..26c09ec78e 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml @@ -46,13 +46,13 @@ - + units/cart_ship_bireme units/cart_ship_trireme units/cart_ship_quinquereme - + 100 diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_temple.xml b/binaries/data/mods/public/simulation/templates/structures/cart_temple.xml index 7f5d40bc73..4719a9330f 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart_temple.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart_temple.xml @@ -18,11 +18,11 @@ - + units/cart_champion_infantry - + structures/carthaginians/temple_big.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/celt_barracks.xml index b4f66349ed..89434e3ab8 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_barracks.xml @@ -14,7 +14,7 @@ - + units/celt_infantry_spearman_b units/celt_infantry_javelinist_b @@ -22,7 +22,7 @@ units/celt_cavalry_swordsman_b units/celt_cavalry_javelinist_b - + structures/celts/barracks_new.xml structures/fndn_5x5.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/celt_civil_centre.xml index d9cd35c376..279eb15b80 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_civil_centre.xml @@ -12,13 +12,13 @@ - + units/celt_infantry_spearman_b units/celt_infantry_javelinist_b units/celt_cavalry_javelinist_b - + structures/celts/civic_centre.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_corral.xml b/binaries/data/mods/public/simulation/templates/structures/celt_corral.xml index f356495f7c..2fdc485204 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_corral.xml @@ -12,11 +12,11 @@ - + gaia/fauna_sheep - + structures/celts/plot_corral.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_dock.xml b/binaries/data/mods/public/simulation/templates/structures/celt_dock.xml index a3b1cb4a52..789472d2b6 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_dock.xml @@ -15,11 +15,11 @@ - + units/celt_ship_trireme - + structures/celts/dock_new.xml structures/fndn_celt_dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_fortress_b.xml b/binaries/data/mods/public/simulation/templates/structures/celt_fortress_b.xml index 8e8599593b..9bbdd7c914 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_fortress_b.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_fortress_b.xml @@ -29,7 +29,7 @@ attack/destruction/building_collapse_large.xml - + units/celt_hero_boudicca units/celt_hero_caratacos @@ -38,7 +38,7 @@ units/celt_champion_infantry_brit units/celt_mechanical_siege_ram - + 100 diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_fortress_g.xml b/binaries/data/mods/public/simulation/templates/structures/celt_fortress_g.xml index a22e621827..161f2802d7 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_fortress_g.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_fortress_g.xml @@ -16,7 +16,7 @@ attack/destruction/building_collapse_large.xml - + units/celt_hero_brennus units/celt_hero_britomartus @@ -25,7 +25,7 @@ units/celt_champion_infantry_gaul units/celt_mechanical_siege_ram - + structures/celts/fortress_gallic.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_kennel.xml b/binaries/data/mods/public/simulation/templates/structures/celt_kennel.xml index 6b99b76f6b..52b9e3a462 100644 --- a/binaries/data/mods/public/simulation/templates/structures/celt_kennel.xml +++ b/binaries/data/mods/public/simulation/templates/structures/celt_kennel.xml @@ -40,11 +40,11 @@ 20 65536 - + units/celt_war_dog_b - + structures/celts/kennel.xml structures/fndn_2x2.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/hele_barracks.xml index 6881ce1353..e510257777 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_barracks.xml @@ -15,7 +15,7 @@ Stratēgeîon The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi". - + units/hele_infantry_spearman_b units/hele_infantry_javelinist_b @@ -24,7 +24,7 @@ units/hele_cavalry_swordsman_b units/hele_cavalry_javelinist_b - + structures/hellenes/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/hele_civil_centre.xml index 5ab74e15bc..c7232229c8 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_civil_centre.xml @@ -11,13 +11,13 @@ Agorā́ The most important place in most Classical Greek poleis, the Agora served many purposes; it was a place for public speeches and was the stage for civic life and commercial interests. - + units/hele_infantry_spearman_b units/hele_infantry_javelinist_b units/hele_cavalry_javelinist_b - + structures/hellenes/civic_centre_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_corral.xml b/binaries/data/mods/public/simulation/templates/structures/hele_corral.xml index b41bb7f410..ad0f18d69d 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_corral.xml @@ -18,11 +18,11 @@ - + gaia/fauna_goat - + structures/hellenes/corral.xml structures/fndn_3x3.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_dock.xml b/binaries/data/mods/public/simulation/templates/structures/hele_dock.xml index 74b0300627..817f8320c6 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_dock.xml @@ -11,12 +11,12 @@ Limḗn Greece is a sea country, which is why some of the greatest Hellenic and Hellenistic cities like Ephesus, Corinth, Alexandria and Antioch were built by the sea. It should also be noted that all colonies during the Great Colonisation were thriving port centres, which traded with the local population. - + units/hele_ship_bireme units/hele_ship_trireme - + structures/hellenes/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/hele_fortress.xml index ee5b43a1fd..87536ef194 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_fortress.xml @@ -19,13 +19,13 @@ - + units/hele_mechanical_siege_oxybeles units/hele_mechanical_siege_lithobolos units/hele_mechanical_siege_tower - + structures/hellenes/fortress_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_gymnasion.xml b/binaries/data/mods/public/simulation/templates/structures/hele_gymnasion.xml index 6f0370feab..2d07c88ed6 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_gymnasion.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_gymnasion.xml @@ -33,14 +33,14 @@ attack/destruction/building_collapse_large.xml - + units/hele_champion_cavalry_mace units/hele_champion_infantry_mace units/hele_champion_infantry_polis units/hele_champion_swordsman_polis - + structures/hellenes/gymnasion.xml structures/fndn_6x6.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_prytaneion.xml b/binaries/data/mods/public/simulation/templates/structures/hele_prytaneion.xml index 905bafb4a4..b89d12f6a3 100644 --- a/binaries/data/mods/public/simulation/templates/structures/hele_prytaneion.xml +++ b/binaries/data/mods/public/simulation/templates/structures/hele_prytaneion.xml @@ -34,7 +34,7 @@ attack/destruction/building_collapse_large.xml - + units/hele_hero_alexander units/hele_hero_demetrius @@ -43,7 +43,7 @@ units/hele_hero_themistocles units/hele_hero_xenophon - + structures/hellenes/tholos.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/iber_barracks.xml index 84df9061c3..afdc3d7aae 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber_barracks.xml @@ -11,7 +11,7 @@ Cuartel To the best of our knowledge, the Iberians did not have standing armies in the sense that we know of them elsewhere or of today, it is doubtful that they had specific structures designated as military centres; however as a game construct we show a modest structure wherein military related activities take place. - + units/iber_infantry_spearman_b units/iber_infantry_swordsman_b @@ -20,7 +20,7 @@ units/iber_cavalry_spearman_b units/iber_cavalry_javelinist_b - + structures/iberians/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/iber_civil_centre.xml index 93a7f9dd51..beeacc2f45 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber_civil_centre.xml @@ -5,13 +5,13 @@ Oppidum The Oppidum, plural Oppida (oh-PEE-dah), has a long history in the Iberian Peninsula. They were walled towns, dating back to even before the time period of the game and expanding greatly during it. They were usually built upon heights for better defensive purposes but sometimes right out on the plains, especially in the east where there may not have been heights at desirable locations near meandering rivers. This concept drawing is derived from an actual archeological site that has been excavated in the northeast of Spain having belonged to the Ilergete (ee-layer-HAY-tay) tribe as shown in the figure below and from the virtual reconstruction of the site at the museum located adjacent to it. - + units/iber_infantry_swordsman_b units/iber_infantry_javelinist_b units/iber_cavalry_javelinist_b - + structures/iberians/civil_centre.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_corral.xml b/binaries/data/mods/public/simulation/templates/structures/iber_corral.xml index 16ccecf44b..9abb09f2c1 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber_corral.xml @@ -12,11 +12,11 @@ - + gaia/fauna_goat - + structures/iberians/corral.xml structures/fndn_4x4.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_dock.xml b/binaries/data/mods/public/simulation/templates/structures/iber_dock.xml index 6b4b34e082..991c8cf1ba 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber_dock.xml @@ -12,11 +12,11 @@ - + units/iber_ship_fire - + structures/iberians/dock.xml structures/fndn_dock_iber.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/iber_fortress.xml index 856ad4bc73..652493af80 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber_fortress.xml @@ -19,7 +19,7 @@ - + units/iber_hero_caros units/iber_hero_indibil @@ -28,7 +28,7 @@ units/iber_champion_cavalry units/iber_mechanical_siege_ram - + structures/iberians/fortress.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/mace_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/mace_barracks.xml index 48e6ed5092..b24d41a0fd 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace_barracks.xml @@ -15,7 +15,7 @@ Stratēgeîon The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi". - + units/mace_infantry_spearman_b units/mace_infantry_javelinist_b @@ -24,7 +24,7 @@ units/mace_cavalry_spearman_b units/mace_cavalry_javelinist_b - + structures/hellenes/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/mace_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/mace_civil_centre.xml index de4c8eee42..8e8bc4bc0a 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace_civil_centre.xml @@ -11,13 +11,13 @@ Agorā́ The most important place in most Classical Greek poleis, the Agora served many purposes; it was a place for public speeches and was the stage for civic life and commercial interests. - + units/mace_infantry_spearman_b units/mace_infantry_javelinist_b units/mace_cavalry_spearman_b - + structures/hellenes/civic_centre_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/mace_corral.xml b/binaries/data/mods/public/simulation/templates/structures/mace_corral.xml index c188bff0ea..b690df992b 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace_corral.xml @@ -18,11 +18,11 @@ - + gaia/fauna_goat - + structures/hellenes/corral.xml structures/fndn_3x3.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/mace_dock.xml b/binaries/data/mods/public/simulation/templates/structures/mace_dock.xml index 222da645ed..61a102c3a0 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace_dock.xml @@ -11,12 +11,12 @@ Limḗn Greece is a sea country, which is why some of the greatest Hellenic and Hellenistic cities like Ephesus, Corinth, Alexandria and Antioch were built by the sea. It should also be noted that all colonies during the Great Colonisation were thriving port centres, which traded with the local population. - + units/hele_ship_bireme units/hele_ship_trireme - + structures/hellenes/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/mace_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/mace_fortress.xml index d50b7d8a5f..8b4be6016c 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace_fortress.xml @@ -19,7 +19,7 @@ - + units/mace_hero_philip units/mace_hero_alexander @@ -30,7 +30,7 @@ units/mace_mechanical_siege_lithobolos units/mace_mechanical_siege_tower - + structures/hellenes/fortress_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_apadana.xml b/binaries/data/mods/public/simulation/templates/structures/pers_apadana.xml index fab2bf2860..79b8480c3d 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_apadana.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_apadana.xml @@ -32,14 +32,14 @@ true 38 - + units/pers_hero_cyrus units/pers_hero_darius units/pers_hero_xerxes units/pers_champion_infantry - + structures/persians/sb1_new.xml structures/fndn_6x6.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/pers_barracks.xml index 9007f5c0d3..f5c1c4e689 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_barracks.xml @@ -17,13 +17,13 @@ Levy citizen-infantry units. structures/pers_barracks.png - + units/pers_infantry_spearman_b units/pers_infantry_javelinist_b units/pers_infantry_archer_b - + structures/persians/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/pers_civil_centre.xml index 16dd72efe2..8000239274 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_civil_centre.xml @@ -12,13 +12,13 @@ Xsacapava Possibly of Median origin, the word 'satrapy' means province. Soon after coming to the throne, Darius the Great carried out a vast administrative reform, dividing the huge empire into 20 satrapies governed by satraps. - + units/pers_infantry_spearman_b units/pers_infantry_archer_b units/pers_cavalry_javelinist_b - + structures/persians/civil_centre.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_corral.xml b/binaries/data/mods/public/simulation/templates/structures/pers_corral.xml index d136d7a5f9..fa14bb3aba 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_corral.xml @@ -18,11 +18,11 @@ - + gaia/fauna_goat - + structures/persians/corral.xml structures/fndn_4x2.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_dock.xml b/binaries/data/mods/public/simulation/templates/structures/pers_dock.xml index 7b8e83a7ee..843214c16e 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_dock.xml @@ -18,12 +18,12 @@ - + units/pers_ship_bireme units/pers_ship_trireme - + structures/persians/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/pers_fortress.xml index 4144b77e5e..fe34b1abab 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_fortress.xml @@ -12,12 +12,12 @@ The Susa Chateau was a fortress in the administrative capital of Susa, which was reconstructed by a French archaeologist in 1890 with the use of original building material. Train Champion Units and Construct Siege Rams. - + units/pers_champion_cavalry units/pers_mechanical_siege_ram - + structures/persians/fortress.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_sb2.xml b/binaries/data/mods/public/simulation/templates/structures/pers_sb2.xml index 3de7373806..256e248c06 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_sb2.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_sb2.xml @@ -40,13 +40,13 @@ Train War Elephants and Kardakes mercenaries. false 38 - + units/pers_kardakes_hoplite units/pers_kardakes_skirmisher units/pers_war_elephant - + structures/persians/sb2.xml structures/fndn_6x6.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_stables.xml b/binaries/data/mods/public/simulation/templates/structures/pers_stables.xml index 38007e2ed9..f591699e80 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers_stables.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers_stables.xml @@ -25,14 +25,14 @@ - + units/pers_cavalry_spearman_b units/pers_cavalry_swordsman_b units/pers_cavalry_javelinist_b units/pers_cavalry_archer_b - + structures/persians/stables.xml structures/fndn_4x4.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml index f484f2d868..6fdbbc3e85 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml @@ -68,7 +68,7 @@ 5 - + units/rome_infantry_swordsman_b units/rome_infantry_spearman_a @@ -78,7 +78,7 @@ units/rome_mechanical_siege_scorpio units/rome_mechanical_siege_ram - + 60 @@ -86,4 +86,4 @@ structures/fndn_8x8.xml structures/romans/camp.xml - \ No newline at end of file + diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/rome_barracks.xml index a743ae4329..da1b54f20c 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_barracks.xml @@ -18,14 +18,14 @@ - + units/rome_infantry_swordsman_b units/rome_infantry_spearman_a units/rome_infantry_javelinist_b units/rome_cavalry_spearman_b - + structures/fndn_5x5.xml structures/romans/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/rome_civil_centre.xml index d8e8266847..83cb117ee7 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_civil_centre.xml @@ -12,13 +12,13 @@ - + units/rome_infantry_swordsman_b units/rome_infantry_javelinist_b units/rome_cavalry_spearman_b - + structures/fndn_8x8.xml structures/romans/civic_centre.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_corral.xml b/binaries/data/mods/public/simulation/templates/structures/rome_corral.xml index 677b14579b..9f400e8358 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_corral.xml @@ -12,12 +12,12 @@ - + gaia/fauna_goat gaia/fauna_sheep - + structures/fndn_2x4.xml structures/romans/corral.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_dock.xml b/binaries/data/mods/public/simulation/templates/structures/rome_dock.xml index e446992fde..287cbd68eb 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_dock.xml @@ -12,13 +12,13 @@ - + units/rome_ship_bireme units/rome_ship_trireme units/rome_ship_quinquereme - + structures/romans/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/rome_fortress.xml index cc6ca0cdd6..cb287854b9 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_fortress.xml @@ -5,7 +5,7 @@ Castellum Fortified auxillary camp. - + units/rome_hero_marcellus units/rome_hero_maximus @@ -16,7 +16,7 @@ units/rome_mechanical_siege_scorpio units/rome_mechanical_siege_ram - + structures/romans/fortress.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_barracks.xml b/binaries/data/mods/public/simulation/templates/structures/spart_barracks.xml index 599b9b353c..a1a9f8af25 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_barracks.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_barracks.xml @@ -15,14 +15,14 @@ Stratēgeîon The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi". - + units/spart_infantry_spearman_b units/spart_champion_infantry_sword units/spart_infantry_javelinist_b units/spart_cavalry_javelinist_b - + structures/hellenes/barracks.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_civil_centre.xml b/binaries/data/mods/public/simulation/templates/structures/spart_civil_centre.xml index 6996210190..499131f217 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_civil_centre.xml @@ -11,13 +11,13 @@ Agorā́ The most important place in most Classical Greek poleis, the Agora served many purposes; it was a place for public speeches and was the stage for civic life and commercial interests. - + units/spart_infantry_spearman_b units/spart_infantry_javelinist_b units/spart_cavalry_javelinist_b - + structures/hellenes/civic_centre_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_corral.xml b/binaries/data/mods/public/simulation/templates/structures/spart_corral.xml index c64c51efb3..1fc4e0ead9 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_corral.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_corral.xml @@ -18,11 +18,11 @@ - + gaia/fauna_goat - + structures/hellenes/corral.xml structures/fndn_3x3.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_dock.xml b/binaries/data/mods/public/simulation/templates/structures/spart_dock.xml index 2eeb91cc68..a976012b07 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_dock.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_dock.xml @@ -11,12 +11,12 @@ Limḗn Greece is a sea country, which is why some of the greatest Hellenic and Hellenistic cities like Ephesus, Corinth, Alexandria and Antioch were built by the sea. It should also be noted that all colonies during the Great Colonisation were thriving port centres, which traded with the local population. - + units/spart_ship_bireme units/spart_ship_trireme - + structures/hellenes/dock.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_fortress.xml b/binaries/data/mods/public/simulation/templates/structures/spart_fortress.xml index 261835602e..daee95abb8 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_fortress.xml @@ -19,11 +19,11 @@ - + units/spart_mechanical_siege_ram - + structures/hellenes/fortress_new.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_gerousia.xml b/binaries/data/mods/public/simulation/templates/structures/spart_gerousia.xml index 31752dd4e3..b698ceb722 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_gerousia.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_gerousia.xml @@ -34,11 +34,11 @@ attack/destruction/building_collapse_large.xml - + units/hele_hero_leonidas - + structures/hellenes/tholos.xml diff --git a/binaries/data/mods/public/simulation/templates/structures/spart_syssiton.xml b/binaries/data/mods/public/simulation/templates/structures/spart_syssiton.xml index 550c3320dd..377e2bbee2 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart_syssiton.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart_syssiton.xml @@ -33,12 +33,12 @@ attack/destruction/building_collapse_large.xml - + units/hele_hero_leonidas units/spart_champion_infantry_spear - + structures/hellenes/gymnasion.xml structures/fndn_6x6.xml diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml index f6e57c2cdd..2ef1c9651a 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -62,6 +62,7 @@ CivCentre structures/civic_centre.png + town_phase 200 @@ -89,11 +90,16 @@ 180 65536 - + units/{civ}_support_female_citizen - + + town_phase + city_phase + plough + + 90 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml index 72b6b46cd5..470069cae9 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml @@ -63,11 +63,11 @@ 40 65536 - + units/{civ}_support_healer_b - + 40 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml index 1f1ca880fa..690c9b1e27 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml @@ -49,11 +49,11 @@ 40 65536 - + units/{civ}_support_trader - + 32 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml b/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml index 8a263828e1..86785488a7 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml @@ -57,12 +57,12 @@ - + units/{civ}_ship_fishing units/{civ}_ship_merchant - + 40 diff --git a/binaries/data/mods/public/simulation/templates/units/athen_ship_trireme.xml b/binaries/data/mods/public/simulation/templates/units/athen_ship_trireme.xml index 58cdd94ce0..83c93562cc 100644 --- a/binaries/data/mods/public/simulation/templates/units/athen_ship_trireme.xml +++ b/binaries/data/mods/public/simulation/templates/units/athen_ship_trireme.xml @@ -9,11 +9,11 @@ Ramming Secondary Attack. The first Triremes were built circa 650BC, and by 500BC the Trireme was the most widely used heavy warship of the Greek city-states. In the Trireme the outriggers were now an integral part of the ship's hull. The Trireme also had a partial or full fighting deck above the rowers. The length of the Trireme remained approximately 35-38 meters, and the beam was approximately 3.5 metres. A Trireme carried 170 oarsmen, plus twenty sailors and fourteen marines in Greek navies. The top speed of a Trireme was approximately 11.5 knots. Some Triremes may have been able to reach higher speeds in short bursts. A Trireme travelling from Athens to Mitylene in 427BC made the 350 kilometre trip in only 24 hours, averaging eight knots (14.6 km/h). The Trireme could accelerate much faster than a Bireme or Penteconter, and was much more manoeuvrable. This gave the Trireme an advantage in combat, where higher speed and manoeuvrability meant a better chance of victory. units/hele_ship_trireme.png - + units/athen_champion_marine - + structures/athenians/trireme.xml diff --git a/binaries/data/mods/public/simulation/templates/units/pers_hero_cyrus.xml b/binaries/data/mods/public/simulation/templates/units/pers_hero_cyrus.xml index bb94c1113f..ac875d47bb 100644 --- a/binaries/data/mods/public/simulation/templates/units/pers_hero_cyrus.xml +++ b/binaries/data/mods/public/simulation/templates/units/pers_hero_cyrus.xml @@ -9,11 +9,11 @@ Hero Aura: "Lead from the Front." Boosts attack of nearby cavalry units. (559 BC - 530 BC) The son of a Median princess and the ruler of Anshan; justly called the 'Father of the Empire', Cyrus the Great conquered Media, Lydia, Babylonia and Bactria, thereby establishing the Persian Empire. He was also renown as a benevolent conqueror. Technically the second ruler of the Persians by that name, and so appears as Kurush II on his documents and coins. Kurush I was his grandfather. - + units/pers_champion_infantry - + units/persians/hero_cyrus.xml diff --git a/binaries/data/mods/public/simulation/templates/units/pers_infantry_spearman_b.xml b/binaries/data/mods/public/simulation/templates/units/pers_infantry_spearman_b.xml index c1b6047915..1cce7dc294 100644 --- a/binaries/data/mods/public/simulation/templates/units/pers_infantry_spearman_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/pers_infantry_spearman_b.xml @@ -18,8 +18,8 @@ pers - units/pers_infantry_spearman_b - Median Shieldbearer + units/pers_infantry_spearman_b + Median Shieldbearer Mada Sparabara units/pers_infantry_spearman.png Mede Shieldbearers comprised the main infantry regiment of the Persians during this period, especially in the reign of Xerxes. First under the Medes and later the Achaemenids these soldiers were the bread and butter infantry for hand-to-hand engagement. Within the Satabam, the basic tactical unit of the Achamenid army, the shieldbearers formed the first two ranks, protecting the arhcers and also serving as a way to keep the enemy pinned down until the cavalry could act. While well known for tenacity the shieldbearers were not equipped to last long in an extended melee with heavy infantry, like hoplites. diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index 893b6545e5..62840e40ee 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -124,6 +124,7 @@ public: LOAD_SCRIPTED_COMPONENT("EndGameManager"); LOAD_SCRIPTED_COMPONENT("GuiInterface"); LOAD_SCRIPTED_COMPONENT("PlayerManager"); + LOAD_SCRIPTED_COMPONENT("TechnologyTemplateManager"); LOAD_SCRIPTED_COMPONENT("Timer"); #undef LOAD_SCRIPTED_COMPONENT diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index a2e244f920..1013b259f4 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -81,6 +81,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); m_ScriptInterface.RegisterFunction ("ReadJSONFile"); + m_ScriptInterface.RegisterFunction, std::wstring, CComponentManager::Script_FindJSONFiles> ("FindJSONFiles"); } // Define MT_*, IID_* as script globals, and store their names @@ -944,3 +945,34 @@ CScriptVal CComponentManager::Script_ReadJSONFile(void* cbdata, std::wstring fil return componentManager->GetScriptInterface().ReadJSONFile(path).get(); } + +std::vector CComponentManager::Script_FindJSONFiles(void* UNUSED(cbdata), std::wstring subPath) +{ + VfsPath path(L"simulation/data/" + subPath + L"/"); + VfsPaths pathnames; + + std::vector templates; + + // Find all simulation/data/{subPath}/*.json + Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames); + if (ret == INFO::OK) + { + for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it) + { + // Strip the .json extension + VfsPath pathstem = it->ChangeExtension(L""); + // Strip the root from the path + std::wstring name = pathstem.string().substr(path.string().length()); + + templates.push_back(std::string(name.begin(), name.end())); + } + } + else + { + // Some error reading directory + wchar_t error[200]; + LOGERROR(L"Error reading directory '%ls': %ls", path.string().c_str(), StatusDescription(ret, error, ARRAY_SIZE(error))); + } + + return templates; +} diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h index 92dbdd09d2..8ca0ba81e7 100644 --- a/source/simulation2/system/ComponentManager.h +++ b/source/simulation2/system/ComponentManager.h @@ -233,6 +233,7 @@ private: static int Script_AddLocalEntity(void* cbdata, std::string templateName); static void Script_DestroyEntity(void* cbdata, int ent); static CScriptVal Script_ReadJSONFile(void* cbdata, std::wstring fileName); + static std::vector Script_FindJSONFiles(void* cbdata, std::wstring subPath); CMessage* ConstructMessage(int mtid, CScriptVal data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg) const;