Index: binaries/data/mods/public/gui/session/selection.js =================================================================== --- binaries/data/mods/public/gui/session/selection.js +++ binaries/data/mods/public/gui/session/selection.js @@ -438,6 +438,7 @@ var g_canMoveIntoFormation = {}; var g_allBuildableEntities; var g_allTrainableEntities; +var g_allTrainableGroups; // Reset cached quantities function onSelectionChange() @@ -445,6 +446,7 @@ g_canMoveIntoFormation = {}; g_allBuildableEntities = undefined; g_allTrainableEntities = undefined; + g_allTrainableGroups = undefined; } /** Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -1020,6 +1020,139 @@ } }; +g_SelectionPanels.GroupTraining = { + "getMaxNumberOfItems": function() + { + return 24 - getNumberOfRightPanelButtons(); + }, + "getItems": function() + { + return getAllTrainableGroupsFromSelection(); + }, + "setupButton": function(data) + { + let template = GetGroupTemplateData(data.item); + if (!template) + return false; + + let technologyEnabled = true; + for (let entry in template.Entries) + { + technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", { + "tech": template.Entries[entry].templateData.requiredTechnology, + "player": data.player + }); + if (!technologyEnabled) + break; + } + + let unitIds = data.unitEntStates.map(status => status.id); + + let trainNum = 0; + let tooltips = []; + let maxNeededResources; + let entriesToRecruit = Object.keys(template.Entries); + if (template.MutuallyExclusive && entriesToRecruit.length > 1) + { + let weightedListOfEntries = []; + let maxTrainNum = 0; + for (let entry in template.Entries) + { + let entTemplate = template.Entries[entry].data.Template; + let [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] = + getTrainingStatus(unitIds, entTemplate, data.playerState); + trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch; + maxTrainNum = Math.max(+(template.Entries[entry].data.Amount || 1) * trainNum, maxTrainNum); + + let neededResources; + if (template.cost) + neededResources = Engine.GuiInterfaceCall("GetNeededResources", { + "cost": multiplyEntityCosts(template, trainNum), + "player": data.player + }); + maxNeededResources = neededResources; +warn("MNR: " + uneval(maxNeededResources)); // Needs to be split to check all resources seperately. +warn("MTN: " + uneval(maxTrainNum)); + + let limits = getEntityLimitAndCount(data.playerState, entTemplate); + tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers)); + + let chance = +(template.Entries[entry].data.Chance || 1); + for (let i = 0; i < chance; i++) + weightedListOfEntries.push(entry); + } + entriesToRecruit = pickRandom(weightedListOfEntries); + } + + data.button.onPress = function() { + if (!maxNeededResources) + addTrainingToQueue(unitIds, template.Entries[entriesToRecruit].data.Template, data.playerState); + }; + + // Not yet supported. +// data.button.onPressRight = function() { +// showTemplateDetails(data.item); +// }; + + data.countDisplay.caption = trainNum > 1 ? trainNum : ""; + + // Not yet supported + tooltips = [ + "[font=\"sans-bold-16\"]" + + colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) + + "[/font]" + " " + template.Name //+ getEntityNamesFormatted(template), +// getVisibleEntityClassesFormatted(template), +// getAurasTooltip(template), +// getEntityTooltip(template), +// getEntityCostTooltip(template, unitIds[0], buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) + ]; + +// if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true") +// tooltips = tooltips.concat([ +// getHealthTooltip, +// getAttackTooltip, +// getSplashDamageTooltip, +// getHealerTooltip, +// getArmorTooltip, +// getGarrisonTooltip, +// getProjectilesTooltip, +// getSpeedTooltip +// ].map(func => func(template))); + +// tooltips.push(showTemplateViewerOnRightClickTooltip()); +// tooltips.push( +// formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch), +// getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ), +// getNeededResourcesTooltip(neededResources)); + + data.button.tooltip = tooltips.filter(tip => tip).join("\n"); + + let modifier = ""; +// if (!technologyEnabled || limits.canBeAddedCount == 0) +// { +// data.button.enabled = false; +// modifier = "color:0 0 0 127:grayscale:"; +// } +// else + { + data.button.enabled = controlsPlayer(data.player); + if (maxNeededResources) + modifier = resourcesToAlphaMask(maxNeededResources) + ":"; + } + + if (template.icon) + data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon; + + let index = data.i + getNumberOfRightPanelButtons(); + setPanelObjectPosition(data.button, index, data.rowLength); + +warn("Called: " + uneval(template.Entries[entriesToRecruit].data.Template)); +//return false; + + return true; + } +}; + g_SelectionPanels.Upgrade = { "getMaxNumberOfItems": function() { @@ -1171,6 +1304,7 @@ "Training", "Construction", "Research", // Normal together with training + "GroupTraining", // Should fit together with training and research. // UNIQUE PANES (importance doesn't matter) "Command", Index: binaries/data/mods/public/gui/session/selection_panels_right/grouptraining_panel.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/selection_panels_right/grouptraining_panel.xml @@ -0,0 +1,19 @@ + + + + + Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -152,6 +152,7 @@ var g_SimState; var g_EntityStates = {}; var g_TemplateData = {}; +var g_GroupTemplateData = {}; var g_TechnologyData = {}; var g_ResourceData = new Resources(); @@ -230,6 +231,18 @@ return g_TemplateData[templateName]; } +function GetGroupTemplateData(groupName) +{ + if (!(groupName in g_GroupTemplateData)) + { + let template = Engine.GuiInterfaceCall("GetGroupTemplateData", groupName); + translateObjectKeys(template, ["Name", "Tooltip"]); + g_GroupTemplateData[groupName] = deepfreeze(template); + } + + return g_GroupTemplateData[groupName]; +} + function GetTechnologyData(technologyName, civ) { if (!g_TechnologyData[civ]) Index: binaries/data/mods/public/gui/session/unit_commands.js =================================================================== --- binaries/data/mods/public/gui/session/unit_commands.js +++ binaries/data/mods/public/gui/session/unit_commands.js @@ -5,6 +5,7 @@ "Formation": 0, "Garrison": 0, "Training": 0, + "GroupTraining": 0, "Research": 0, "Alert": 0, "Barter": 0, @@ -204,6 +205,31 @@ return g_allTrainableEntities; } +// Get all of the available entities which can be trained by the selected entities +function getAllTrainableGroups(selection) +{ + let trainableGroups = []; + // Get all buildable and trainable groups + for (let ent of selection) + { + let state = GetEntityState(ent); + if (state && state.production && state.production.groups.length) + trainableGroups = trainableGroups.concat(state.production.groups); + } + + // Remove duplicates + removeDupes(trainableGroups); + return trainableGroups; +} + +function getAllTrainableGroupsFromSelection() +{ + if (!g_allTrainableGroups) + g_allTrainableGroups = getAllTrainableGroups(g_Selection.toList()); + + return g_allTrainableGroups; +} + // Get all of the available entities which can be built by the selected entities function getAllBuildableEntities(selection) { Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -295,6 +295,7 @@ if (cmpProductionQueue) ret.production = { "entities": cmpProductionQueue.GetEntitiesList(), + "groups": cmpProductionQueue.GetGroupsList(), "technologies": cmpProductionQueue.GetTechnologiesList(), "techCostMultiplier": cmpProductionQueue.GetTechCostMultiplier(), "queue": cmpProductionQueue.GetQueue() @@ -557,6 +558,92 @@ return GetTemplateDataHelper(template, player, aurasTemplate, Resources); }; +/* + * Calculate the and pass the data of a group. + * @param {object} group - The group to check. + * @return {object} - A object with e.g. maximum tech-requirements, resource costs etc. + */ +GuiInterface.prototype.GetGroupTemplateData = function(player, templateName) +{ + let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + let template = cmpTemplateManager.GetTemplate(templateName); + + if (!template || !template.ProductionQueue || + !template.ProductionQueue.Composition || !template.ProductionQueue.Composition.Entries) + return null; + + template = template.ProductionQueue.Composition; + let ret = { + "Name": template.Name, + "Tooltip": template.Tooltip, + "MutuallyExclusive": template.MutuallyExclusive, + "Icon": template.Icon, + "Entries" : {} + }; + for (let entry in template.Entries) + ret.Entries[entry] = { + "data": template.Entries[entry], + "templateData": this.GetTemplateData(player, template.Entries[entry].Template) + }; +warn(uneval(ret)); + return ret; + +/* +getTrainingStatus on selection_panels.js? + + let maxNeededResources = "ToDo"; + let maxAmount = 0; + let limits = "ToDo"; + let requiredTechs = []; + let entriesToRecruit = Object.keys(group.Entries); + let weightedListOfTemplates = []; + let recruitableTemplates = []; + + for (let entry in entriesToRecruit) + { + requiredTechs.push(group.Entries[entry].templateData.requiredTechnology); + recruitableTemplates.push(group.Entries[entry].data.Template); +// Limits + + } + + if (group.MutuallyExclusive && entriesToRecruit.length > 1) + { + for (let entry in entriesToRecruit) + { + let entTemplateName = group.Entries[entry].data.Template; + maxAmount = Math.max(+(group.Entries[entry].data.Amount || 1), maxAmount); +// Resources + + // Construct something of a weighted list. + let chance = +(group.Entries[entry].data.Chance || 1); + for (let i = 0; i < chance; i++) + weightedListOfTemplates.push(entTemplateName); + } + } + else + for (let entry in entriesToRecruit) + { + let entTemplateName = group.Entries[entry].data.Template; + maxAmount += +(group.Entries[entry].data.Amount || 1); + } + + data = { + "GenericName": group.GenericName, + "SpecificName": group.SpecificName, + "Tooltip": group.Tooltip, + "technologies": requiredTechs, + "resources": maxNeededResources, + "maxAmount": maxAmount, + "icon": group.Icon, + "weightedListOfTemplates": weightedListOfTemplates, + "recruitableTemplates": recruitableTemplates + }; +warn(uneval(data)); + return data; +*/ +}; + GuiInterface.prototype.IsTechnologyResearched = function(player, data) { if (!data.tech) @@ -1919,6 +2006,7 @@ "GetMultipleEntityStates": 1, "GetAverageRangeForBuildings": 1, "GetTemplateData": 1, + "GetGroupTemplateData": 1, "IsTechnologyResearched": 1, "CheckTechnologyRequirements": 1, "GetStartedResearch": 1, Index: binaries/data/mods/public/simulation/components/ProductionQueue.js =================================================================== --- binaries/data/mods/public/simulation/components/ProductionQueue.js +++ binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -30,6 +30,48 @@ "" + "" + "" + + "" + + "" + + "" + + "tokens" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + "" + Resources.BuildSchema("nonNegativeDecimal", ["time"]) + ""; @@ -128,6 +170,48 @@ }; /* + * Returns list of groups that can be trained by this building. + * @return {string[]} - As the title says. + */ +ProductionQueue.prototype.GetGroupsList = function() +{ + return this.groupsList; +}; + +/* + * Calculate the list of groups that can be trained by this entity. + */ +ProductionQueue.prototype.CalculateGroupsList = function() +{ + this.groupsList = []; + if (!this.template.Groups) + return; + + let string = this.template.Groups._string; + if (!string) + return; + + // Replace the "{civ}" and "{native}" codes with the owner's civ ID and training entity's civ ID. + let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + let cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return; + + let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); + if (cmpIdentity) + string = string.replace(/\{native\}/g, cmpIdentity.GetCiv()); + + let groupsList = string.replace(/\{civ\}/g, cmpPlayer.GetCiv()).split(/\s+/); + + // Filter out disabled and invalid entities. + let disabledEntities = cmpPlayer.GetDisabledTemplates(); + groupsList = groupsList.filter(group => !disabledEntities[group] );//&& cmpTemplateManager.TemplateExists(group)); + + for (let groupName of groupsList) + this.groupsList.push(groupName); +}; + +/* * Returns list of technologies that can be researched by this building. */ ProductionQueue.prototype.GetTechnologiesList = function() @@ -516,7 +600,10 @@ cmpPlayer.UnBlockTraining(); } if (msg.to != INVALID_PLAYER) + { this.CalculateEntitiesList(); + this.CalculateGroupsList(); + } // Reset the production queue whenever the owner changes. // (This should prevent players getting surprised when they capture @@ -529,6 +616,7 @@ ProductionQueue.prototype.OnCivChanged = function() { this.CalculateEntitiesList(); + this.CalculateGroupsList(); }; ProductionQueue.prototype.OnDestroy = function() @@ -828,6 +916,7 @@ // if the disabled templates of the player is changed, // update the entities list so that this is reflected there this.CalculateEntitiesList(); + this.CalculateGroupsList(); }; Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue); Index: binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/structures/athen_civil_centre.xml @@ -13,6 +13,9 @@ units/{civ}_infantry_slinger_b units/{civ}_cavalry_javelinist_b + + units/compositions/group + structures/athenians/civil_centre.xml Index: binaries/data/mods/public/simulation/templates/units/compositions/group.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/templates/units/compositions/group.xml @@ -0,0 +1,32 @@ + + + + 1 + + 1.0 + 1.0 + 1.0 + 1.0 + + + + Some generic name. + Some specific name. + Some elaborate tooltip. + true + structures/civic_centre.png + + + + 5 + 1 + + + + 5 + 2 + + + + +