mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 13:53:57 -07:00
Training limits. Limit heroes to one living per player. Allow heroes to be trained again. Closes #1432
This was SVN commit r12832.
This commit is contained in:
parent
669b7e6e2c
commit
7e21db08d5
29 changed files with 474 additions and 154 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
function BuildLimits() {}
|
||||
function EntityLimits() {}
|
||||
|
||||
BuildLimits.prototype.Schema =
|
||||
"<a:help>Specifies per category limits on number of buildings that can be constructed for each player.</a:help>" +
|
||||
EntityLimits.prototype.Schema =
|
||||
"<a:help>Specifies per category limits on number of entities (buildings or units) that can be created for each player.</a:help>" +
|
||||
"<a:example>" +
|
||||
"<Limits>" +
|
||||
"<CivilCentre/>" +
|
||||
"<DefenseTower>25</DefenseTower>" +
|
||||
"<Fortress>10</Fortress>" +
|
||||
"<Hero>1</Hero>" +
|
||||
"<Special>" +
|
||||
"<LimitPerCivCentre>1</LimitPerCivCentre>" +
|
||||
"</Special>" +
|
||||
|
|
@ -17,7 +18,7 @@ BuildLimits.prototype.Schema =
|
|||
"</element>" +
|
||||
"<element name='Limits'>" +
|
||||
"<zeroOrMore>" +
|
||||
"<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
|
||||
"<element a:help='Specifies a category of building/unit on which to apply this limit. See BuildRestrictions/TrainingRestrictions for list of categories.'>" +
|
||||
"<anyName />" +
|
||||
"<choice>" +
|
||||
"<text />" +
|
||||
|
|
@ -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);
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
function TrainingRestrictions() {}
|
||||
|
||||
TrainingRestrictions.prototype.Schema =
|
||||
"<a:help>Specifies unit training restrictions, currently only unit category.</a:help>" +
|
||||
"<a:example>" +
|
||||
"<TrainingRestrictions>" +
|
||||
"<Category>Hero</Category>" +
|
||||
"</TrainingRestrictions>" +
|
||||
"</a:example>" +
|
||||
"<element name='Category' a:help='Specifies the category of this unit, for satisfying special constraints.'>" +
|
||||
"<choice>" +
|
||||
"<value>Hero</value>" +
|
||||
"<value>FemaleCitizen</value>" +
|
||||
"</choice>" +
|
||||
"</element>";
|
||||
|
||||
TrainingRestrictions.prototype.GetCategory = function()
|
||||
{
|
||||
return this.template.Category;
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_TrainingRestrictions, "TrainingRestrictions", TrainingRestrictions);
|
||||
|
|
@ -1 +0,0 @@
|
|||
Engine.RegisterInterface("BuildLimits");
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("EntityLimits");
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Engine.RegisterInterface("TrainingRestrictions");
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
24
binaries/data/mods/public/simulation/helpers/Templates.js
Normal file
24
binaries/data/mods/public/simulation/helpers/Templates.js
Normal file
|
|
@ -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);
|
||||
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity>
|
||||
<BuildLimits>
|
||||
<EntityLimits>
|
||||
<LimitMultiplier>1.0</LimitMultiplier>
|
||||
<Limits>
|
||||
<CivilCentre/>
|
||||
<DefenseTower>25</DefenseTower>
|
||||
<Fortress>10</Fortress>
|
||||
<Hero>1</Hero>
|
||||
</Limits>
|
||||
</BuildLimits>
|
||||
</EntityLimits>
|
||||
<Player/>
|
||||
<StatisticsTracker/>
|
||||
<TechnologyManager/>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
<Identity>
|
||||
<GenericName>Hero</GenericName>
|
||||
<Classes datatype="tokens">Hero Organic</Classes>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>400</xp>
|
||||
|
|
@ -63,6 +62,9 @@
|
|||
<death>actor/human/death/death.xml</death>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>9.0</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
<Identity>
|
||||
<GenericName>Hero Cavalry</GenericName>
|
||||
<Classes datatype="tokens">Hero Cavalry</Classes>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>500</xp>
|
||||
|
|
@ -70,6 +69,9 @@
|
|||
<Stamina>
|
||||
<Max>2500</Max>
|
||||
</Stamina>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>11.0</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
<GenericName>Hero Cavalry Archer</GenericName>
|
||||
<Tooltip>Hero Aura: n/a.
|
||||
Ranged attack 2x vs. spearmen. Ranged attack 1.5x vs. Swordsmen.</Tooltip>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>450</xp>
|
||||
|
|
@ -70,6 +69,9 @@ Ranged attack 2x vs. spearmen. Ranged attack 1.5x vs. Swordsmen.</Tooltip>
|
|||
</Texture>
|
||||
</Overlay>
|
||||
</Selectable>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>11.0</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@
|
|||
<Identity>
|
||||
<Classes datatype="tokens">Hero</Classes>
|
||||
<GenericName>Hero Cavalry Skirmisher</GenericName>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>450</xp>
|
||||
|
|
@ -60,6 +59,9 @@
|
|||
</Texture>
|
||||
</Overlay>
|
||||
</Selectable>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>11.5</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
<Identity>
|
||||
<Classes datatype="tokens">Hero Infantry</Classes>
|
||||
<GenericName>Hero</GenericName>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>400</xp>
|
||||
|
|
@ -68,6 +67,9 @@
|
|||
<Stamina>
|
||||
<Max>1500</Max>
|
||||
</Stamina>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>8.5</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
<Classes datatype="tokens">Hero</Classes>
|
||||
<Tooltip>Hero Archer.
|
||||
Counters Swordsmen and Cavalry Spearmen. Countered by Skirmishers and other Cavalry types.</Tooltip>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>350</xp>
|
||||
|
|
@ -62,4 +61,7 @@ Counters Swordsmen and Cavalry Spearmen. Countered by Skirmishers and other Cava
|
|||
</Texture>
|
||||
</Overlay>
|
||||
</Selectable>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
</Entity>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@
|
|||
<Identity>
|
||||
<Classes datatype="tokens">Hero</Classes>
|
||||
<GenericName>Hero Skirmisher</GenericName>
|
||||
<RequiredTechnology>no_heroes</RequiredTechnology>
|
||||
</Identity>
|
||||
<Loot>
|
||||
<xp>350</xp>
|
||||
|
|
@ -66,4 +65,7 @@
|
|||
</Texture>
|
||||
</Overlay>
|
||||
</Selectable>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
</Entity>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@
|
|||
</Texture>
|
||||
</Overlay>
|
||||
</Selectable>
|
||||
<TrainingRestrictions>
|
||||
<Category>Hero</Category>
|
||||
</TrainingRestrictions>
|
||||
<UnitMotion>
|
||||
<WalkSpeed>8.5</WalkSpeed>
|
||||
<Run>
|
||||
|
|
|
|||
Loading…
Reference in a new issue