1
0
forked from 0ad/0ad

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.
This commit is contained in:
Jonathan Waller 2012-04-20 17:21:04 +00:00
parent be0109331c
commit 755e407aeb
111 changed files with 1510 additions and 652 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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)

View File

@ -727,11 +727,15 @@
</object>
<object name="unitResearchPanel"
style="TranslucentPanelThinBorder"
size="0 100%-56 100% 100%"
type="text"
size="14 100%-56 100% 100%"
>
<object size="0 0 100% 100%">
<repeat count="8">
<object name="unitResearchButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitResearchIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
</object>
</repeat>
</object>
[research commands]
</object>

View File

@ -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"];
@ -164,6 +165,11 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
numberOfItems = 24;
break;
case RESEARCH:
if (numberOfItems > 8)
numberOfItems = 8;
break;
case CONSTRUCTION:
if (numberOfItems > 24)
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)
{
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)
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);
@ -251,6 +298,15 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
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);
if (template.tooltip)
@ -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
{
@ -538,15 +612,21 @@ 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.training && entState.training.queue.length)
setupUnitPanel("Queue", usedPanels, entState, entState.training.queue,
function (item) { removeFromTrainingQueue(entState.id, item.id); } );
if (entState.production && entState.production.technologies.length)
{
setupUnitPanel("Research", usedPanels, entState, entState.production.technologies,
function (researchType) { addResearchToQueue(entState.id, researchType); } );
}
if (entState.production && entState.production.queue.length)
setupUnitPanel("Queue", usedPanels, entState, entState.production.queue,
function (item) { removeFromProductionQueue(entState.id, item.id); } );
if (entState.trader)
{

View File

@ -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,

View File

@ -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;
},

View File

@ -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,

View File

@ -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;
},

View File

@ -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);

View File

@ -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,

View File

@ -68,6 +68,11 @@ Identity.prototype.Schema =
"<element name='Icon'>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='RequiredTechnology' a:help='Optional name of a technology which must be researched before the entity can be produced'>" +
"<text/>" +
"</element>" +
"</optional>";
@ -126,6 +131,6 @@ Identity.prototype.CanUseFormation = function(name)
Identity.prototype.GetSelectionGroupName = function()
{
return (this.template.SelectionGroupName || "");
}
};
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);

View File

@ -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;

View File

@ -0,0 +1,538 @@
var g_ProgressInterval = 1000;
const MAX_QUEUE_SIZE = 16;
function ProductionQueue() {}
ProductionQueue.prototype.Schema =
"<a:help>Allows the building to train new units and research technologies</a:help>" +
"<a:example>" +
"<Entities datatype='tokens'>" +
"\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"</Entities>" +
"</a:example>" +
"<optional>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this building can train. The special string \"{civ}\" will be automatically replaced by the building&apos;s four-character civ code'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Technologies' a:help='Space-separated list of technology names that this building can research.'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>";
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);

View File

@ -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 = {};
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)
ret[r] = this.template.Rates[r] * this.template.BaseSpeed;
return ret;
{
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.

View File

@ -0,0 +1,320 @@
function TechnologyManager() {}
TechnologyManager.prototype.Schema =
"<a:component type='system'/><empty/>";
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);

View File

@ -0,0 +1,39 @@
/**
* System component which loads the technology data files
*/
function TechnologyTemplateManager() {}
TechnologyTemplateManager.prototype.Schema =
"<a:component type='system'/><empty/>";
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);

View File

@ -1,400 +0,0 @@
var g_ProgressInterval = 1000;
const MAX_QUEUE_SIZE = 16;
function TrainingQueue() {}
TrainingQueue.prototype.Schema =
"<a:help>Allows the building to train new units.</a:help>" +
"<a:example>" +
"<Entities datatype='tokens'>" +
"\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"</Entities>" +
"</a:example>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this building can train. The special string \"{civ}\" will be automatically replaced by the building&apos;s four-character civ code'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>";
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);

View File

@ -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.

View File

@ -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");

View File

@ -0,0 +1 @@
Engine.RegisterInterface("TechnologyTemplateManager");

View File

@ -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",

View File

@ -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"
}

View File

@ -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"]
}

View File

@ -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"
}

View File

@ -0,0 +1,4 @@
{
"genericName": "Village Phase",
"autoResearch": true
}

View File

@ -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);
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, +cmd.count, cmd.metadata);
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;
@ -234,6 +266,22 @@ function ProcessCommand(player, cmd)
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())
{

View File

@ -35,13 +35,13 @@
<Radius>150</Radius>
<Weight>35000</Weight>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
-units/{civ}_support_female_citizen
campaigns/army_mace_hero_alexander
campaigns/army_mace_standard
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>campaigns/structures/hellenes/settlement_curtainwall.xml</Actor>
</VisualActor>

View File

@ -35,14 +35,14 @@
<Radius>300</Radius>
<Weight>35000</Weight>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
-units/{civ}_support_female_citizen
campaigns/army_mace_hero_alexander
campaigns/army_mace_standard
units/{civ}_support_trader
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>campaigns/structures/hellenes/settlement_curtainwall.xml</Actor>
</VisualActor>

View File

@ -20,7 +20,7 @@
<Radius>100</Radius>
<Weight>65536</Weight>
</TerritoryInfluence>
<TrainingQueue disable=""/>
<ProductionQueue disable=""/>
<VisualActor>
<Actor>structures/hellenes/temple_new.xml</Actor>
</VisualActor>

View File

@ -34,11 +34,11 @@
<Static width="22.0" depth="24.0"/>
</Obstruction>
<TerritoryDecay disable=""/>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_sacred_band_cavalry
</Entities>
</TrainingQueue>
</ProductionQueue>
<Vision>
<Range>40</Range>
</Vision>

View File

@ -35,13 +35,13 @@
<ResourceDropsite>
<Types>food wood stone metal</Types>
</ResourceDropsite>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_cavalry_javelinist_b
units/celt_infantry_javelinist_b
units/celt_infantry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<TerritoryInfluence>
<Radius>100</Radius>
<Weight>65536</Weight>

View File

@ -39,11 +39,11 @@
<Radius>32</Radius>
<Weight>65536</Weight>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_fanatic
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/tavern.xml</Actor>
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>

View File

@ -48,13 +48,13 @@
<Radius>40</Radius>
<Weight>65536</Weight>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/thrace_black_cloak
units/mace_thorakites
units/mace_thureophoros
</Entities>
</TrainingQueue>
</ProductionQueue>
<Vision>
<Range>40</Range>
<RetainInFog>true</RetainInFog>

View File

@ -21,11 +21,11 @@
<TerritoryInfluence>
<Radius>25</Radius>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/{civ}_support_female_citizen
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>props/structures/persians/alt_building_03.xml</Actor>
</VisualActor>

View File

@ -21,11 +21,11 @@
<TerritoryInfluence>
<Radius>26</Radius>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/{civ}_support_female_citizen
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>props/structures/persians/alt_building_04.xml</Actor>
</VisualActor>

View File

@ -10,4 +10,5 @@
</BuildLimits>
<Player/>
<StatisticsTracker/>
<TechnologyManager/>
</Entity>

View File

@ -15,7 +15,7 @@
<SpecificName>Stratēgeîon</SpecificName>
<History>The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi".</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_infantry_spearman_b
units/athen_infantry_javelinist_b
@ -23,7 +23,7 @@
units/athen_cavalry_swordsman_b
units/athen_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/barracks.xml</Actor>
</VisualActor>

View File

@ -11,13 +11,13 @@
<SpecificName>Agorā́</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_infantry_spearman_b
units/athen_infantry_slinger_b
units/athen_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/civic_centre_new.xml</Actor>
</VisualActor>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="14.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/corral.xml</Actor>
<FoundationActor>structures/fndn_3x3.xml</FoundationActor>

View File

@ -11,12 +11,12 @@
<SpecificName>Limḗn</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_ship_bireme
units/athen_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/dock.xml</Actor>
</VisualActor>

View File

@ -19,12 +19,12 @@
<Obstruction>
<Static width="24.0" depth="26.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_mechanical_siege_oxybeles
units/athen_mechanical_siege_lithobolos
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/fortress.xml</Actor>
</VisualActor>

View File

@ -33,12 +33,12 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_champion_infantry
units/athen_champion_ranged
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/gymnasion.xml</Actor>
<FoundationActor>structures/fndn_6x6.xml</FoundationActor>

View File

@ -34,13 +34,13 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/athen_hero_themistocles
units/athen_hero_pericles
units/athen_hero_xenophon
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/athenians/prytaneion.xml</Actor>
</VisualActor>

View File

@ -19,13 +19,13 @@
<Obstruction>
<Static width="22.0" depth="23.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_spearman_b
units/cart_infantry_archer_b
units/cart_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/barracks.xml</Actor>
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>

View File

@ -5,13 +5,13 @@
<SpecificName>Merkāz</SpecificName>
<History>Carthiginian's History</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_spearman_b
units/cart_infantry_archer_b
units/cart_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/civil_centre.xml</Actor>
</VisualActor>

View File

@ -12,11 +12,11 @@
<Obstruction>
<Static width="16.0" depth="14.5"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/corral.xml</Actor>
<FoundationActor>structures/fndn_3x3.xml</FoundationActor>

View File

@ -21,7 +21,7 @@
<Obstruction>
<Static width="28.0" depth="28.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_swordsman_b
units/cart_infantry_javelinist_b
@ -29,7 +29,7 @@
units/cart_cavalry_swordsman_b
units/cart_cavalry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/embassy.xml</Actor>
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>

View File

@ -24,12 +24,12 @@
<Obstruction>
<Static width="15.0" depth="12.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_swordsman_b
units/cart_cavalry_swordsman_2_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/embassy_celtic.xml</Actor>
</VisualActor>

View File

@ -17,13 +17,13 @@
<Obstruction>
<Static width="16.0" depth="16.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_javelinist_b
units/cart_infantry_slinger_b
units/cart_cavalry_swordsman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/embassy_iberian.xml</Actor>
</VisualActor>

View File

@ -20,12 +20,12 @@
<Obstruction>
<Static width="11.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_infantry_swordsman_2_b
units/cart_cavalry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/embassy_italiote.xml</Actor>
</VisualActor>

View File

@ -13,7 +13,7 @@
<Obstruction>
<Static width="26.0" depth="28.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_hero_hamilcar
units/cart_hero_hannibal
@ -22,7 +22,7 @@
units/cart_mechanical_siege_ballista
units/cart_mechanical_siege_oxybeles
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/fortress.xml</Actor>
</VisualActor>

View File

@ -46,13 +46,13 @@
</Sound>
<TerritoryDecay disable=""/>
<TerritoryInfluence disable=""/>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_ship_bireme
units/cart_ship_trireme
units/cart_ship_quinquereme
</Entities>
</TrainingQueue>
</ProductionQueue>
<Vision>
<Range>100</Range>
</Vision>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="17.0" depth="30.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/cart_champion_infantry
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/carthaginians/temple_big.xml</Actor>
</VisualActor>

View File

@ -14,7 +14,7 @@
<Obstruction>
<Static width="20.0" depth="20.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_infantry_spearman_b
units/celt_infantry_javelinist_b
@ -22,7 +22,7 @@
units/celt_cavalry_swordsman_b
units/celt_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/barracks_new.xml</Actor>
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>

View File

@ -12,13 +12,13 @@
<Obstruction>
<Static width="25.0" depth="25.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_infantry_spearman_b
units/celt_infantry_javelinist_b
units/celt_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/civic_centre.xml</Actor>
</VisualActor>

View File

@ -12,11 +12,11 @@
<Obstruction>
<Static width="10.0" depth="20.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_sheep
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/plot_corral.xml</Actor>
</VisualActor>

View File

@ -15,11 +15,11 @@
<Obstruction>
<Static width="10.0" depth="22.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/dock_new.xml</Actor>
<FoundationActor>structures/fndn_celt_dock.xml</FoundationActor>

View File

@ -29,7 +29,7 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_hero_boudicca
units/celt_hero_caratacos
@ -38,7 +38,7 @@
units/celt_champion_infantry_brit
units/celt_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<Vision>
<Range>100</Range>
</Vision>

View File

@ -16,7 +16,7 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_hero_brennus
units/celt_hero_britomartus
@ -25,7 +25,7 @@
units/celt_champion_infantry_gaul
units/celt_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/fortress_gallic.xml</Actor>
</VisualActor>

View File

@ -40,11 +40,11 @@
<Radius>20</Radius>
<Weight>65536</Weight>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/celt_war_dog_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/celts/kennel.xml</Actor>
<FoundationActor>structures/fndn_2x2.xml</FoundationActor>

View File

@ -15,7 +15,7 @@
<SpecificName>Stratēgeîon</SpecificName>
<History>The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi".</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_infantry_spearman_b
units/hele_infantry_javelinist_b
@ -24,7 +24,7 @@
units/hele_cavalry_swordsman_b
units/hele_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/barracks.xml</Actor>
</VisualActor>

View File

@ -11,13 +11,13 @@
<SpecificName>Agorā́</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_infantry_spearman_b
units/hele_infantry_javelinist_b
units/hele_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/civic_centre_new.xml</Actor>
</VisualActor>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="14.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/corral.xml</Actor>
<FoundationActor>structures/fndn_3x3.xml</FoundationActor>

View File

@ -11,12 +11,12 @@
<SpecificName>Limḗn</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_ship_bireme
units/hele_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/dock.xml</Actor>
</VisualActor>

View File

@ -19,13 +19,13 @@
<Obstruction>
<Static width="24.0" depth="26.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_mechanical_siege_oxybeles
units/hele_mechanical_siege_lithobolos
units/hele_mechanical_siege_tower
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/fortress_new.xml</Actor>
</VisualActor>

View File

@ -33,14 +33,14 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_champion_cavalry_mace
units/hele_champion_infantry_mace
units/hele_champion_infantry_polis
units/hele_champion_swordsman_polis
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/gymnasion.xml</Actor>
<FoundationActor>structures/fndn_6x6.xml</FoundationActor>

View File

@ -34,7 +34,7 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_hero_alexander
units/hele_hero_demetrius
@ -43,7 +43,7 @@
units/hele_hero_themistocles
units/hele_hero_xenophon
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/tholos.xml</Actor>
</VisualActor>

View File

@ -11,7 +11,7 @@
<SpecificName>Cuartel</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/iber_infantry_spearman_b
units/iber_infantry_swordsman_b
@ -20,7 +20,7 @@
units/iber_cavalry_spearman_b
units/iber_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/iberians/barracks.xml</Actor>
</VisualActor>

View File

@ -5,13 +5,13 @@
<SpecificName>Oppidum</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/iber_infantry_swordsman_b
units/iber_infantry_javelinist_b
units/iber_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/iberians/civil_centre.xml</Actor>
</VisualActor>

View File

@ -12,11 +12,11 @@
<Obstruction>
<Static width="14.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/iberians/corral.xml</Actor>
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>

View File

@ -12,11 +12,11 @@
<Obstruction>
<Static width="14.0" depth="24.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/iber_ship_fire
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/iberians/dock.xml</Actor>
<FoundationActor>structures/fndn_dock_iber.xml</FoundationActor>

View File

@ -19,7 +19,7 @@
<Obstruction>
<Static width="27.0" depth="27.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/iber_hero_caros
units/iber_hero_indibil
@ -28,7 +28,7 @@
units/iber_champion_cavalry
units/iber_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/iberians/fortress.xml</Actor>
</VisualActor>

View File

@ -15,7 +15,7 @@
<SpecificName>Stratēgeîon</SpecificName>
<History>The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi".</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/mace_infantry_spearman_b
units/mace_infantry_javelinist_b
@ -24,7 +24,7 @@
units/mace_cavalry_spearman_b
units/mace_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/barracks.xml</Actor>
</VisualActor>

View File

@ -11,13 +11,13 @@
<SpecificName>Agorā́</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/mace_infantry_spearman_b
units/mace_infantry_javelinist_b
units/mace_cavalry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/civic_centre_new.xml</Actor>
</VisualActor>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="14.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/corral.xml</Actor>
<FoundationActor>structures/fndn_3x3.xml</FoundationActor>

View File

@ -11,12 +11,12 @@
<SpecificName>Limḗn</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_ship_bireme
units/hele_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/dock.xml</Actor>
</VisualActor>

View File

@ -19,7 +19,7 @@
<Obstruction>
<Static width="24.0" depth="26.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/mace_hero_philip
units/mace_hero_alexander
@ -30,7 +30,7 @@
units/mace_mechanical_siege_lithobolos
units/mace_mechanical_siege_tower
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/fortress_new.xml</Actor>
</VisualActor>

View File

@ -32,14 +32,14 @@
<Root>true</Root>
<Radius>38</Radius>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_hero_cyrus
units/pers_hero_darius
units/pers_hero_xerxes
units/pers_champion_infantry
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/sb1_new.xml</Actor>
<FoundationActor>structures/fndn_6x6.xml</FoundationActor>

View File

@ -17,13 +17,13 @@
<Tooltip>Levy citizen-infantry units.</Tooltip>
<Icon>structures/pers_barracks.png</Icon>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_infantry_spearman_b
units/pers_infantry_javelinist_b
units/pers_infantry_archer_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/barracks.xml</Actor>
</VisualActor>

View File

@ -12,13 +12,13 @@
<SpecificName>Xsacapava</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_infantry_spearman_b
units/pers_infantry_archer_b
units/pers_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/civil_centre.xml</Actor>
</VisualActor>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="17.0" depth="11.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/corral.xml</Actor>
<FoundationActor>structures/fndn_4x2.xml</FoundationActor>

View File

@ -18,12 +18,12 @@
<Obstruction>
<Static width="23.0" depth="15.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_ship_bireme
units/pers_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/dock.xml</Actor>
</VisualActor>

View File

@ -12,12 +12,12 @@
<History>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.</History>
<Tooltip>Train Champion Units and Construct Siege Rams.</Tooltip>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_champion_cavalry
units/pers_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/fortress.xml</Actor>
</VisualActor>

View File

@ -40,13 +40,13 @@ Train War Elephants and Kardakes mercenaries.</Tooltip>
<Root>false</Root>
<Radius>38</Radius>
</TerritoryInfluence>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_kardakes_hoplite
units/pers_kardakes_skirmisher
units/pers_war_elephant
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/sb2.xml</Actor>
<FoundationActor>structures/fndn_6x6.xml</FoundationActor>

View File

@ -25,14 +25,14 @@
<Obstruction>
<Static width="18.0" depth="16.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/pers_cavalry_spearman_b
units/pers_cavalry_swordsman_b
units/pers_cavalry_javelinist_b
units/pers_cavalry_archer_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/persians/stables.xml</Actor>
<FoundationActor>structures/fndn_4x4.xml</FoundationActor>

View File

@ -68,7 +68,7 @@
<HealthDecayRate>5</HealthDecayRate>
</TerritoryDecay>
<TerritoryInfluence disable=""/>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/rome_infantry_swordsman_b
units/rome_infantry_spearman_a
@ -78,7 +78,7 @@
units/rome_mechanical_siege_scorpio
units/rome_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<Vision>
<Range>60</Range>
</Vision>

View File

@ -18,14 +18,14 @@
<Obstruction>
<Static width="24.0" depth="24.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/rome_infantry_swordsman_b
units/rome_infantry_spearman_a
units/rome_infantry_javelinist_b
units/rome_cavalry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<FoundationActor>structures/fndn_5x5.xml</FoundationActor>
<Actor>structures/romans/barracks.xml</Actor>

View File

@ -12,13 +12,13 @@
<Obstruction>
<Static width="37.0" depth="37.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/rome_infantry_swordsman_b
units/rome_infantry_javelinist_b
units/rome_cavalry_spearman_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<FoundationActor>structures/fndn_8x8.xml</FoundationActor>
<Actor>structures/romans/civic_centre.xml</Actor>

View File

@ -12,12 +12,12 @@
<Obstruction>
<Static width="14" depth="20"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
gaia/fauna_sheep
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<FoundationActor>structures/fndn_2x4.xml</FoundationActor>
<Actor>structures/romans/corral.xml</Actor>

View File

@ -12,13 +12,13 @@
<Obstruction>
<Static width="20.0" depth="24.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/rome_ship_bireme
units/rome_ship_trireme
units/rome_ship_quinquereme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/romans/dock.xml</Actor>
</VisualActor>

View File

@ -5,7 +5,7 @@
<SpecificName>Castellum</SpecificName>
<History>Fortified auxillary camp.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/rome_hero_marcellus
units/rome_hero_maximus
@ -16,7 +16,7 @@
units/rome_mechanical_siege_scorpio
units/rome_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/romans/fortress.xml</Actor>
</VisualActor>

View File

@ -15,14 +15,14 @@
<SpecificName>Stratēgeîon</SpecificName>
<History>The Stratigeion was the main military headquarters, where important decisions were taken and plans for battles discussed by the Hellene Generals, or "Strategoi".</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/spart_infantry_spearman_b
units/spart_champion_infantry_sword
units/spart_infantry_javelinist_b
units/spart_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/barracks.xml</Actor>
</VisualActor>

View File

@ -11,13 +11,13 @@
<SpecificName>Agorā́</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/spart_infantry_spearman_b
units/spart_infantry_javelinist_b
units/spart_cavalry_javelinist_b
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/civic_centre_new.xml</Actor>
</VisualActor>

View File

@ -18,11 +18,11 @@
<Obstruction>
<Static width="14.0" depth="14.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
gaia/fauna_goat
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/corral.xml</Actor>
<FoundationActor>structures/fndn_3x3.xml</FoundationActor>

View File

@ -11,12 +11,12 @@
<SpecificName>Limḗn</SpecificName>
<History>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.</History>
</Identity>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/spart_ship_bireme
units/spart_ship_trireme
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/dock.xml</Actor>
</VisualActor>

View File

@ -19,11 +19,11 @@
<Obstruction>
<Static width="24.0" depth="26.0"/>
</Obstruction>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/spart_mechanical_siege_ram
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/fortress_new.xml</Actor>
</VisualActor>

View File

@ -34,11 +34,11 @@
<death>attack/destruction/building_collapse_large.xml</death>
</SoundGroups>
</Sound>
<TrainingQueue>
<ProductionQueue>
<Entities datatype="tokens">
units/hele_hero_leonidas
</Entities>
</TrainingQueue>
</ProductionQueue>
<VisualActor>
<Actor>structures/hellenes/tholos.xml</Actor>
</VisualActor>

Some files were not shown because too many files have changed in this diff Show More