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