diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index 7160e49226..74fc97670e 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -1467,17 +1467,76 @@ function exchangeResources(command)
var batchTrainingEntities;
var batchTrainingType;
var batchTrainingCount;
+var batchTrainingEntityAllowedCount;
const batchIncrementSize = 5;
function flushTrainingBatch()
{
- Engine.PostNetworkCommand({"type": "train", "entities": batchTrainingEntities, "template": batchTrainingType, "count": batchTrainingCount});
+ var appropriateBuildings = getBuidlingsWhichCanTrainEntity(batchTrainingEntities, batchTrainingType);
+ // If training limits don't allow us to train batchTrainingCount in each appropriate building
+ if (batchTrainingEntityAllowedCount !== undefined &&
+ batchTrainingEntityAllowedCount < batchTrainingCount * appropriateBuildings.length)
+ {
+ // Train as many full batches as we can
+ var buildingsCountToTrainFullBatch = Math.floor(batchTrainingEntityAllowedCount / batchTrainingCount);
+ var buildingsToTrainFullBatch = appropriateBuildings.slice(0, buildingsCountToTrainFullBatch);
+ Engine.PostNetworkCommand({"type": "train", "entities": buildingsToTrainFullBatch,
+ "template": batchTrainingType, "count": batchTrainingCount});
+
+ // Train remainer in one more building
+ var remainderToTrain = batchTrainingEntityAllowedCount % batchTrainingCount;
+ Engine.PostNetworkCommand({"type": "train",
+ "entities": [ appropriateBuildings[buildingsCountToTrainFullBatch] ],
+ "template": batchTrainingType, "count": remainderToTrain});
+ }
+ else
+ {
+ Engine.PostNetworkCommand({"type": "train", "entities": appropriateBuildings,
+ "template": batchTrainingType, "count": batchTrainingCount});
+ }
+}
+
+function getBuidlingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
+{
+ return entitiesToCheck.filter(function(entity) {
+ var state = GetEntityState(entity);
+ var canTrain = state && state.production && state.production.entities.length &&
+ state.production.entities.indexOf(trainEntType) != -1;
+ return canTrain;
+ });
+}
+
+function getEntityLimitAndCount(playerState, entType)
+{
+ var template = GetTemplateData(entType);
+ var trainingCategory = null;
+ if (template.trainingRestrictions)
+ trainingCategory = template.trainingRestrictions.category;
+ var trainEntLimit = undefined;
+ var trainEntCount = undefined;
+ var canBeTrainedCount = undefined;
+ if (trainingCategory && playerState.entityLimits[trainingCategory])
+ {
+ trainEntLimit = playerState.entityLimits[trainingCategory];
+ trainEntCount = playerState.entityCounts[trainingCategory];
+ canBeTrainedCount = trainEntLimit - trainEntCount;
+ }
+ return [trainEntLimit, trainEntCount, canBeTrainedCount];
}
// Called by GUI when user clicks training button
-function addTrainingToQueue(selection, trainEntType)
+function addTrainingToQueue(selection, trainEntType, playerState)
{
- if (Engine.HotkeyIsPressed("session.batchtrain"))
+ // Create list of buildings which can train trainEntType
+ var appropriateBuildings = getBuidlingsWhichCanTrainEntity(selection, trainEntType);
+
+ // Check trainEntType entity limit and count
+ var [trainEntLimit, trainEntCount, canBeTrainedCount] = getEntityLimitAndCount(playerState, trainEntType)
+
+ // Batch training possible if we can train at least 2 units
+ var batchTrainingPossible = canBeTrainedCount == undefined || canBeTrainedCount > 1;
+
+ if (Engine.HotkeyIsPressed("session.batchtrain") && batchTrainingPossible)
{
if (inputState == INPUT_BATCHTRAINING)
{
@@ -1494,9 +1553,13 @@ function addTrainingToQueue(selection, trainEntType)
}
}
// If we're already creating a batch of this unit (in the same building(s)), then just extend it
+ // (if training limits allow)
if (sameEnts && batchTrainingType == trainEntType)
{
- batchTrainingCount += batchIncrementSize;
+ if (canBeTrainedCount == undefined ||
+ canBeTrainedCount > batchTrainingCount * appropriateBuildings.length)
+ batchTrainingCount += batchIncrementSize;
+ batchTrainingEntityAllowedCount = canBeTrainedCount;
return;
}
// Otherwise start a new one
@@ -1509,12 +1572,18 @@ function addTrainingToQueue(selection, trainEntType)
inputState = INPUT_BATCHTRAINING;
batchTrainingEntities = selection;
batchTrainingType = trainEntType;
+ batchTrainingEntityAllowedCount = canBeTrainedCount;
batchTrainingCount = batchIncrementSize;
}
else
{
- // Non-batched - just create a single entity
- Engine.PostNetworkCommand({"type": "train", "template": trainEntType, "count": 1, "entities": selection});
+ // Non-batched - just create a single entity in each building
+ // (but no more than entity limit allows)
+ var buildingsForTraining = appropriateBuildings;
+ if (trainEntLimit)
+ buildingsForTraining = buildingsForTraining.slice(0, canBeTrainedCount);
+ Engine.PostNetworkCommand({"type": "train", "template": trainEntType,
+ "count": 1, "entities": buildingsForTraining});
}
}
@@ -1526,12 +1595,39 @@ function addResearchToQueue(entity, researchType)
// Returns the number of units that will be present in a batch if the user clicks
// the training button with shift down
-function getTrainingBatchStatus(entity, trainEntType)
+function getTrainingBatchStatus(playerState, entity, trainEntType, selection)
{
- if (inputState == INPUT_BATCHTRAINING && batchTrainingEntities.indexOf(entity) != -1 && batchTrainingType == trainEntType)
- return [batchTrainingCount, batchIncrementSize];
+ var apporpriateBuildings = [entity];
+ if (selection && selection.indexOf(entity) != -1)
+ appropriateBuildings = getBuidlingsWhichCanTrainEntity(selection, trainEntType);
+ var nextBatchTrainingCount = 0;
+ if (inputState == INPUT_BATCHTRAINING && batchTrainingEntities.indexOf(entity) != -1 &&
+ batchTrainingType == trainEntType)
+ {
+ nextBatchTrainingCount = batchTrainingCount;
+ var canBeTrainedCount = batchTrainingEntityAllowedCount;
+ }
else
- return [0, batchIncrementSize];
+ {
+ var [trainEntLimit, trainEntCount, canBeTrainedCount] =
+ getEntityLimitAndCount(playerState, trainEntType);
+ var batchSize = Math.min(canBeTrainedCount, batchIncrementSize);
+ }
+ // We need to calculate count after the next increment if it's possible
+ if (canBeTrainedCount == undefined ||
+ canBeTrainedCount > nextBatchTrainingCount * appropriateBuildings.length)
+ nextBatchTrainingCount += batchIncrementSize;
+ // If training limits don't allow us to train batchTrainingCount in each appropriate building
+ // train as many full batches as we can and remainer in one more building.
+ var buildingsCountToTrainFullBatch = appropriateBuildings.length;
+ var remainderToTrain = 0;
+ if (canBeTrainedCount !== undefined &&
+ canBeTrainedCount < nextBatchTrainingCount * appropriateBuildings.length)
+ {
+ buildingsCountToTrainFullBatch = Math.floor(canBeTrainedCount / nextBatchTrainingCount);
+ remainderToTrain = canBeTrainedCount % nextBatchTrainingCount;
+ }
+ return [buildingsCountToTrainFullBatch, nextBatchTrainingCount, remainderToTrain];
}
// Called by GUI when user clicks production queue item
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
index 528c9bdf75..98dab51752 100644
--- a/binaries/data/mods/public/gui/session/unit_commands.js
+++ b/binaries/data/mods/public/gui/session/unit_commands.js
@@ -138,6 +138,65 @@ function setOverlay(object, value)
object.hidden = !value;
}
+/**
+ * Format entity count/limit message for the tooltip
+ */
+function formatLimitString(trainEntLimit, trainEntCount)
+{
+ if (trainEntLimit == undefined)
+ return "";
+ var text = "\n\nCurrent count: " + trainEntCount + ", limit: " + trainEntLimit + ".";
+ if (trainEntCount >= trainEntLimit)
+ text = "[color=\"red\"]" + text + "[/color]";
+ return text;
+}
+
+/**
+ * Format batch training string for the tooltip
+ * Examples:
+ * buildingsCountToTrainFullBatch = 1, fullBatchSize = 5, remainderBatch = 0:
+ * "Shift-click to train 5"
+ * buildingsCountToTrainFullBatch = 2, fullBatchSize = 5, remainderBatch = 0:
+ * "Shift-click to train 10 (2*5)"
+ * buildingsCountToTrainFullBatch = 1, fullBatchSize = 15, remainderBatch = 12:
+ * "Shift-click to train 27 (15 + 12)"
+ */
+function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
+{
+ var totalBatchTrainingCount = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
+ // Don't show the batch training tooltip if either units of this type can't be trained at all
+ // or only one unit can be trained
+ if (totalBatchTrainingCount < 2)
+ return "";
+ var batchTrainingString = "";
+ var fullBatchesString = "";
+ if (buildingsCountToTrainFullBatch > 0)
+ {
+ if (buildingsCountToTrainFullBatch > 1)
+ fullBatchesString += buildingsCountToTrainFullBatch + "*";
+ fullBatchesString += fullBatchSize;
+ }
+ var remainderBatchString = remainderBatch > 0 ? remainderBatch : "";
+ var batchDetailsString = "";
+ // We need to display the batch details part if there is either more than
+ // one building with full batch or one building with the full batch and
+ // another with a partial batch
+ if (buildingsCountToTrainFullBatch > 1 ||
+ (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0))
+ {
+ batchDetailsString += " (";
+ if (fullBatchesString != "" && remainderBatchString != "")
+ batchDetailsString += fullBatchesString + " + " + remainderBatchString;
+ else if (fullBatchesString != "")
+ batchDetailsString += fullBatchesString;
+ else
+ batchDetailsString += remainderBatchString;
+ batchDetailsString += ")";
+ }
+ return "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train "
+ + totalBatchTrainingCount + batchDetailsString + ".[/font]";
+}
+
/**
* Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected
* unit.
@@ -150,7 +209,7 @@ function setOverlay(object, value)
* @param items Panel-specific data to construct the icons with.
* @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument.
*/
-function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
+function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, callback)
{
usedPanels[guiName] = 1;
@@ -374,9 +433,6 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
if (template.tooltip)
tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
- var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType);
- var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement;
-
tooltip += "\n" + getEntityCostTooltip(template);
if (template.health)
@@ -388,8 +444,13 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
if (template.speed)
tooltip += "\n" + getEntitySpeed(template);
- tooltip += "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train " + trainNum + ".[/font]";
+ var [trainEntLimit, trainEntCount, canBeTrainedCount] =
+ getEntityLimitAndCount(playerState, entType)
+ tooltip += formatLimitString(trainEntLimit, trainEntCount);
+ var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
+ getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
+ tooltip += formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch);
break;
case RESEARCH:
@@ -627,17 +688,30 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
pair.hidden = true;
button1.hidden = true;
affordableMask1.hidden = true;
- }
+ }
}
else if (guiName == CONSTRUCTION || guiName == TRAINING)
{
+ if (guiName == TRAINING)
+ {
+ var trainingCategory = null;
+ if (template.trainingRestrictions)
+ trainingCategory = template.trainingRestrictions.category;
+ var grayscale = "";
+ if (trainingCategory && playerState.entityLimits[trainingCategory] &&
+ playerState.entityCounts[trainingCategory] >= playerState.entityLimits[trainingCategory])
+ grayscale = "grayscale:";
+ icon.sprite = "stretched:" + grayscale + "session/portraits/" + template.icon;
+ }
+
affordableMask.hidden = true;
var totalCosts = {};
var trainNum = 1;
if (Engine.HotkeyIsPressed("session.batchtrain") && guiName == TRAINING)
{
- var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType);
- trainNum = batchSize + batchIncrement;
+ var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
+ getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
+ trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
}
// Walls have no cost defined.
@@ -767,7 +841,7 @@ function setupUnitTradingPanel(usedPanels, unitEntState, selection)
}
// Sets up "unit barter panel" - special case for setupUnitPanel
-function setupUnitBarterPanel(unitEntState)
+function setupUnitBarterPanel(unitEntState, playerState)
{
// Amount of player's resource to exchange
var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
@@ -862,13 +936,18 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
var player = Engine.GetPlayerID();
if (entState.player == player || g_DevSettings.controlAll)
{
+ // Get player state to check some constraints
+ // e.g. presence of a hero or build limits
+ var simState = Engine.GuiInterfaceCall("GetSimulationState");
+ var playerState = simState.players[player];
+
if (selection.length > 1)
- setupUnitPanel(SELECTION, usedPanels, entState, g_Selection.groups.getTemplateNames(),
+ setupUnitPanel(SELECTION, usedPanels, entState, playerState, g_Selection.groups.getTemplateNames(),
function (entType) { changePrimarySelectionGroup(entType); } );
var commands = getEntityCommandsList(entState);
if (commands.length)
- setupUnitPanel(COMMAND, usedPanels, entState, commands,
+ setupUnitPanel(COMMAND, usedPanels, entState, playerState, commands,
function (item) { performCommand(entState.id, item.name); } );
if (entState.garrisonHolder)
@@ -881,14 +960,14 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
groups.add(state.garrisonHolder.entities)
}
- setupUnitPanel(GARRISON, usedPanels, entState, groups.getTemplateNames(),
+ setupUnitPanel(GARRISON, usedPanels, entState, playerState, groups.getTemplateNames(),
function (item) { unloadTemplate(item); } );
}
var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
{
- setupUnitPanel(FORMATION, usedPanels, entState, formations,
+ setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
function (item) { performFormation(entState.id, item); } );
}
@@ -897,7 +976,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
{
- setupUnitPanel(STANCE, usedPanels, entState, stances,
+ setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
function (item) { performStance(entState.id, item); } );
}
@@ -905,7 +984,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
if (entState.barterMarket)
{
usedPanels["Barter"] = 1;
- setupUnitBarterPanel(entState);
+ setupUnitBarterPanel(entState, playerState);
}
var buildableEnts = [];
@@ -929,10 +1008,10 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
// The first selected entity's type has priority.
if (entState.buildEntities)
- setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
+ setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
else if (entState.production && entState.production.entities)
- setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
- function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
+ setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
+ function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
else if (entState.trader)
setupUnitTradingPanel(usedPanels, entState, selection);
else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
@@ -994,7 +1073,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
else if (trainableEnts.length)
setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
- function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
+ function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
}
// Show technologies if the active panel has at most one row of icons.
@@ -1007,7 +1086,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
}
if (entState.production && entState.production.queue.length)
- setupUnitPanel(QUEUE, usedPanels, entState, entState.production.queue,
+ setupUnitPanel(QUEUE, usedPanels, entState, playerState, entState.production.queue,
function (item) { removeFromProductionQueue(entState.id, item.id); } );
supplementalDetailsPanel.hidden = false;
diff --git a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js
index 3731523300..b039d1293b 100644
--- a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js
+++ b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js
@@ -416,33 +416,35 @@ var GameState = Class({
* Returns player build limits
* an object where each key is a category corresponding to a build limit for the player.
*/
- getBuildLimits: function()
+ getEntityLimits: function()
{
- return this.playerData.buildLimits;
+ return this.playerData.entityLimits;
},
/**
* Returns player build counts
* an object where each key is a category corresponding to the current building count for the player.
*/
- getBuildCounts: function()
+ getEntityCounts: function()
{
- return this.playerData.buildCounts;
+ return this.playerData.entityCounts;
},
/**
* Checks if the player's build limit has been reached for the given category.
- * The category comes from the entity tenplate, specifically the BuildRestrictions component.
+ * The category comes from the entity template, specifically the
+ * BuildRestrictions/TrainingRestrictions components.
*/
- isBuildLimitReached: function(category)
+ isEntityLimitReached: function(category)
{
- if (this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
+ if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
return false;
// There's a special case of build limits per civ centre, so check that first
- if (this.playerData.buildLimits[category].LimitPerCivCentre !== undefined)
- return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
+ if (this.playerData.entityLimits[category].LimitPerCivCentre !== undefined)
+ return (this.playerData.entityCounts[category] >=
+ this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
else
- return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
+ return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
},
});
diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js b/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js
index 1563dc96ae..3cc18038f5 100644
--- a/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js
+++ b/binaries/data/mods/public/simulation/ai/qbot-wc/gamestate.js
@@ -315,22 +315,23 @@ GameState.prototype.getResourceSupplies = function(resource){
return this.updatingCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
};
-GameState.prototype.getBuildLimits = function() {
- return this.playerData.buildLimits;
+GameState.prototype.getEntityLimits = function() {
+ return this.playerData.entityLimits;
};
-GameState.prototype.getBuildCounts = function() {
- return this.playerData.buildCounts;
+GameState.prototype.getEntityCounts = function() {
+ return this.playerData.entityCounts;
};
-// Checks whether the maximum number of buildings have been cnstructed for a certain catergory
-GameState.prototype.isBuildLimitReached = function(category) {
- if(this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
+// Checks whether the maximum number of buildings have been constructed for a certain catergory
+GameState.prototype.isEntityLimitReached = function(category) {
+ if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
return false;
- if(this.playerData.buildLimits[category].LimitsPerCivCentre != undefined)
- return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
+ if(this.playerData.entityLimits[category].LimitsPerCivCentre != undefined)
+ return (this.playerData.entityCounts[category] >=
+ this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
else
- return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
+ return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
};
GameState.prototype.findTrainableUnits = function(classes){
diff --git a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js
index c9aa2f4e3f..e1d4699cd8 100755
--- a/binaries/data/mods/public/simulation/ai/qbot-wc/military.js
+++ b/binaries/data/mods/public/simulation/ai/qbot-wc/military.js
@@ -368,7 +368,7 @@ MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
// Adds towers to the defenceBuilding queue
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
- + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["DefenseTower"]) {
+ + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]) {
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
if (dropsiteEnt.resourceDropsiteTypes() && dropsiteEnt.getMetadata("defenseTower") !== true){
@@ -386,7 +386,7 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
- if (numFortresses + queues.defenceBuilding.totalLength() < 1){ //gameState.getBuildLimits()["Fortress"]) {
+ if (numFortresses + queues.defenceBuilding.totalLength() < 1){ //gameState.getEntityLimits()["Fortress"]) {
if (gameState.getTimeElapsed() > 840 * 1000 + numFortresses * 300 * 1000){
if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
var position = gameState.ai.pathsToMe.shift();
diff --git a/binaries/data/mods/public/simulation/ai/qbot/gamestate.js b/binaries/data/mods/public/simulation/ai/qbot/gamestate.js
index 2461dff622..3fbb9591fa 100644
--- a/binaries/data/mods/public/simulation/ai/qbot/gamestate.js
+++ b/binaries/data/mods/public/simulation/ai/qbot/gamestate.js
@@ -303,20 +303,21 @@ GameState.prototype.getResourceSupplies = function(resource){
return this.updatingCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
};
-GameState.prototype.getBuildLimits = function() {
- return this.playerData.buildLimits;
+GameState.prototype.getEntityLimits = function() {
+ return this.playerData.entityLimits;
};
-GameState.prototype.getBuildCounts = function() {
- return this.playerData.buildCounts;
+GameState.prototype.getEntityCounts = function() {
+ return this.playerData.entityCounts;
};
-// Checks whether the maximum number of buildings have been cnstructed for a certain catergory
-GameState.prototype.isBuildLimitReached = function(category) {
- if(this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
+// Checks whether the maximum number of buildings have been constructed for a certain catergory
+GameState.prototype.isEntityLimitReached = function(category) {
+ if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
return false;
- if(this.playerData.buildLimits[category].LimitsPerCivCentre != undefined)
- return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
+ if(this.playerData.entityLimits[category].LimitsPerCivCentre != undefined)
+ return (this.playerData.entityCounts[category] >=
+ this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
else
- return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
+ return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
};
diff --git a/binaries/data/mods/public/simulation/ai/qbot/military.js b/binaries/data/mods/public/simulation/ai/qbot/military.js
index c48c82d326..62cdb83148 100755
--- a/binaries/data/mods/public/simulation/ai/qbot/military.js
+++ b/binaries/data/mods/public/simulation/ai/qbot/military.js
@@ -324,7 +324,7 @@ MilitaryAttackManager.prototype.measureEnemyStrength = function(gameState){
// Adds towers to the defenceBuilding queue
MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
- + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["DefenseTower"]) {
+ + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]) {
gameState.getOwnEntities().forEach(function(dropsiteEnt) {
@@ -343,7 +343,7 @@ MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
}
- if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["Fortress"]) {
+ if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["Fortress"]) {
if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > gameState.ai.modules["economy"].targetNumWorkers * 0.5){
if (gameState.getTimeElapsed() > 350 * 1000 * numFortresses){
if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
diff --git a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js
index 11d1ed1850..521f6ae772 100644
--- a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js
+++ b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js
@@ -269,33 +269,35 @@ var GameState = Class({
* Returns player build limits
* an object where each key is a category corresponding to a build limit for the player.
*/
- getBuildLimits: function()
+ getEntityLimits: function()
{
- return this.playerData.buildLimits;
+ return this.playerData.entityLimits;
},
/**
- * Returns player build counts
- * an object where each key is a category corresponding to the current building count for the player.
+ * Returns player entity counts
+ * an object where each key is a category corresponding to the current entity count for the player.
*/
- getBuildCounts: function()
+ getEntityCounts: function()
{
- return this.playerData.buildCounts;
+ return this.playerData.entityCounts;
},
/**
- * Checks if the player's build limit has been reached for the given category.
- * The category comes from the entity tenplate, specifically the BuildRestrictions component.
+ * Checks if the player's entity limit has been reached for the given category.
+ * The category comes from the entity template, specifically the
+ * BuildRestrictions/TrainingRestrictions components.
*/
- isBuildLimitReached: function(category)
+ isEntityLimitReached: function(category)
{
- if (this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
+ if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
return false;
// There's a special case of build limits per civ centre, so check that first
- if (this.playerData.buildLimits[category].LimitPerCivCentre !== undefined)
- return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
+ if (this.playerData.entityLimits[category].LimitPerCivCentre !== undefined)
+ return (this.playerData.entityCounts[category] >=
+ this.playerData.entityCounts["CivilCentre"] * this.playerData.entityLimits[category].LimitPerCivCentre);
else
- return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
+ return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
},
});
diff --git a/binaries/data/mods/public/simulation/components/BuildLimits.js b/binaries/data/mods/public/simulation/components/EntityLimits.js
similarity index 50%
rename from binaries/data/mods/public/simulation/components/BuildLimits.js
rename to binaries/data/mods/public/simulation/components/EntityLimits.js
index 78c18055be..0b2927a7cf 100644
--- a/binaries/data/mods/public/simulation/components/BuildLimits.js
+++ b/binaries/data/mods/public/simulation/components/EntityLimits.js
@@ -1,12 +1,13 @@
-function BuildLimits() {}
+function EntityLimits() {}
-BuildLimits.prototype.Schema =
- "Specifies per category limits on number of buildings that can be constructed for each player." +
+EntityLimits.prototype.Schema =
+ "Specifies per category limits on number of entities (buildings or units) that can be created for each player." +
"" +
"" +
"" +
"25" +
"10" +
+ "1" +
"" +
"1" +
"" +
@@ -17,7 +18,7 @@ BuildLimits.prototype.Schema =
"" +
"" +
"" +
- "" +
+ "" +
"" +
"" +
"" +
@@ -33,7 +34,10 @@ BuildLimits.prototype.Schema =
* TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
*/
-BuildLimits.prototype.Init = function()
+const TRAINING = "training";
+const BUILD = "build";
+
+EntityLimits.prototype.Init = function()
{
this.limit = {};
this.count = {};
@@ -44,60 +48,70 @@ BuildLimits.prototype.Init = function()
}
};
-BuildLimits.prototype.IncrementCount = function(category)
+EntityLimits.prototype.IncreaseCount = function(category, value)
{
if (this.count[category] !== undefined)
- {
- this.count[category]++;
- }
+ this.count[category] += value;
};
-BuildLimits.prototype.DecrementCount = function(category)
+EntityLimits.prototype.DecreaseCount = function(category, value)
{
if (this.count[category] !== undefined)
- {
- this.count[category]--;
- }
+ this.count[category] -= value;
};
-BuildLimits.prototype.GetLimits = function()
+EntityLimits.prototype.IncrementCount = function(category)
+{
+ this.IncreaseCount(category, 1);
+};
+
+EntityLimits.prototype.DecrementCount = function(category)
+{
+ this.DecreaseCount(category, 1);
+};
+
+EntityLimits.prototype.GetLimits = function()
{
return this.limit;
};
-BuildLimits.prototype.GetCounts = function()
+EntityLimits.prototype.GetCounts = function()
{
return this.count;
};
-BuildLimits.prototype.AllowedToBuild = function(category)
+EntityLimits.prototype.AllowedToCreate = function(limitType, category, count)
{
// TODO: The UI should reflect this before the user tries to place the building,
// since the limits are independent of placement location
// Allow unspecified categories and those with no limit
if (this.count[category] === undefined || this.limit[category] === undefined)
- {
return true;
- }
// Rather than complicating the schema unecessarily, just handle special cases here
if (this.limit[category].LimitPerCivCentre !== undefined)
{
if (this.count[category] >= this.count["CivilCentre"] * this.limit[category].LimitPerCivCentre)
{
- var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
- var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category].LimitPerCivCentre+" per civil centre reached"};
+ var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
+ var notification = {
+ "player": cmpPlayer.GetPlayerID(),
+ "message": category + " " + limitType + " limit of " +
+ this.limit[category].LimitPerCivCentre + " per civil centre reached"
+ };
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
return false;
}
}
- else if (this.count[category] >= this.limit[category])
+ else if (this.count[category] + count > this.limit[category])
{
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
- var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category]+ " reached"};
+ var notification = {
+ "player": cmpPlayer.GetPlayerID(),
+ "message": category + " " + limitType + " limit of " + this.limit[category] + " reached"};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
@@ -105,24 +119,39 @@ BuildLimits.prototype.AllowedToBuild = function(category)
}
return true;
+}
+
+EntityLimits.prototype.AllowedToBuild = function(category)
+{
+ // TODO: The UI should reflect this before the user tries to place the building,
+ // since the limits are independent of placement location
+
+ return this.AllowedToCreate(BUILD, category, 1);
};
-BuildLimits.prototype.OnGlobalOwnershipChanged = function(msg)
+EntityLimits.prototype.AllowedToTrain = function(category, count)
{
- // This automatically updates build counts
+ return this.AllowedToCreate(TRAINING, category, count);
+};
+
+EntityLimits.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ // This automatically updates entity counts
+ var category = null;
var cmpBuildRestrictions = Engine.QueryInterface(msg.entity, IID_BuildRestrictions);
if (cmpBuildRestrictions)
+ category = cmpBuildRestrictions.GetCategory();
+ var cmpTrainingRestrictions = Engine.QueryInterface(msg.entity, IID_TrainingRestrictions);
+ if (cmpTrainingRestrictions)
+ category = cmpTrainingRestrictions.GetCategory();
+ if (category)
{
var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
if (msg.from == playerID)
- {
- this.DecrementCount(cmpBuildRestrictions.GetCategory());
- }
+ this.DecrementCount(category);
if (msg.to == playerID)
- {
- this.IncrementCount(cmpBuildRestrictions.GetCategory());
- }
+ this.IncrementCount(category);
}
};
-Engine.RegisterComponentType(IID_BuildLimits, "BuildLimits", BuildLimits);
+Engine.RegisterComponentType(IID_EntityLimits, "EntityLimits", EntityLimits);
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 935966df27..d436fa808f 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -49,7 +49,7 @@ GuiInterface.prototype.GetSimulationState = function(player)
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
- var cmpPlayerBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits);
+ var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Work out what phase we are in
@@ -88,8 +88,8 @@ GuiInterface.prototype.GetSimulationState = function(player)
"isAlly": allies,
"isNeutral": neutrals,
"isEnemy": enemies,
- "buildLimits": cmpPlayerBuildLimits.GetLimits(),
- "buildCounts": cmpPlayerBuildLimits.GetCounts(),
+ "entityLimits": cmpPlayerEntityLimits.GetLimits(),
+ "entityCounts": cmpPlayerEntityLimits.GetCounts(),
"techModifications": cmpTechnologyManager.GetTechModifications()
};
ret.players.push(playerData);
@@ -384,7 +384,14 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
}
}
-
+
+ if (template.TrainingRestrictions)
+ {
+ ret.trainingRestrictions = {
+ "category": template.TrainingRestrictions.Category,
+ };
+ }
+
if (template.Cost)
{
ret.cost = {};
@@ -453,6 +460,7 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
+ ret.identityClassesString = GetTemplateIdentityClassesString(template);
}
if (template.UnitMotion)
diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js
index 8a0cbfbeab..67c6606278 100644
--- a/binaries/data/mods/public/simulation/components/Player.js
+++ b/binaries/data/mods/public/simulation/components/Player.js
@@ -440,23 +440,19 @@ Player.prototype.IsNeutral = function(id)
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
var isConquestCritical = false;
-
// Load class list only if we're going to need it
if (msg.from == this.playerID || msg.to == this.playerID)
{
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (cmpIdentity)
{
- var classes = cmpIdentity.GetClassesList();
- isConquestCritical = classes.indexOf("ConquestCritical") != -1;
+ isConquestCritical = cmpIdentity.HasClass("ConquestCritical");
}
}
-
if (msg.from == this.playerID)
{
if (isConquestCritical)
this.conquestCriticalEntitiesCount--;
-
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
@@ -464,12 +460,10 @@ Player.prototype.OnGlobalOwnershipChanged = function(msg)
this.popBonuses -= cost.GetPopBonus();
}
}
-
if (msg.to == this.playerID)
{
if (isConquestCritical)
this.conquestCriticalEntitiesCount++;
-
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js
index c7aac662ff..e1bcbcf0d9 100644
--- a/binaries/data/mods/public/simulation/components/ProductionQueue.js
+++ b/binaries/data/mods/public/simulation/components/ProductionQueue.js
@@ -212,6 +212,14 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
if (!cmpPlayer.TrySubtractResources(totalCosts))
return;
+ // Update entity count in the EntityLimits component
+ if (template.TrainingRestrictions)
+ {
+ var unitCategory = template.TrainingRestrictions.Category;
+ var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
+ cmpPlayerEntityLimits.IncreaseCount(unitCategory, count);
+ }
+
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
@@ -307,6 +315,19 @@ ProductionQueue.prototype.RemoveBatch = function(id)
var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player);
+ // Update entity count in the EntityLimits component
+ if (item.unitTemplate)
+ {
+ var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTempMan.GetTemplate(item.unitTemplate);
+ if (template.TrainingRestrictions)
+ {
+ var unitCategory = template.TrainingRestrictions.Category;
+ var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
+ cmpPlayerEntityLimits.DecreaseCount(unitCategory, item.count);
+ }
+ }
+
// Refund the resource cost for this batch
var totalCosts = {};
var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
@@ -422,7 +443,19 @@ ProductionQueue.prototype.SpawnUnits = function(templateName, count, metadata)
// so only create them once and use as needed
for (var i = 0; i < count; ++i)
{
- this.entityCache.push(Engine.AddEntity(templateName));
+ var ent = Engine.AddEntity(templateName);
+ this.entityCache.push(ent);
+
+ // Decrement entity count in the EntityLimits component
+ // since it will be increased by EntityLimits.OnGlobalOwnershipChanged function,
+ // i.e. we replace a 'trained' entity to an 'alive' one
+ var cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
+ if (cmpTrainingRestrictions)
+ {
+ var unitCategory = cmpTrainingRestrictions.GetCategory();
+ var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
+ cmpPlayerEntityLimits.DecrementCount(unitCategory);
+ }
}
}
diff --git a/binaries/data/mods/public/simulation/components/TrainingRestrictions.js b/binaries/data/mods/public/simulation/components/TrainingRestrictions.js
new file mode 100644
index 0000000000..efe1eeb41c
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/TrainingRestrictions.js
@@ -0,0 +1,22 @@
+function TrainingRestrictions() {}
+
+TrainingRestrictions.prototype.Schema =
+ "Specifies unit training restrictions, currently only unit category." +
+ "" +
+ "" +
+ "Hero" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "Hero" +
+ "FemaleCitizen" +
+ "" +
+ "";
+
+TrainingRestrictions.prototype.GetCategory = function()
+{
+ return this.template.Category;
+};
+
+Engine.RegisterComponentType(IID_TrainingRestrictions, "TrainingRestrictions", TrainingRestrictions);
diff --git a/binaries/data/mods/public/simulation/components/interfaces/BuildLimits.js b/binaries/data/mods/public/simulation/components/interfaces/BuildLimits.js
deleted file mode 100644
index ca1fa218fa..0000000000
--- a/binaries/data/mods/public/simulation/components/interfaces/BuildLimits.js
+++ /dev/null
@@ -1 +0,0 @@
-Engine.RegisterInterface("BuildLimits");
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/components/interfaces/EntityLimits.js b/binaries/data/mods/public/simulation/components/interfaces/EntityLimits.js
new file mode 100644
index 0000000000..bcd0770e3f
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/interfaces/EntityLimits.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("EntityLimits");
diff --git a/binaries/data/mods/public/simulation/components/interfaces/TrainingRestrictions.js b/binaries/data/mods/public/simulation/components/interfaces/TrainingRestrictions.js
new file mode 100644
index 0000000000..79d7a7185e
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/interfaces/TrainingRestrictions.js
@@ -0,0 +1,2 @@
+Engine.RegisterInterface("TrainingRestrictions");
+
diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
index 1dbec77e3c..144400e264 100644
--- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -1,8 +1,8 @@
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Barter.js");
Engine.LoadComponentScript("interfaces/Builder.js");
-Engine.LoadComponentScript("interfaces/BuildLimits.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/EntityLimits.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Gate.js");
@@ -69,7 +69,7 @@ AddMock(100, IID_Player, {
IsEnemy: function() { return true; },
});
-AddMock(100, IID_BuildLimits, {
+AddMock(100, IID_EntityLimits, {
GetLimits: function() { return {"Foo": 10}; },
GetCounts: function() { return {"Foo": 5}; },
});
@@ -122,7 +122,7 @@ AddMock(101, IID_Player, {
IsEnemy: function() { return false; },
});
-AddMock(101, IID_BuildLimits, {
+AddMock(101, IID_EntityLimits, {
GetLimits: function() { return {"Bar": 20}; },
GetCounts: function() { return {"Bar": 0}; },
});
@@ -177,8 +177,8 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
isAlly: [false, false],
isNeutral: [false, false],
isEnemy: [true, true],
- buildLimits: {"Foo": 10},
- buildCounts: {"Foo": 5},
+ entityLimits: {"Foo": 10},
+ entityCounts: {"Foo": 5},
techModifications: {},
},
{
@@ -197,8 +197,8 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
isAlly: [true, true],
isNeutral: [false, false],
isEnemy: [false, false],
- buildLimits: {"Bar": 20},
- buildCounts: {"Bar": 0},
+ entityLimits: {"Bar": 20},
+ entityCounts: {"Bar": 0},
techModifications: {},
}
],
@@ -224,8 +224,8 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
isAlly: [false, false],
isNeutral: [false, false],
isEnemy: [true, true],
- buildLimits: {"Foo": 10},
- buildCounts: {"Foo": 5},
+ entityLimits: {"Foo": 10},
+ entityCounts: {"Foo": 5},
techModifications: {},
statistics: {
unitsTrained: 10,
@@ -260,8 +260,8 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
isAlly: [true, true],
isNeutral: [false, false],
isEnemy: [false, false],
- buildLimits: {"Bar": 20},
- buildCounts: {"Bar": 0},
+ entityLimits: {"Bar": 20},
+ entityCounts: {"Bar": 0},
techModifications: {},
statistics: {
unitsTrained: 10,
diff --git a/binaries/data/mods/public/simulation/data/technologies/no_heroes.json b/binaries/data/mods/public/simulation/data/technologies/no_heroes.json
deleted file mode 100644
index c54494fac9..0000000000
--- a/binaries/data/mods/public/simulation/data/technologies/no_heroes.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "genericName": "Future Alpha",
- "description": "Temporarily Disable Heroes",
- "cost": {"food": 0, "wood": 100, "stone": 0, "metal": 0},
- "requirements": {"tech": "phase_city"},
- "requirementsTooltip": "Unlocked in City Phase.",
- "icon": "arrow.png",
- "researchTime": 20,
- "tooltip": "Need build limits before renabling this."
-}
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index b1435921d5..69e26d9eb6 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -169,11 +169,30 @@ function ProcessCommand(player, cmd)
case "train":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
+
+ // Check entity limits
+ var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTempMan.GetTemplate(cmd.template);
+ var unitCategory = null;
+ if (template.TrainingRestrictions)
+ unitCategory = template.TrainingRestrictions.Category;
+
// Verify that the building(s) can be controlled by the player
if (entities.length > 0)
{
for each (var ent in entities)
{
+ if (unitCategory)
+ {
+ var cmpPlayerEntityLimits = QueryOwnerInterface(ent, IID_EntityLimits);
+ if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count))
+ {
+ if (g_DebugCommands)
+ warn(unitCategory + " train limit is reached: " + uneval(cmd));
+ continue;
+ }
+ }
+
var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
// TODO: Enable this check once the AI gets technology support
if (cmpTechnologyManager.CanProduce(cmd.template) || true)
@@ -568,9 +587,9 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
return false;
}
- // Check build limits
- var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
- if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
+ // Check entity limits
+ var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
+ if (!cmpEntityLimits || !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
{
if (g_DebugCommands)
{
diff --git a/binaries/data/mods/public/simulation/helpers/Templates.js b/binaries/data/mods/public/simulation/helpers/Templates.js
new file mode 100644
index 0000000000..ae1ae259e9
--- /dev/null
+++ b/binaries/data/mods/public/simulation/helpers/Templates.js
@@ -0,0 +1,24 @@
+/**
+ * Return template.Identity.Classes._string if exists
+ */
+function GetTemplateIdentityClassesString(template)
+{
+ var identityClassesString = undefined;
+ if (template.Identity && template.Identity.Classes && "_string" in template.Identity.Classes)
+ identityClassesString = template.Identity.Classes._string;
+ return identityClassesString;
+}
+
+/**
+ * Check whether template.Identity.Classes contains specified class
+ */
+function TemplateHasIdentityClass(template, className)
+{
+ var identityClassesString = GetTemplateIdentityClassesString(template);
+ var hasClass = identityClassesString && identityClassesString.indexOf(className) != -1;
+ return hasClass;
+}
+
+Engine.RegisterGlobal("GetTemplateIdentityClassesString", GetTemplateIdentityClassesString);
+Engine.RegisterGlobal("TemplateHasIdentityClass", TemplateHasIdentityClass);
+
diff --git a/binaries/data/mods/public/simulation/templates/special/player.xml b/binaries/data/mods/public/simulation/templates/special/player.xml
index 725f1c884a..7ed0741fc7 100644
--- a/binaries/data/mods/public/simulation/templates/special/player.xml
+++ b/binaries/data/mods/public/simulation/templates/special/player.xml
@@ -1,13 +1,14 @@
-
+
1.0
25
10
+ 1
-
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero.xml
index 384b86a99c..5e312d2201 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero.xml
@@ -36,7 +36,6 @@
Hero
Hero Organic
- no_heroes
400
@@ -63,6 +62,9 @@
actor/human/death/death.xml
+
+ Hero
+
9.0
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry.xml
index 933f0a473b..c0d5bdf571 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry.xml
@@ -40,7 +40,6 @@
Hero Cavalry
Hero Cavalry
- no_heroes
500
@@ -70,6 +69,9 @@
2500
+
+ Hero
+
11.0
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
index 4ca3b4a6b0..eb194c7547 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
@@ -49,7 +49,6 @@
Hero Cavalry Archer
Hero Aura: n/a.
Ranged attack 2x vs. spearmen. Ranged attack 1.5x vs. Swordsmen.
- no_heroes
450
@@ -70,6 +69,9 @@ Ranged attack 2x vs. spearmen. Ranged attack 1.5x vs. Swordsmen.
+
+ Hero
+
11.0
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
index 6eaca8ccac..a3d3b4c940 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
@@ -39,7 +39,6 @@
Hero
Hero Cavalry Skirmisher
- no_heroes
450
@@ -60,6 +59,9 @@
+
+ Hero
+
11.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry.xml
index 37748c2859..c1d592fd20 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry.xml
@@ -38,7 +38,6 @@
Hero Infantry
Hero
- no_heroes
400
@@ -68,6 +67,9 @@
1500
+
+ Hero
+
8.5
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
index fa5f00fd53..6fc76b5ff6 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
@@ -41,7 +41,6 @@
Hero
Hero Archer.
Counters Swordsmen and Cavalry Spearmen. Countered by Skirmishers and other Cavalry types.
- no_heroes
350
@@ -62,4 +61,7 @@ Counters Swordsmen and Cavalry Spearmen. Countered by Skirmishers and other Cava
+
+ Hero
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
index 884104c4b0..5b9b98d582 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
@@ -45,7 +45,6 @@
Hero
Hero Skirmisher
- no_heroes
350
@@ -66,4 +65,7 @@
+
+ Hero
+
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
index 5ddd8a30d6..20e93fb1fb 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml
@@ -51,6 +51,9 @@
+
+ Hero
+
8.5