Ticket #1432: one_hero_2012_10_06.diff

File one_hero_2012_10_06.diff, 59.0 KB (added by fcxSanya, 12 years ago)
  • binaries/data/mods/public/gui/session/input.js

     
    14701470
    14711471function flushTrainingBatch()
    14721472{
    1473     Engine.PostNetworkCommand({"type": "train", "entities": batchTrainingEntities, "template": batchTrainingType, "count": batchTrainingCount});
     1473    var appropriateBuildings = getBuidlingsWhichCanTrainEntity(batchTrainingEntities, batchTrainingType);
     1474    // If training limits don't allow us to train batchTrainingCount in each appropriate building
     1475    if (batchTrainingEntityAllowedCount !== undefined && batchTrainingEntityAllowedCount < batchTrainingCount * appropriateBuildings.length)
     1476    {
     1477        // Train as many full batches as we can
     1478        var buildingsCountToTrainFullBatch = Math.floor(batchTrainingEntityAllowedCount / batchTrainingCount);
     1479        var buildingsToTrainFullBatch = appropriateBuildings.slice(0, buildingsCountToTrainFullBatch);
     1480        Engine.PostNetworkCommand({"type": "train", "entities": buildingsToTrainFullBatch, "template": batchTrainingType, "count": batchTrainingCount});
     1481
     1482        // Train remainer in one more building
     1483        var remainderToTrain = batchTrainingEntityAllowedCount % batchTrainingCount;
     1484        Engine.PostNetworkCommand({"type": "train", "entities": [ appropriateBuildings[buildingsCountToTrainFullBatch] ], "template": batchTrainingType, "count": remainderToTrain});
     1485    }
     1486    else
     1487    {
     1488        Engine.PostNetworkCommand({"type": "train", "entities": appropriateBuildings, "template": batchTrainingType, "count": batchTrainingCount});
     1489    }
    14741490}
    14751491
     1492function getBuidlingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
     1493{
     1494    return entitiesToCheck.filter(function(entity) {
     1495        var state = GetEntityState(entity);
     1496        var canTrain = state && state.production && state.production.entities.length && state.production.entities.indexOf(trainEntType) != -1;
     1497        return canTrain;
     1498    });
     1499}
     1500
     1501function getEntityLimitAndCount(playerState, entType)
     1502{
     1503    var template = GetTemplateData(entType);
     1504    var trainingCategory = null;
     1505    if (template.trainingRestrictions)
     1506        trainingCategory = template.trainingRestrictions.category;
     1507    var trainEntLimit = undefined;
     1508    var trainEntCount = undefined;
     1509    var canBeTrainedCount = undefined;
     1510    if (trainingCategory && playerState.entityLimits[trainingCategory])
     1511    {
     1512        trainEntLimit = playerState.entityLimits[trainingCategory];
     1513        trainEntCount = playerState.entityCounts[trainingCategory];
     1514        canBeTrainedCount = trainEntLimit - trainEntCount;
     1515    }
     1516    return [trainEntLimit, trainEntCount, canBeTrainedCount];
     1517}
     1518
    14761519// Called by GUI when user clicks training button
    1477 function addTrainingToQueue(selection, trainEntType)
     1520function addTrainingToQueue(selection, trainEntType, playerState)
    14781521{
    1479     if (Engine.HotkeyIsPressed("session.batchtrain"))
     1522    // Create list of buildings which can train trainEntType
     1523    var appropriateBuildings = getBuidlingsWhichCanTrainEntity(selection, trainEntType);
     1524
     1525    // Check trainEntType entity limit and count
     1526    var [trainEntLimit, trainEntCount, canBeTrainedCount] = getEntityLimitAndCount(playerState, trainEntType)
     1527
     1528    // Batch training possible if we can train at least 2 units
     1529    var batchTrainingPossible = canBeTrainedCount !== undefined && canBeTrainedCount > 1;
     1530
     1531    if (Engine.HotkeyIsPressed("session.batchtrain") && batchTrainingPossible)
    14801532    {
    14811533        if (inputState == INPUT_BATCHTRAINING)
    14821534        {
     
    14931545                }
    14941546            }
    14951547            // If we're already creating a batch of this unit (in the same building(s)), then just extend it
     1548            // (if training limits allow)
    14961549            if (sameEnts && batchTrainingType == trainEntType)
    14971550            {
    1498                 batchTrainingCount += batchIncrementSize;
     1551                if (canBeTrainedCount !== undefined && canBeTrainedCount > batchTrainingCount * appropriateBuildings.length)
     1552                    batchTrainingCount += batchIncrementSize;
     1553                batchTrainingEntityAllowedCount = canBeTrainedCount;
    14991554                return;
    15001555            }
    15011556            // Otherwise start a new one
     
    15081563        inputState = INPUT_BATCHTRAINING;
    15091564        batchTrainingEntities = selection;
    15101565        batchTrainingType = trainEntType;
     1566        batchTrainingEntityAllowedCount = canBeTrainedCount;
    15111567        batchTrainingCount = batchIncrementSize;
    15121568    }
    15131569    else
    15141570    {
    1515         // Non-batched - just create a single entity
    1516         Engine.PostNetworkCommand({"type": "train", "template": trainEntType, "count": 1, "entities": selection});
     1571        // Non-batched - just create a single entity in each building
     1572        // (but no more than entity limit allows)
     1573        var buildingsForTraining = appropriateBuildings;
     1574        if (trainEntLimit)
     1575            buildingsForTraining = buildingsForTraining.slice(0, canBeTrainedCount);
     1576        Engine.PostNetworkCommand({"type": "train", "template": trainEntType, "count": 1, "entities": buildingsForTraining});
    15171577    }
    15181578}
    15191579
     
    15251585
    15261586// Returns the number of units that will be present in a batch if the user clicks
    15271587// the training button with shift down
    1528 function getTrainingBatchStatus(entity, trainEntType)
     1588function getTrainingBatchStatus(playerState, entity, trainEntType, selection)
    15291589{
     1590    var apporpriateBuildings = [entity];
     1591    if (selection && selection.indexOf(entity) != -1)
     1592        appropriateBuildings = getBuidlingsWhichCanTrainEntity(selection, trainEntType);
     1593    var nextBatchTrainingCount = 0;
    15301594    if (inputState == INPUT_BATCHTRAINING && batchTrainingEntities.indexOf(entity) != -1 && batchTrainingType == trainEntType)
    1531         return [batchTrainingCount, batchIncrementSize];
     1595    {
     1596        nextBatchTrainingCount = batchTrainingCount;
     1597        var canBeTrainedCount = batchTrainingEntityAllowedCount;
     1598    }
    15321599    else
    1533         return [0, batchIncrementSize];
     1600    {
     1601        var [trainEntLimit, trainEntCount, canBeTrainedCount] = getEntityLimitAndCount(playerState, trainEntType);
     1602        var batchSize = Math.min(canBeTrainedCount, batchIncrementSize);
     1603    }
     1604    // We need to calculate count after the next increment if it's possible
     1605    if (canBeTrainedCount == undefined || canBeTrainedCount > nextBatchTrainingCount * appropriateBuildings.length)
     1606        nextBatchTrainingCount += batchIncrementSize;
     1607    // If training limits don't allow us to train batchTrainingCount in each appropriate building
     1608    // train as many full batches as we can and remainer in one more building.
     1609    var buildingsCountToTrainFullBatch = appropriateBuildings.length;
     1610    var remainderToTrain = 0;
     1611    if (canBeTrainedCount !== undefined && canBeTrainedCount < nextBatchTrainingCount * appropriateBuildings.length)
     1612    {
     1613        buildingsCountToTrainFullBatch = Math.floor(canBeTrainedCount / nextBatchTrainingCount);
     1614        remainderToTrain = canBeTrainedCount % nextBatchTrainingCount;
     1615    }
     1616    return [buildingsCountToTrainFullBatch, nextBatchTrainingCount, remainderToTrain];
    15341617}
    15351618
    15361619// Called by GUI when user clicks production queue item
  • binaries/data/mods/public/gui/session/unit_commands.js

     
    139139}
    140140
    141141/**
     142 * Format batch training string for tooltip
     143 * Examples:
     144 * buildingsCountToTrainFullBatch = 1, fullBatchSize = 5, remainderBatch = 0:
     145 * "Shift-click to train 5"
     146 * buildingsCountToTrainFullBatch = 2, fullBatchSize = 5, remainderBatch = 0:
     147 * "Shift-click to train 10 (2*5)"
     148 * buildingsCountToTrainFullBatch = 1, fullBatchSize = 15, remainderBatch = 12:
     149 * "Shift-click to train 27 (15 + 12)"
     150 */
     151function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
     152{
     153    var totalBatchTrainingCount = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
     154    // Don't show the batch training tooltip if either units of this type can't be trained at all
     155    // or only one unit can be trained
     156    if (totalBatchTrainingCount < 2)
     157        return "";
     158    var batchTrainingString = "";
     159    var fullBatchesString = "";
     160    if (buildingsCountToTrainFullBatch > 0)
     161    {
     162        if (buildingsCountToTrainFullBatch > 1)
     163            fullBatchesString += buildingsCountToTrainFullBatch + "*";
     164        fullBatchesString += fullBatchSize;
     165    }
     166    var remainderBatchString = remainderBatch > 0 ? remainderBatch : "";
     167    var batchDetailsString = buildingsCountToTrainFullBatch > 1 || (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0) ?
     168        " (" + [fullBatchesString, remainderBatchString].filter(function(string) { return string != "" }).join(" + ") + ")" :
     169        "";
     170    return "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train "
     171        + totalBatchTrainingCount + batchDetailsString + ".[/font]";
     172}
     173
     174/**
    142175 * Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected
    143176 * unit.
    144177 *
     
    150183 * @param items Panel-specific data to construct the icons with.
    151184 * @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument.
    152185 */
    153 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
     186function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, callback)
    154187{
    155188    usedPanels[guiName] = 1;
    156189
     
    347380            case GATE:
    348381                var tooltip = item.tooltip;
    349382                if (item.template)
    350                 {
     383                {
    351384                    var template = GetTemplateData(item.template);
    352                     tooltip += "\n" + getEntityCostTooltip(template);
    353 
    354                     var affordableMask = getGUIObjectByName("unitGateUnaffordable["+i+"]");
     385                    tooltip += "\n" + getEntityCostTooltip(template);
     386
     387                    var affordableMask = getGUIObjectByName("unitGateUnaffordable["+i+"]");
    355388                    affordableMask.hidden = true;
    356 
    357                     var neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
    358                     if (neededResources)
    359                     {
    360                         affordableMask.hidden = false;
    361                         tooltip += getNeededResourcesTooltip(neededResources);
    362                     }
    363                 }
     389
     390                    var neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
     391                    if (neededResources)
     392                    {
     393                        affordableMask.hidden = false;
     394                        tooltip += getNeededResourcesTooltip(neededResources);
     395                    }
     396                }
    364397                break;
    365398
    366399            case STANCE:
     
    374407                if (template.tooltip)
    375408                    tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
    376409
    377                 var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType);
    378                 var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement;
     410                var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] = getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
    379411
    380412                tooltip += "\n" + getEntityCostTooltip(template);
    381413
     
    388420                if (template.speed)
    389421                    tooltip += "\n" + getEntitySpeed(template);
    390422
    391                 tooltip += "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train " + trainNum + ".[/font]";
    392 
     423                tooltip += formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch);
    393424                break;
    394425               
    395426            case RESEARCH:
     
    444475        // Button
    445476        var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]");
    446477        var button1 = getGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]");
    447         var affordableMask = getGUIObjectByName("unit"+guiName+"Unaffordable["+i+"]");
    448         var affordableMask1 = getGUIObjectByName("unit"+guiName+"Unaffordable["+(i+rowLength)+"]");
     478        var affordableMask = getGUIObjectByName("unit"+guiName+"Unaffordable["+i+"]");
     479        var affordableMask1 = getGUIObjectByName("unit"+guiName+"Unaffordable["+(i+rowLength)+"]");
    449480        var icon = getGUIObjectByName("unit"+guiName+"Icon["+i+"]");
    450481        var selection = getGUIObjectByName("unit"+guiName+"Selection["+i+"]");
    451482        var pair = getGUIObjectByName("unit"+guiName+"Pair["+i+"]");
     
    540571        {
    541572            icon.sprite = "stretched:session/icons/" + item.icon;
    542573        }
     574        else if (guiName == TRAINING)
     575        {
     576            if (template.icon)
     577            {
     578                var trainingCategory = null;
     579                if (template.trainingRestrictions)
     580                    trainingCategory = template.trainingRestrictions.category;
     581                var grayscale = "";
     582                if (trainingCategory && playerState.entityLimits[trainingCategory] && playerState.entityCounts[trainingCategory] >= playerState.entityLimits[trainingCategory])
     583                    grayscale = "grayscale:";
     584                icon.sprite = "stretched:" + grayscale + "session/portraits/" + template.icon;
     585            }
     586        }
    543587        else if (guiName == GATE)
    544588        {
    545589            var gateIcon;
     
    583627           
    584628            if (guiName == RESEARCH)
    585629            {
    586                 // Check resource requirements for first button
    587                 affordableMask.hidden = true;
    588                 var neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
    589                 if (neededResources)
    590                 {
    591                     if (button.enabled !== false)
    592                     {
    593                         button.enabled = false;
    594                         affordableMask.hidden = false;
    595                     }
    596                     button.tooltip += getNeededResourcesTooltip(neededResources);
    597                 }
    598 
     630                // Check resource requirements for first button
     631                affordableMask.hidden = true;
     632                var neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
     633                if (neededResources)
     634                {
     635                    if (button.enabled !== false)
     636                    {
     637                        button.enabled = false;
     638                        affordableMask.hidden = false;
     639                    }
     640                    button.tooltip += getNeededResourcesTooltip(neededResources);
     641                }
     642
    599643                if (item.pair)
    600644                {
    601645                    grayscale = "";
    602646                    button1.enabled = true;
    603 
    604                     if (!Engine.GuiInterfaceCall("CheckTechnologyRequirements", entType1))
     647
     648                    if (!Engine.GuiInterfaceCall("CheckTechnologyRequirements", entType1))
    605649                    {
    606650                        button1.enabled = false;
    607651                        button1.tooltip += "\n" + GetTechnologyData(entType1).requirementsTooltip;
    608652                        grayscale = "grayscale:";
    609653                    }
    610654                    icon1.sprite = "stretched:" + grayscale + "session/portraits/" +template1.icon;
    611 
    612                     // Check resource requirements for second button
    613                     affordableMask1.hidden = true;
    614                     neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
    615                     if (neededResources)
    616                     {
    617                         if (button1.enabled !== false)
    618                         {
    619                             button1.enabled = false;
    620                             affordableMask1.hidden = false;
    621                         }
    622                         button1.tooltip += getNeededResourcesTooltip(neededResources);
    623                     }
     655
     656                    // Check resource requirements for second button
     657                    affordableMask1.hidden = true;
     658                    neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
     659                    if (neededResources)
     660                    {
     661                        if (button1.enabled !== false)
     662                        {
     663                            button1.enabled = false;
     664                            affordableMask1.hidden = false;
     665                        }
     666                        button1.tooltip += getNeededResourcesTooltip(neededResources);
     667                    }
    624668                }
    625669                else
    626670                {
    627671                    pair.hidden = true;
    628672                    button1.hidden = true;
    629                     affordableMask1.hidden = true;
    630                 }
    631             }
    632             else if (guiName == CONSTRUCTION || guiName == TRAINING)
    633             {
    634                 affordableMask.hidden = true;
    635                 var totalCosts = {};
    636                 var trainNum = 1;
    637                 if (Engine.HotkeyIsPressed("session.batchtrain") && guiName == TRAINING)
    638                 {
    639                     var [batchSize, batchIncrement] = getTrainingBatchStatus(unitEntState.id, entType);
    640                     trainNum = batchSize + batchIncrement;
     673                    affordableMask1.hidden = true;
     674                }
     675            }
     676            else if (guiName == CONSTRUCTION || guiName == TRAINING)
     677            {
     678                affordableMask.hidden = true;
     679                var totalCosts = {};
     680                var trainNum = 1;
     681                if (Engine.HotkeyIsPressed("session.batchtrain") && guiName == TRAINING)
     682                {
     683                    var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] = getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
     684                    trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
    641685                }
    642 
    643                 // Walls have no cost defined.
     686
     687                // Walls have no cost defined.
    644688                if (template.cost !== undefined)
    645689                    for (var r in template.cost)
    646690                        totalCosts[r] = Math.floor(template.cost[r] * trainNum);
    647 
    648                 var neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCosts);
    649                 if (neededResources)
     691
     692                var neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCosts);
     693                if (neededResources)
    650694                {
    651                     var totalCost = 0;
    652                     if (button.enabled !== false)
     695                    var totalCost = 0;
     696                    if (button.enabled !== false)
    653697                    {
    654698                        for each (var resource in neededResources)
    655699                            totalCost += resource;
    656 
    657                         button.enabled = false;
    658                         affordableMask.hidden = false;
    659                         var alpha = 75 + totalCost/6;
    660                         alpha = alpha > 150 ? 150 : alpha;
    661                         affordableMask.sprite = "colour: 255 0 0 " + (alpha);
    662                     }
    663                     button.tooltip += getNeededResourcesTooltip(neededResources);
    664                 }
     700
     701                        button.enabled = false;
     702                        affordableMask.hidden = false;
     703                        var alpha = 75 + totalCost/6;
     704                        alpha = alpha > 150 ? 150 : alpha;
     705                        affordableMask.sprite = "colour: 255 0 0 " + (alpha);
     706                    }
     707                    button.tooltip += getNeededResourcesTooltip(neededResources);
     708                }
    665709            }
    666710        }
    667711        else
     
    767811}
    768812
    769813// Sets up "unit barter panel" - special case for setupUnitPanel
    770 function setupUnitBarterPanel(unitEntState)
     814function setupUnitBarterPanel(unitEntState, playerState)
    771815{
    772816    // Amount of player's resource to exchange
    773817    var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
     
    862906    var player = Engine.GetPlayerID();
    863907    if (entState.player == player || g_DevSettings.controlAll)
    864908    {
     909        // Get player state to check some constraints
     910        // e.g. presence of a hero or build limits
     911        var simState = Engine.GuiInterfaceCall("GetSimulationState");
     912        var playerState = simState.players[player];
     913
    865914        if (selection.length > 1)
    866             setupUnitPanel(SELECTION, usedPanels, entState, g_Selection.groups.getTemplateNames(),
     915            setupUnitPanel(SELECTION, usedPanels, entState, playerState, g_Selection.groups.getTemplateNames(),
    867916                function (entType) { changePrimarySelectionGroup(entType); } );
    868917
    869918        var commands = getEntityCommandsList(entState);
    870919        if (commands.length)
    871             setupUnitPanel(COMMAND, usedPanels, entState, commands,
     920            setupUnitPanel(COMMAND, usedPanels, entState, playerState, commands,
    872921                function (item) { performCommand(entState.id, item.name); } );
    873922
    874923        if (entState.garrisonHolder)
     
    881930                    groups.add(state.garrisonHolder.entities)
    882931            }
    883932
    884             setupUnitPanel(GARRISON, usedPanels, entState, groups.getTemplateNames(),
     933            setupUnitPanel(GARRISON, usedPanels, entState, playerState, groups.getTemplateNames(),
    885934                function (item) { unloadTemplate(item); } );
    886935        }
    887936
    888937        var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
    889938        if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
    890939        {
    891             setupUnitPanel(FORMATION, usedPanels, entState, formations,
     940            setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
    892941                function (item) { performFormation(entState.id, item); } );
    893942        }
    894943
     
    897946        var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
    898947        if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
    899948        {
    900             setupUnitPanel(STANCE, usedPanels, entState, stances,
     949            setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
    901950                function (item) { performStance(entState.id, item); } );
    902951        }
    903952
     
    905954        if (entState.barterMarket)
    906955        {
    907956            usedPanels["Barter"] = 1;
    908             setupUnitBarterPanel(entState);
     957            setupUnitBarterPanel(entState, playerState);
    909958        }
    910959
    911960        var buildableEnts = [];
     
    929978
    930979        // The first selected entity's type has priority.
    931980        if (entState.buildEntities)
    932             setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
     981            setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
    933982        else if (entState.production && entState.production.entities)
    934             setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
    935                 function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
     983            setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
     984                function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
    936985        else if (entState.trader)
    937986            setupUnitTradingPanel(usedPanels, entState, selection);
    938987        else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
     
    9941043                setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
    9951044            else if (trainableEnts.length)
    9961045                setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
    997                     function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
     1046                    function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
    9981047        }
    9991048
    10001049        // Show technologies if the active panel has at most one row of icons.
     
    10071056        }
    10081057
    10091058        if (entState.production && entState.production.queue.length)
    1010             setupUnitPanel(QUEUE, usedPanels, entState, entState.production.queue,
     1059            setupUnitPanel(QUEUE, usedPanels, entState, playerState, entState.production.queue,
    10111060                function (item) { removeFromProductionQueue(entState.id, item.id); } );
    10121061       
    10131062        supplementalDetailsPanel.hidden = false;
  • binaries/data/mods/public/simulation/ai/jubot/gamestate.js

     
    416416     * Returns player build limits
    417417     * an object where each key is a category corresponding to a build limit for the player.
    418418     */
    419     getBuildLimits: function()
     419    getEntityLimits: function()
    420420    {
    421         return this.playerData.buildLimits;
     421        return this.playerData.entityLimits;
    422422    },
    423423   
    424424    /**
    425425     * Returns player build counts
    426426     * an object where each key is a category corresponding to the current building count for the player.
    427427     */
    428     getBuildCounts: function()
     428    getEntityCounts: function()
    429429    {
    430         return this.playerData.buildCounts;
     430        return this.playerData.entityCounts;
    431431    },
    432432   
    433433    /**
    434434     * Checks if the player's build limit has been reached for the given category.
    435435     * The category comes from the entity tenplate, specifically the BuildRestrictions component.
    436436     */
    437     isBuildLimitReached: function(category)
     437    isEntityLimitReached: function(category)
    438438    {
    439         if (this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
     439        if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
    440440            return false;
    441441       
    442442        // There's a special case of build limits per civ centre, so check that first
    443         if (this.playerData.buildLimits[category].LimitPerCivCentre !== undefined)
    444             return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
     443        if (this.playerData.entityLimits[category].LimitPerCivCentre !== undefined)
     444            return (this.playerData.entityCounts[category] >= this.playerData.entityCounts["CivilCentre"]*this.playerData.entityLimits[category].LimitPerCivCentre);
    445445        else
    446             return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
     446            return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
    447447    },
    448448});
  • binaries/data/mods/public/simulation/ai/qbot/gamestate.js

     
    303303    return this.updatingCollection("resource-" + resource, Filters.byResource(resource), this.getEntities());
    304304};
    305305
    306 GameState.prototype.getBuildLimits = function() {
    307     return this.playerData.buildLimits;
     306GameState.prototype.getEntityLimits = function() {
     307    return this.playerData.entityLimits;
    308308};
    309309
    310 GameState.prototype.getBuildCounts = function() {
    311     return this.playerData.buildCounts;
     310GameState.prototype.getEntityCounts = function() {
     311    return this.playerData.entityCounts;
    312312};
    313313
    314314// Checks whether the maximum number of buildings have been cnstructed for a certain catergory
    315 GameState.prototype.isBuildLimitReached = function(category) {
    316     if(this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
     315GameState.prototype.isEntityLimitReached = function(category) {
     316    if(this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
    317317        return false;
    318     if(this.playerData.buildLimits[category].LimitsPerCivCentre != undefined)
    319         return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
     318    if(this.playerData.entityLimits[category].LimitsPerCivCentre != undefined)
     319        return (this.playerData.entityCounts[category] >= this.playerData.entityCounts["CivilCentre"]*this.playerData.entityLimits[category].LimitPerCivCentre);
    320320    else
    321         return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
     321        return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
    322322};
  • binaries/data/mods/public/simulation/ai/qbot/military.js

     
    324324// Adds towers to the defenceBuilding queue
    325325MilitaryAttackManager.prototype.buildDefences = function(gameState, queues){
    326326    if (gameState.countEntitiesAndQueuedByType(gameState.applyCiv('structures/{civ}_defense_tower'))
    327             + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["DefenseTower"]) {
     327            + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["DefenseTower"]) {
    328328       
    329329       
    330330        gameState.getOwnEntities().forEach(function(dropsiteEnt) {
     
    343343        numFortresses += gameState.countEntitiesAndQueuedByType(gameState.applyCiv(this.bFort[i]));
    344344    }
    345345   
    346     if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getBuildLimits()["Fortress"]) {
     346    if (numFortresses + queues.defenceBuilding.totalLength() < gameState.getEntityLimits()["Fortress"]) {
    347347        if (gameState.countEntitiesByType(gameState.applyCiv("units/{civ}_support_female_citizen")) > gameState.ai.modules["economy"].targetNumWorkers * 0.5){
    348348            if (gameState.getTimeElapsed() > 350 * 1000 * numFortresses){
    349349                if (gameState.ai.pathsToMe && gameState.ai.pathsToMe.length > 0){
  • binaries/data/mods/public/simulation/ai/testbot/gamestate.js

     
    269269     * Returns player build limits
    270270     * an object where each key is a category corresponding to a build limit for the player.
    271271     */
    272     getBuildLimits: function()
     272    getEntityLimits: function()
    273273    {
    274         return this.playerData.buildLimits;
     274        return this.playerData.entityLimits;
    275275    },
    276276   
    277277    /**
    278      * Returns player build counts
    279      * an object where each key is a category corresponding to the current building count for the player.
     278     * Returns player entity counts
     279     * an object where each key is a category corresponding to the current entity count for the player.
    280280     */
    281     getBuildCounts: function()
     281    getEntityCounts: function()
    282282    {
    283         return this.playerData.buildCounts;
     283        return this.playerData.entityCounts;
    284284    },
    285285   
    286286    /**
    287      * Checks if the player's build limit has been reached for the given category.
     287     * Checks if the player's entity limit has been reached for the given category.
    288288     * The category comes from the entity tenplate, specifically the BuildRestrictions component.
    289289     */
    290     isBuildLimitReached: function(category)
     290    isEntityLimitReached: function(category)
    291291    {
    292         if (this.playerData.buildLimits[category] === undefined || this.playerData.buildCounts[category] === undefined)
     292        if (this.playerData.entityLimits[category] === undefined || this.playerData.entityCounts[category] === undefined)
    293293            return false;
    294294       
    295295        // There's a special case of build limits per civ centre, so check that first
    296         if (this.playerData.buildLimits[category].LimitPerCivCentre !== undefined)
    297             return (this.playerData.buildCounts[category] >= this.playerData.buildCounts["CivilCentre"]*this.playerData.buildLimits[category].LimitPerCivCentre);
     296        if (this.playerData.entityLimits[category].LimitPerCivCentre !== undefined)
     297            return (this.playerData.entityCounts[category] >= this.playerData.entityCounts["CivilCentre"]*this.playerData.entityLimits[category].LimitPerCivCentre);
    298298        else
    299             return (this.playerData.buildCounts[category] >= this.playerData.buildLimits[category]);
     299            return (this.playerData.entityCounts[category] >= this.playerData.entityLimits[category]);
    300300    },
    301301});
  • binaries/data/mods/public/simulation/components/BuildLimits.js

     
    1 function BuildLimits() {}
    2 
    3 BuildLimits.prototype.Schema =
    4     "<a:help>Specifies per category limits on number of buildings that can be constructed for each player.</a:help>" +
    5     "<a:example>" +
    6         "<Limits>" +
    7           "<CivilCentre/>" +
    8           "<DefenseTower>25</DefenseTower>" +
    9           "<Fortress>10</Fortress>" +
    10           "<Special>" +
    11             "<LimitPerCivCentre>1</LimitPerCivCentre>" +
    12           "</Special>" +
    13         "</Limits>" +
    14     "</a:example>" +
    15     "<element name='LimitMultiplier'>" +
    16         "<ref name='positiveDecimal'/>" +
    17     "</element>" +
    18     "<element name='Limits'>" +
    19         "<zeroOrMore>" +
    20             "<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
    21                 "<anyName />" +
    22                 "<choice>" +
    23                     "<text />" +
    24                     "<element name='LimitPerCivCentre' a:help='Specifies that this limit is per number of civil centres.'>" +
    25                         "<data type='nonNegativeInteger'/>" +
    26                     "</element>" +
    27                 "</choice>" +
    28             "</element>" +
    29         "</zeroOrMore>" +
    30     "</element>";
    31 
    32 /*
    33  *  TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
    34  */
    35 
    36 BuildLimits.prototype.Init = function()
    37 {
    38     this.limit = {};
    39     this.count = {};
    40     for (var category in this.template.Limits)
    41     {
    42         this.limit[category] = this.template.Limits[category];
    43         this.count[category] = 0;
    44     }
    45 };
    46 
    47 BuildLimits.prototype.IncrementCount = function(category)
    48 {
    49     if (this.count[category] !== undefined)
    50     {
    51         this.count[category]++;
    52     }
    53 };
    54 
    55 BuildLimits.prototype.DecrementCount = function(category)
    56 {
    57     if (this.count[category] !== undefined)
    58     {
    59         this.count[category]--;
    60     }
    61 };
    62 
    63 BuildLimits.prototype.GetLimits = function()
    64 {
    65     return this.limit;
    66 };
    67 
    68 BuildLimits.prototype.GetCounts = function()
    69 {
    70     return this.count;
    71 };
    72 
    73 BuildLimits.prototype.AllowedToBuild = function(category)
    74 {
    75     // TODO: The UI should reflect this before the user tries to place the building,
    76     //          since the limits are independent of placement location
    77 
    78     // Allow unspecified categories and those with no limit
    79     if (this.count[category] === undefined || this.limit[category] === undefined)
    80     {
    81         return true;
    82     }
    83    
    84     // Rather than complicating the schema unecessarily, just handle special cases here
    85     if (this.limit[category].LimitPerCivCentre !== undefined)
    86     {
    87         if (this.count[category] >= this.count["CivilCentre"] * this.limit[category].LimitPerCivCentre)
    88         {
    89             var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    90             var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category].LimitPerCivCentre+" per civil centre reached"};
    91             var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
    92             cmpGUIInterface.PushNotification(notification);
    93            
    94             return false;
    95         }
    96     }
    97     else if (this.count[category] >= this.limit[category])
    98     {
    99         var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    100         var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category]+ " reached"};
    101         var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
    102         cmpGUIInterface.PushNotification(notification);
    103        
    104         return false;
    105     }
    106    
    107     return true;
    108 };
    109 
    110 BuildLimits.prototype.OnGlobalOwnershipChanged = function(msg)
    111 {
    112     // This automatically updates build counts
    113     var cmpBuildRestrictions = Engine.QueryInterface(msg.entity, IID_BuildRestrictions);
    114     if (cmpBuildRestrictions)
    115     {
    116         var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
    117         if (msg.from == playerID)
    118         {
    119             this.DecrementCount(cmpBuildRestrictions.GetCategory());
    120         }
    121         if (msg.to == playerID)
    122         {
    123             this.IncrementCount(cmpBuildRestrictions.GetCategory());
    124         }
    125     }
    126 };
    127 
    128 Engine.RegisterComponentType(IID_BuildLimits, "BuildLimits", BuildLimits);
  • binaries/data/mods/public/simulation/components/EntityLimits.js

     
     1function EntityLimits() {}
     2
     3EntityLimits.prototype.Schema =
     4    "<a:help>Specifies per category limits on number of entities (buildings or units) that can be created for each player.</a:help>" +
     5    "<a:example>" +
     6        "<Limits>" +
     7          "<CivilCentre/>" +
     8          "<DefenseTower>25</DefenseTower>" +
     9          "<Fortress>10</Fortress>" +
     10          "<Hero>1</Hero>" +
     11          "<Special>" +
     12            "<LimitPerCivCentre>1</LimitPerCivCentre>" +
     13          "</Special>" +
     14        "</Limits>" +
     15    "</a:example>" +
     16    "<element name='LimitMultiplier'>" +
     17        "<ref name='positiveDecimal'/>" +
     18    "</element>" +
     19    "<element name='Limits'>" +
     20        "<zeroOrMore>" +
     21            "<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
     22                "<anyName />" +
     23                "<choice>" +
     24                    "<text />" +
     25                    "<element name='LimitPerCivCentre' a:help='Specifies that this limit is per number of civil centres.'>" +
     26                        "<data type='nonNegativeInteger'/>" +
     27                    "</element>" +
     28                "</choice>" +
     29            "</element>" +
     30        "</zeroOrMore>" +
     31    "</element>";
     32
     33/*
     34 *  TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
     35 */
     36
     37EntityLimits.prototype.Init = function()
     38{
     39    this.limit = {};
     40    this.count = {};
     41    for (var category in this.template.Limits)
     42    {
     43        this.limit[category] = this.template.Limits[category];
     44        this.count[category] = 0;
     45    }
     46};
     47
     48EntityLimits.prototype.IncreaseCount = function(category, value)
     49{
     50    if (this.count[category] !== undefined)
     51    {
     52        this.count[category] += value;
     53    }
     54};
     55
     56EntityLimits.prototype.DecreaseCount = function(category, value)
     57{
     58    if (this.count[category] !== undefined)
     59    {
     60        this.count[category] -= value;
     61    }
     62};
     63
     64EntityLimits.prototype.IncrementCount = function(category)
     65{
     66    this.IncreaseCount(category, 1);
     67};
     68
     69EntityLimits.prototype.DecrementCount = function(category)
     70{
     71    this.DecreaseCount(category, 1);
     72};
     73
     74EntityLimits.prototype.GetLimits = function()
     75{
     76    return this.limit;
     77};
     78
     79EntityLimits.prototype.GetCounts = function()
     80{
     81    return this.count;
     82};
     83
     84EntityLimits.prototype.AllowedToBuild = function(category)
     85{
     86    // TODO: The UI should reflect this before the user tries to place the building,
     87    //          since the limits are independent of placement location
     88
     89    // Allow unspecified categories and those with no limit
     90    if (this.count[category] === undefined || this.limit[category] === undefined)
     91    {
     92        return true;
     93    }
     94   
     95    // Rather than complicating the schema unecessarily, just handle special cases here
     96    if (this.limit[category].LimitPerCivCentre !== undefined)
     97    {
     98        if (this.count[category] >= this.count["CivilCentre"] * this.limit[category].LimitPerCivCentre)
     99        {
     100            var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     101            var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category].LimitPerCivCentre+" per civil centre reached"};
     102            var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     103            cmpGUIInterface.PushNotification(notification);
     104           
     105            return false;
     106        }
     107    }
     108    else if (this.count[category] >= this.limit[category])
     109    {
     110        var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     111        var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" train limit of "+this.limit[category]+ " reached"};
     112        var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     113        cmpGUIInterface.PushNotification(notification);
     114       
     115        return false;
     116    }
     117   
     118    return true;
     119};
     120
     121EntityLimits.prototype.AllowedToTrain = function(category, count)
     122{
     123    // Allow unspecified categories and those with no limit
     124    if (this.count[category] === undefined || this.limit[category] === undefined)
     125    {
     126        return true;
     127    }
     128    if (this.count[category] + count > this.limit[category])
     129    {
     130        var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     131        var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category]+ " reached"};
     132        var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     133        cmpGUIInterface.PushNotification(notification);
     134       
     135        return false;
     136    }
     137    return true;
     138};
     139
     140EntityLimits.prototype.OnGlobalOwnershipChanged = function(msg)
     141{
     142    // This automatically updates entity counts
     143    var category = null;
     144    var cmpBuildRestrictions = Engine.QueryInterface(msg.entity, IID_BuildRestrictions);
     145    if (cmpBuildRestrictions)
     146        category = cmpBuildRestrictions.GetCategory();
     147    var cmpTrainingRestrictions = Engine.QueryInterface(msg.entity, IID_TrainingRestrictions);
     148    if (cmpTrainingRestrictions)
     149        category = cmpTrainingRestrictions.GetCategory();
     150    if (category)
     151    {
     152        var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
     153        if (msg.from == playerID)
     154        {
     155            this.DecrementCount(category);
     156        }
     157        if (msg.to == playerID)
     158        {
     159            this.IncrementCount(category);
     160        }
     161    }
     162};
     163
     164Engine.RegisterComponentType(IID_EntityLimits, "EntityLimits", EntityLimits);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    4949    for (var i = 0; i < n; ++i)
    5050    {
    5151        var playerEnt = cmpPlayerMan.GetPlayerByID(i);
    52         var cmpPlayerBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits);
     52        var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
    5353        var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
    5454       
    5555        // Work out what phase we are in
     
    8484            "phase": phase,
    8585            "isAlly": allies,
    8686            "isEnemy": enemies,
    87             "buildLimits": cmpPlayerBuildLimits.GetLimits(),
    88             "buildCounts": cmpPlayerBuildLimits.GetCounts(),
     87            "entityLimits": cmpPlayerEntityLimits.GetLimits(),
     88            "entityCounts": cmpPlayerEntityLimits.GetCounts(),
    8989            "techModifications": cmpTechnologyManager.GetTechModifications()
    9090        };
    9191        ret.players.push(playerData);
     
    380380            if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
    381381        }
    382382    }
    383    
     383
     384    if (template.TrainingRestrictions)
     385    {
     386        ret.trainingRestrictions = {
     387            "category": template.TrainingRestrictions.Category,
     388        };
     389    }
     390
    384391    if (template.Cost)
    385392    {
    386393        ret.cost = {};
     
    449456        ret.icon = template.Identity.Icon;
    450457        ret.tooltip =  template.Identity.Tooltip;
    451458        ret.requiredTechnology = template.Identity.RequiredTechnology;
     459        ret.identityClassesString = GetTemplateIdentityClassesString(template);
    452460    }
    453461
    454462    if (template.UnitMotion)
  • binaries/data/mods/public/simulation/components/Player.js

     
    358358Player.prototype.OnGlobalOwnershipChanged = function(msg)
    359359{
    360360    var isConquestCritical = false;
    361 
    362361    // Load class list only if we're going to need it
    363362    if (msg.from == this.playerID || msg.to == this.playerID)
    364363    {
    365364        var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
    366365        if (cmpIdentity)
    367366        {
    368             var classes = cmpIdentity.GetClassesList();
    369             isConquestCritical = classes.indexOf("ConquestCritical") != -1;
     367            isConquestCritical = cmpIdentity.HasClass("ConquestCritical");
    370368        }
    371369    }
    372    
    373370    if (msg.from == this.playerID)
    374371    {
    375372        if (isConquestCritical)
    376             this.conquestCriticalEntitiesCount--;   
    377 
     373            this.conquestCriticalEntitiesCount--;
    378374        var cost = Engine.QueryInterface(msg.entity, IID_Cost);
    379375        if (cost)
    380376        {
     
    382378            this.popBonuses -= cost.GetPopBonus();
    383379        }
    384380    }
    385    
    386381    if (msg.to == this.playerID)
    387382    {
    388383        if (isConquestCritical)
    389384            this.conquestCriticalEntitiesCount++;
    390            
    391385        var cost = Engine.QueryInterface(msg.entity, IID_Cost);
    392386        if (cost)
    393387        {
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

     
    212212            if (!cmpPlayer.TrySubtractResources(totalCosts))
    213213                return;
    214214
     215            // Update entity count in the EntityLimits component
     216            if (template.TrainingRestrictions)
     217            {
     218                var unitCategory = template.TrainingRestrictions.Category;
     219                var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
     220                cmpPlayerEntityLimits.IncreaseCount(unitCategory, count);
     221            }
     222
    215223            this.queue.push({
    216224                "id": this.nextID++,
    217225                "player": cmpPlayer.GetPlayerID(),
     
    307315       
    308316        var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player);
    309317
     318        // Update entity count in the EntityLimits component
     319        if (item.unitTemplate)
     320        {
     321            var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     322            var template = cmpTempMan.GetTemplate(item.unitTemplate);
     323            if (template.TrainingRestrictions)
     324            {
     325                var unitCategory = template.TrainingRestrictions.Category;
     326                var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
     327                cmpPlayerEntityLimits.DecreaseCount(unitCategory, item.count);
     328            }
     329        }
     330
    310331        // Refund the resource cost for this batch
    311332        var totalCosts = {};
    312333        var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
     
    422443        //  so only create them once and use as needed
    423444        for (var i = 0; i < count; ++i)
    424445        {
    425             this.entityCache.push(Engine.AddEntity(templateName));
     446            var ent = Engine.AddEntity(templateName);
     447            this.entityCache.push(ent);
     448
     449            // Decrement entity count in the EntityLimits component
     450            // since it will be increased by EntityLimits.OnGlobalOwnershipChanged function,
     451            // i.e. we replace a 'trained' entity to an 'alive' one
     452            var cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
     453            if (cmpTrainingRestrictions)
     454            {
     455                var unitCategory = cmpTrainingRestrictions.GetCategory();
     456                var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
     457                cmpPlayerEntityLimits.DecrementCount(unitCategory);
     458            }
    426459        }
    427460    }
    428461
  • binaries/data/mods/public/simulation/components/TrainingRestrictions.js

     
     1function TrainingRestrictions() {}
     2
     3TrainingRestrictions.prototype.Schema =
     4    "<a:help>Specifies unit training restrictions, currently only unit category.</a:help>" +
     5    "<a:example>" +
     6        "<TrainingRestrictions>" +
     7            "<Category>Hero</Category>" +
     8        "</TrainingRestrictions>" +
     9    "</a:example>" +
     10    "<element name='Category' a:help='Specifies the category of this unit, for satisfying special constraints.'>" +
     11        "<choice>" +
     12            "<value>Hero</value>" +
     13            "<value>FemaleCitizen</value>" +
     14        "</choice>" +
     15    "</element>";
     16
     17TrainingRestrictions.prototype.GetCategory = function()
     18{
     19    return this.template.Category;
     20};
     21
     22Engine.RegisterComponentType(IID_TrainingRestrictions, "TrainingRestrictions", TrainingRestrictions);
  • binaries/data/mods/public/simulation/components/interfaces/BuildLimits.js

     
    1 Engine.RegisterInterface("BuildLimits");
    2  No newline at end of file
  • binaries/data/mods/public/simulation/components/interfaces/EntityLimits.js

     
     1Engine.RegisterInterface("EntityLimits");
  • binaries/data/mods/public/simulation/components/interfaces/TrainingRestrictions.js

     
     1Engine.RegisterInterface("TrainingRestrictions");
     2
  • binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js

     
    11Engine.LoadComponentScript("interfaces/Attack.js");
    22Engine.LoadComponentScript("interfaces/Barter.js");
    33Engine.LoadComponentScript("interfaces/Builder.js");
    4 Engine.LoadComponentScript("interfaces/BuildLimits.js");
    54Engine.LoadComponentScript("interfaces/DamageReceiver.js");
     5Engine.LoadComponentScript("interfaces/EntityLimits.js");
    66Engine.LoadComponentScript("interfaces/Foundation.js");
    77Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
    88Engine.LoadComponentScript("interfaces/Gate.js");
     
    6767    IsEnemy: function() { return true; },
    6868});
    6969
    70 AddMock(100, IID_BuildLimits, {
     70AddMock(100, IID_EntityLimits, {
    7171    GetLimits: function() { return {"Foo": 10}; },
    7272    GetCounts: function() { return {"Foo": 5}; },
    7373});
     
    118118    IsEnemy: function() { return false; },
    119119});
    120120
    121 AddMock(101, IID_BuildLimits, {
     121AddMock(101, IID_EntityLimits, {
    122122    GetLimits: function() { return {"Bar": 20}; },
    123123    GetCounts: function() { return {"Bar": 0}; },
    124124});
     
    171171            phase: "",
    172172            isAlly: [false, false, false],
    173173            isEnemy: [true, true, true],
    174             buildLimits: {"Foo": 10},
    175             buildCounts: {"Foo": 5},
     174            entityLimits: {"Foo": 10},
     175            entityCounts: {"Foo": 5},
    176176            techModifications: {},
    177177        },
    178178        {
     
    189189            phase: "village",
    190190            isAlly: [true, true, true],
    191191            isEnemy: [false, false, false],
    192             buildLimits: {"Bar": 20},
    193             buildCounts: {"Bar": 0},
     192            entityLimits: {"Bar": 20},
     193            entityCounts: {"Bar": 0},
    194194            techModifications: {},
    195195        }
    196196    ],
     
    214214            phase: "",
    215215            isAlly: [false, false, false],
    216216            isEnemy: [true, true, true],
    217             buildLimits: {"Foo": 10},
    218             buildCounts: {"Foo": 5},
     217            entityLimits: {"Foo": 10},
     218            entityCounts: {"Foo": 5},
    219219            techModifications: {},
    220220            statistics: {
    221221                unitsTrained: 10,
     
    248248            phase: "village",
    249249            isAlly: [true, true, true],
    250250            isEnemy: [false, false, false],
    251             buildLimits: {"Bar": 20},
    252             buildCounts: {"Bar": 0},
     251            entityLimits: {"Bar": 20},
     252            entityCounts: {"Bar": 0},
    253253            techModifications: {},
    254254            statistics: {
    255255                unitsTrained: 10,
  • binaries/data/mods/public/simulation/data/technologies/no_heroes.json

     
    1 {   
    2     "genericName": "Future Alpha",
    3     "description": "Temporarily Disable Heroes",
    4     "cost": {"food": 0, "wood": 100, "stone": 0, "metal": 0},
    5     "requirements": {"tech": "phase_city"},
    6     "requirementsTooltip": "Unlocked in City Phase.",
    7     "icon": "arrow.png",
    8     "researchTime": 20,
    9     "tooltip": "Need build limits before renabling this."
    10 }
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    146146
    147147    case "train":
    148148        var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
     149       
     150        // Check entity limits
     151        var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     152        var template = cmpTempMan.GetTemplate(cmd.template);
     153        var unitCategory = null;
     154        if (template.TrainingRestrictions)
     155            unitCategory = template.TrainingRestrictions.Category;
     156
    149157        // Verify that the building(s) can be controlled by the player
    150158        if (entities.length > 0)
    151159        {
    152160            for each (var ent in entities)
    153161            {
     162                if (unitCategory)
     163                {
     164                    var cmpPlayerEntityLimits = QueryOwnerInterface(ent, IID_EntityLimits);
     165                    if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count))
     166                    {
     167                        if (g_DebugCommands)
     168                        {
     169                            warn(unitCategory + " train limit is reached: " + uneval(cmd));
     170                        }
     171                        continue;
     172                    }
     173                }
     174
    154175                var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
    155176                // TODO: Enable this check once the AI gets technology support
    156177                if (cmpTechnologyManager.CanProduce(cmd.template) || true)
     
    545566        return false;
    546567    }
    547568   
    548     // Check build limits
    549     var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
    550     if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
     569    // Check entity limits
     570    var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
     571    if (!cmpEntityLimits || !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
    551572    {
    552573        if (g_DebugCommands)
    553574        {
  • binaries/data/mods/public/simulation/helpers/Templates.js

     
     1/**
     2 * Return template.Identity.Classes._string if exists
     3 */
     4function GetTemplateIdentityClassesString(template)
     5{
     6    var identityClassesString = undefined;
     7    if (template.Identity && template.Identity.Classes && "_string" in template.Identity.Classes)
     8        identityClassesString = template.Identity.Classes._string;
     9    return identityClassesString;
     10}
     11
     12/**
     13 * Check whether template.Identity.Classes contains specified class
     14 */
     15function TemplateHasIdentityClass(template, className)
     16{
     17    var identityClassesString = GetTemplateIdentityClassesString(template);
     18    var hasClass = identityClassesString && identityClassesString.indexOf(className) != -1;
     19    return hasClass;
     20}
     21
     22Engine.RegisterGlobal("GetTemplateIdentityClassesString", GetTemplateIdentityClassesString);
     23Engine.RegisterGlobal("TemplateHasIdentityClass", TemplateHasIdentityClass);
     24
  • binaries/data/mods/public/simulation/templates/special/player.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22<Entity>
    3   <BuildLimits>
     3  <EntityLimits>
    44    <LimitMultiplier>1.0</LimitMultiplier>
    55    <Limits>
    66      <CivilCentre/>
    77      <DefenseTower>25</DefenseTower>
    88      <Fortress>10</Fortress>
     9      <FemaleCitizen>32</FemaleCitizen>
     10      <Hero>1</Hero>
    911    </Limits>
    10   </BuildLimits>
     12  </EntityLimits>
    1113  <Player/>
    1214  <StatisticsTracker/>
    1315  <TechnologyManager/>
  • binaries/data/mods/public/simulation/templates/template_unit_hero.xml

     
    3636  <Identity>
    3737    <GenericName>Hero</GenericName>
    3838    <Classes datatype="tokens">Hero Organic</Classes>
    39     <RequiredTechnology>no_heroes</RequiredTechnology>
    4039  </Identity>
    4140  <Loot>
    4241    <xp>400</xp>
     
    6362      <death>actor/human/death/death.xml</death>
    6463    </SoundGroups>
    6564  </Sound>
     65  <TrainingRestrictions>
     66    <Category>Hero</Category>
     67  </TrainingRestrictions>
    6668  <UnitMotion>
    6769    <WalkSpeed>9.0</WalkSpeed>
    6870    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry.xml

     
    4040  <Identity>
    4141    <GenericName>Hero Cavalry</GenericName>
    4242    <Classes datatype="tokens">Hero Cavalry</Classes>
    43     <RequiredTechnology>no_heroes</RequiredTechnology>
    4443  </Identity>
    4544  <Loot>
    4645    <xp>500</xp>
     
    7069  <Stamina>
    7170    <Max>2500</Max>
    7271  </Stamina>
     72  <TrainingRestrictions>
     73    <Category>Hero</Category>
     74  </TrainingRestrictions>
    7375  <UnitMotion>
    7476    <WalkSpeed>11.0</WalkSpeed>
    7577    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml

     
    4949    <GenericName>Hero Cavalry Archer</GenericName>
    5050    <Tooltip>Hero Aura: n/a.
    5151Ranged attack 2x vs. spearmen. Ranged attack 1.5x vs. Swordsmen.</Tooltip>
    52     <RequiredTechnology>no_heroes</RequiredTechnology>
    5352  </Identity>
    5453  <Loot>
    5554    <xp>450</xp>
     
    7069      </Texture>
    7170    </Overlay>
    7271  </Selectable>
     72  <TrainingRestrictions>
     73    <Category>Hero</Category>
     74  </TrainingRestrictions>
    7375  <UnitMotion>
    7476    <WalkSpeed>11.0</WalkSpeed>
    7577    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml

     
    3939  <Identity>
    4040    <Classes datatype="tokens">Hero</Classes>
    4141    <GenericName>Hero Cavalry Skirmisher</GenericName>
    42     <RequiredTechnology>no_heroes</RequiredTechnology>
    4342  </Identity>
    4443  <Loot>
    4544    <xp>450</xp>
     
    6059      </Texture>
    6160    </Overlay>
    6261  </Selectable>
     62  <TrainingRestrictions>
     63    <Category>Hero</Category>
     64  </TrainingRestrictions>
    6365  <UnitMotion>
    6466    <WalkSpeed>11.5</WalkSpeed>
    6567    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_infantry.xml

     
    3838  <Identity>
    3939    <Classes datatype="tokens">Hero Infantry</Classes>
    4040    <GenericName>Hero</GenericName>
    41     <RequiredTechnology>no_heroes</RequiredTechnology>
    4241  </Identity>
    4342  <Loot>
    4443    <xp>400</xp>
     
    6867  <Stamina>
    6968    <Max>1500</Max>
    7069  </Stamina>
     70  <TrainingRestrictions>
     71    <Category>Hero</Category>
     72  </TrainingRestrictions>
    7173  <UnitMotion>
    7274    <WalkSpeed>8.5</WalkSpeed>
    7375    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml

     
    4141    <Classes datatype="tokens">Hero</Classes>
    4242    <Tooltip>Hero Archer.
    4343Counters Swordsmen and Cavalry Spearmen. Countered by Skirmishers and other Cavalry types.</Tooltip>
    44     <RequiredTechnology>no_heroes</RequiredTechnology>
    4544  </Identity>
    4645  <Loot>
    4746    <xp>350</xp>
     
    6261      </Texture>
    6362    </Overlay>
    6463  </Selectable>
     64  <TrainingRestrictions>
     65    <Category>Hero</Category>
     66  </TrainingRestrictions>
    6567</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml

     
    4545  <Identity>
    4646    <Classes datatype="tokens">Hero</Classes>
    4747    <GenericName>Hero Skirmisher</GenericName>
    48     <RequiredTechnology>no_heroes</RequiredTechnology>
    4948  </Identity>
    5049  <Loot>
    5150    <xp>350</xp>
     
    6665      </Texture>
    6766    </Overlay>
    6867  </Selectable>
     68  <TrainingRestrictions>
     69    <Category>Hero</Category>
     70  </TrainingRestrictions>
    6971</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml

     
    5151      </Texture>
    5252    </Overlay>
    5353  </Selectable>
     54  <TrainingRestrictions>
     55    <Category>Hero</Category>
     56  </TrainingRestrictions>
    5457  <UnitMotion>
    5558    <WalkSpeed>8.5</WalkSpeed>
    5659    <Run>
  • binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml

     
    9999  <Stamina>
    100100    <Max>500</Max>
    101101  </Stamina>
     102  <TrainingRestrictions>
     103    <Category>FemaleCitizen</Category>
     104  </TrainingRestrictions>
    102105  <UnitMotion>
    103106    <WalkSpeed>8.0</WalkSpeed>
    104107    <Run>