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
+
+
+ units/athen_black_cloak
+ 5
+ 1
+
+
+ units/athen_champion_infantry
+ 5
+ 2
+
+
+
+
+