diff --git a/binaries/data/mods/public/globalscripts/l10n.js b/binaries/data/mods/public/globalscripts/l10n.js
index aaf07f0113..34b2497138 100644
--- a/binaries/data/mods/public/globalscripts/l10n.js
+++ b/binaries/data/mods/public/globalscripts/l10n.js
@@ -203,6 +203,11 @@ function translateObjectKeys(object, keys) {
}
}
+/**
+ * Function is used by the extract-messages tool.
+ * So it may only be used on a plain string,
+ * it won't have any effect on a calculated string.
+ */
function markForTranslation(message) {
return message;
}
diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index 7c285b183d..533d9650c7 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -285,7 +285,7 @@ function determineAction(x, y, fromMinimap)
// if two actions are possible, the first one is taken
// so the most specific should appear first
var actions = Object.keys(unitActions).slice();
- actions.sort(function(a, b) {return unitActions[a].specificness > unitActions[b].specificness;});
+ actions.sort(function(a, b) {return unitActions[a].specificness - unitActions[b].specificness;});
var actionInfo = undefined;
if (preSelectedAction != ACTION_NONE)
@@ -1195,7 +1195,7 @@ function handleMinimapEvent(target)
// @param buildTemplate Template name of the entity the user wants to build
function startBuildingPlacement(buildTemplate, playerState)
{
- if(getEntityLimitAndCount(playerState, buildTemplate)[2] == 0)
+ if(getEntityLimitAndCount(playerState, buildTemplate).canBeAddedCount == 0)
return;
// TODO: we should clear any highlight selection rings here. If the mouse was over an entity before going onto the GUI
@@ -1314,24 +1314,26 @@ function getBuildingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
function getEntityLimitAndCount(playerState, entType)
{
+ var r = {
+ "entLimit": undefined,
+ "entCount": undefined,
+ "entLimitChangers": undefined,
+ "canBeAddedCount": undefined
+ };
var template = GetTemplateData(entType);
var entCategory = null;
if (template.trainingRestrictions)
entCategory = template.trainingRestrictions.category;
else if (template.buildRestrictions)
entCategory = template.buildRestrictions.category;
- var entLimit = undefined;
- var entCount = undefined;
- var entLimitChangers = undefined;
- var canBeAddedCount = undefined;
if (entCategory && playerState.entityLimits[entCategory] != null)
{
- entLimit = playerState.entityLimits[entCategory];
- entCount = playerState.entityCounts[entCategory];
- entLimitChangers = playerState.entityLimitChangers[entCategory];
- canBeAddedCount = Math.max(entLimit - entCount, 0);
+ r.entLimit = playerState.entityLimits[entCategory] || Infinity;
+ r.entCount = playerState.entityCounts[entCategory] || 0;
+ r.entLimitChangers = playerState.entityLimitChangers[entCategory];
+ r.canBeAddedCount = Math.max(r.entLimit - r.entCount, 0);
}
- return [entLimit, entCount, canBeAddedCount, entLimitChangers];
+ return r;
}
// Add the unit shown at position to the training queue for all entities in the selection
@@ -1363,10 +1365,10 @@ function addTrainingToQueue(selection, trainEntType, playerState)
var appropriateBuildings = getBuildingsWhichCanTrainEntity(selection, trainEntType);
// Check trainEntType entity limit and count
- var [trainEntLimit, trainEntCount, canBeTrainedCount] = getEntityLimitAndCount(playerState, trainEntType)
+ var limits = getEntityLimitAndCount(playerState, trainEntType);
// Batch training possible if we can train at least 2 units
- var batchTrainingPossible = canBeTrainedCount == undefined || canBeTrainedCount > 1;
+ var batchTrainingPossible = limits.canBeAddedCount == undefined || limits.canBeAddedCount > 1;
var decrement = Engine.HotkeyIsPressed("selection.remove");
if (!decrement)
@@ -1398,8 +1400,8 @@ function addTrainingToQueue(selection, trainEntType, playerState)
if (batchTrainingCount <= 0)
inputState = INPUT_NORMAL;
}
- else if (canBeTrainedCount == undefined ||
- canBeTrainedCount > batchTrainingCount * appropriateBuildings.length)
+ else if (limits.canBeAddedCount == undefined ||
+ limits.canBeAddedCount > batchTrainingCount * appropriateBuildings.length)
{
if (Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(
template, batchTrainingCount + batchIncrementSize)))
@@ -1407,7 +1409,7 @@ function addTrainingToQueue(selection, trainEntType, playerState)
batchTrainingCount += batchIncrementSize;
}
- batchTrainingEntityAllowedCount = canBeTrainedCount;
+ batchTrainingEntityAllowedCount = limits.canBeAddedCount;
return;
}
// Otherwise start a new one
@@ -1426,7 +1428,7 @@ function addTrainingToQueue(selection, trainEntType, playerState)
inputState = INPUT_BATCHTRAINING;
batchTrainingEntities = selection;
batchTrainingType = trainEntType;
- batchTrainingEntityAllowedCount = canBeTrainedCount;
+ batchTrainingEntityAllowedCount = limits.canBeAddedCount;
batchTrainingCount = batchIncrementSize;
}
else
@@ -1434,8 +1436,8 @@ function addTrainingToQueue(selection, trainEntType, playerState)
// Non-batched - just create a single entity in each building
// (but no more than entity limit allows)
var buildingsForTraining = appropriateBuildings;
- if (trainEntLimit)
- buildingsForTraining = buildingsForTraining.slice(0, canBeTrainedCount);
+ if (limits.entLimit)
+ buildingsForTraining = buildingsForTraining.slice(0, limits.canBeAddedCount);
Engine.PostNetworkCommand({"type": "train", "template": trainEntType,
"count": 1, "entities": buildingsForTraining});
}
@@ -1462,27 +1464,27 @@ function getTrainingBatchStatus(playerState, entity, trainEntType, selection)
{
nextBatchTrainingCount = batchTrainingCount;
currentBatchTrainingCount = batchTrainingCount;
- var canBeTrainedCount = batchTrainingEntityAllowedCount;
+ var limits = {
+ "canBeAddedCount": batchTrainingEntityAllowedCount
+ };
}
else
{
- var [trainEntLimit, trainEntCount, canBeTrainedCount] =
- getEntityLimitAndCount(playerState, trainEntType);
- var batchSize = Math.min(canBeTrainedCount, batchIncrementSize);
+ var limits = getEntityLimitAndCount(playerState, trainEntType);
}
// We need to calculate count after the next increment if it's possible
- if (canBeTrainedCount == undefined ||
- canBeTrainedCount > nextBatchTrainingCount * appropriateBuildings.length)
+ if (limits.canBeAddedCount == undefined ||
+ limits.canBeAddedCount > nextBatchTrainingCount * appropriateBuildings.length)
nextBatchTrainingCount += batchIncrementSize;
// If training limits don't allow us to train batchTrainingCount in each appropriate building
// train as many full batches as we can and remainer in one more building.
var buildingsCountToTrainFullBatch = appropriateBuildings.length;
var remainderToTrain = 0;
- if (canBeTrainedCount !== undefined &&
- canBeTrainedCount < nextBatchTrainingCount * appropriateBuildings.length)
+ if (limits.canBeAddedCount !== undefined &&
+ limits.canBeAddedCount < nextBatchTrainingCount * appropriateBuildings.length)
{
- buildingsCountToTrainFullBatch = Math.floor(canBeTrainedCount / nextBatchTrainingCount);
- remainderToTrain = canBeTrainedCount % nextBatchTrainingCount;
+ buildingsCountToTrainFullBatch = Math.floor(limits.canBeAddedCount / nextBatchTrainingCount);
+ remainderToTrain = limits.canBeAddedCount % nextBatchTrainingCount;
}
return [buildingsCountToTrainFullBatch, nextBatchTrainingCount, remainderToTrain, currentBatchTrainingCount];
}
diff --git a/binaries/data/mods/public/gui/session/selection.js b/binaries/data/mods/public/gui/session/selection.js
index 076addf170..c678873709 100644
--- a/binaries/data/mods/public/gui/session/selection.js
+++ b/binaries/data/mods/public/gui/session/selection.js
@@ -61,6 +61,7 @@ EntityGroups.prototype.add = function(ents)
var templateName = entState.template;
var key = GetTemplateData(templateName).selectionGroupName || templateName;
+ // TODO ugly hack, just group them by player too.
// Prefix garrisoned unit's selection name with the player they belong to
var index = templateName.indexOf("&");
if (index != -1 && key.indexOf("&") == -1)
@@ -139,6 +140,23 @@ EntityGroups.prototype.getEntsByName = function(templateName)
return ents;
};
+/**
+ * get a list of entities grouped by templateName
+ */
+EntityGroups.prototype.getEntsGrouped = function()
+{
+ var templateNames = this.getTemplateNames();
+ var list = [];
+ for (var t of templateNames)
+ {
+ list.push({
+ "ents": this.getEntsByName(t),
+ "template": t,
+ });
+ }
+ return list;
+};
+
/**
* Gets all ents in every group except ones of the specified group
*/
diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js
index 31a7b168ba..5b57a69beb 100644
--- a/binaries/data/mods/public/gui/session/selection_details.js
+++ b/binaries/data/mods/public/gui/session/selection_details.js
@@ -428,8 +428,5 @@ function updateSelectionDetails()
{
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
- // Show panels
- supplementalDetailsPanel.hidden = false;
- commandsPanel.hidden = false;
}
}
diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js
index c66e2fe1fb..4d52a00eb0 100644
--- a/binaries/data/mods/public/gui/session/selection_panels.js
+++ b/binaries/data/mods/public/gui/session/selection_panels.js
@@ -1,18 +1,128 @@
/**
* Contains the layout and button settings per selection panel
*
- * addData is called first, and can be used to abort going any furter
- * by returning false. Else it should always return true.
+ * getItems returns a list of basic items used to fill the panel.
+ * This method is obligated. If the items list is empty, the panel
+ * won't be rendered.
+ *
+ * Then there's a loop over all items provided. In the loop,
+ * the item and some other standard data is added to a data object.
*
- * addData is used to add data to the data object that can be used in the
- * content setter functions.
+ * The standard data is
+ * var data = {
+ * "i": index
+ * "item": item coming from the getItems function
+ * "selection": list of currently selected items
+ * "playerState": playerState
+ * "unitEntState": first selected entity state
+ * "rowLength": rowLength
+ * "numberOfItems": number of items that will be processed
+ * "button": gui Button object
+ * "affordableMask": gui Unaffordable overlay
+ * "icon": gui Icon object
+ * "guiSelection": gui button Selection overlay
+ * "countDisplay": gui caption space
+ * };
+ *
+ * Then, addData is called, and can be used to abort the processing
+ * of the current item by returning false.
+ * It should return true if you want the panel to be filled.
+ *
+ * addData is used to add data to the data object on top
+ * (or instead of) the standard data.
+ * addData is not obligated, the function will just continue
+ * with the content setters if no addData is present.
+ *
+ * After the addData, all functions starting with "set" are called.
+ * These are used to set various parts of content.
*/
var g_SelectionPanels = {};
+// BARTER
+g_SelectionPanels.Barter = {
+ "maxNumberOfItems": 4,
+ "rowLength": 4,
+ "getItems": function(unitEntState, selection)
+ {
+ if (!unitEntState.barterMarket)
+ return [];
+ // ["food", "wood", "stone", "metal"]
+ return BARTER_RESOURCES;
+ },
+ "addData": function(data)
+ {
+ // data.item is the resource name in this case
+ data.button = {};
+ data.icon = {};
+ data.amount = {};
+ for (var a of BARTER_ACTIONS)
+ {
+ data.button[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Button["+data.i+"]");
+ data.icon[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Icon["+data.i+"]");
+ data.amount[a] = Engine.GetGUIObjectByName("unitBarter"+a+"Amount["+data.i+"]");
+ }
+ data.selection = Engine.GetGUIObjectByName("unitBarterSellSelection["+data.i+"]");
+ data.affordableMask = Engine.GetGUIObjectByName("unitBarterSellUnaffordable["+data.i+"]");
+
+ data.amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
+ if (Engine.HotkeyIsPressed("session.massbarter"))
+ data.amountToSell *= BARTER_BUNCH_MULTIPLIER;
+ data.isSelected = data.item == g_barterSell;
+ return true;
+ },
+ "setCountDisplay": function(data)
+ {
+ data.amount.Sell.caption = "-" + data.amountToSell;
+ var sellPrice = data.unitEntState.barterMarket.prices["sell"][g_barterSell];
+ var buyPrice = data.unitEntState.barterMarket.prices["buy"][data.item];
+ data.amount.Buy.caption = "+" + Math.round(sellPrice / buyPrice * data.amountToSell);
+ },
+ "setTooltip": function(data)
+ {
+ data.button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), {"resource": translate(data.item)});
+ data.button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), {"resource": translate(data.item)});
+ },
+ "setAction": function(data)
+ {
+ data.button.Sell.onPress = function() { g_barterSell = data.item; };
+ var exchangeResourcesParameters = {
+ "sell": g_barterSell,
+ "buy": data.item,
+ "amount": data.amountToSell
+ };
+ data.button.Buy.onPress = function() { exchangeResources(exchangeResourcesParameters); };
+ },
+ "setGraphics": function(data)
+ {
+ var grayscale = data.isSelected ? "grayscale:" : "";
+ data.button.Buy.hidden = data.isSelected;
+ data.button.Sell.hidden = false;
+ for each (var icon in data.icon)
+ icon.sprite = "stretched:"+grayscale+"session/icons/resources/" + data.item + ".png";
+
+ var neededRes = {};
+ neededRes[data.item] = data.amountToSell;
+ if (Engine.GuiInterfaceCall("GetNeededResources", neededRes))
+ data.affordableMask.hidden = false;
+ else
+ data.affordableMask.hidden = true;
+ data.selection.hidden = !data.isSelected;
+ },
+ "setPosition": function(data)
+ {
+ setPanelObjectPosition(data.button.Sell, data.i, data.rowLength);
+ setPanelObjectPosition(data.button.Buy, data.i + data.rowLength, data.rowLength);
+ },
+},
+
// COMMAND
g_SelectionPanels.Command = {
"maxNumberOfItems": 6,
+ "getItems": function(unitEntState)
+ {
+ return getEntityCommandsList(unitEntState)
+ },
"setTooltip": function(data)
{
if (data.item.tooltip)
@@ -20,12 +130,13 @@ g_SelectionPanels.Command = {
else
data.button.tooltip = toTitleCase(data.item.name);
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { data.item.callback ? data.item.callback(data.item) : performCommand(data.unitEntState.id, data.item.name); };
+ },
"setCountDisplay": function(data)
{
- var count = 0;
- if (data.item.name == "unload-all")
- count = data.garrisonGroups.getTotalCount();
- data.countDisplay.caption = count || "";
+ data.countDisplay.caption = data.item.count || "";
},
"setGraphics": function(data)
{
@@ -48,6 +159,11 @@ g_SelectionPanels.Command = {
// CONSTRUCTION
g_SelectionPanels.Construction = {
"maxNumberOfItems": 24,
+ "conflictsWith": ["Gate", "Pack", "Training"],
+ "getItems": function()
+ {
+ return getAllBuildableEntitiesFromSelection();
+ },
"addData": function(data)
{
data.entType = data.item;
@@ -60,8 +176,13 @@ g_SelectionPanels.Construction = {
var totalCost = multiplyEntityCosts(data.template, 1);
data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCost);
}
+ data.limits = getEntityLimitAndCount(data.playerState, data.entType);
return true;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function () { startBuildingPlacement(data.item, data.playerState); };
+ },
"setTooltip": function(data)
{
var tooltip = getEntityNamesFormatted(data.template);
@@ -73,13 +194,7 @@ g_SelectionPanels.Construction = {
tooltip += "\n" + getEntityCostTooltip(data.template);
tooltip += getPopulationBonusTooltip(data.template);
- var limits = getEntityLimitAndCount(data.playerState, data.entType);
- data.entLimit = limits[0];
- data.entCount = limits[1];
- data.canBeAddedCount = limits[2];
- data.entLimitChangers = limits[3];
-
- tooltip += formatLimitString(data.entLimit, data.entCount, data.entLimitChangers);
+ tooltip += formatLimitString(data.limits.entLimit, data.limits.entCount, data.limits.entLimitChangers);
if (!data.technologyEnabled)
{
var techName = getEntityNames(GetTechnologyData(data.template.requiredTechnology));
@@ -93,7 +208,7 @@ g_SelectionPanels.Construction = {
"setGraphics": function(data)
{
var grayscale = "";
- if (!data.technologyEnabled || data.canBeAddedCount == 0)
+ if (!data.technologyEnabled || data.limits.canBeAddedCount == 0)
{
data.button.enabled = false;
grayscale = "grayscale:";
@@ -115,6 +230,13 @@ g_SelectionPanels.Construction = {
g_SelectionPanels.Formation = {
"maxNumberOfItems": 16,
"rowLength": 4,
+ "conflictsWith": ["Garrison"],
+ "getItems": function(unitEntState)
+ {
+ if (!hasClass(unitEntState, "Unit") || hasClass(unitEntState, "Animal"))
+ return [];
+ return Engine.GuiInterfaceCall("GetAvailableFormations");
+ },
"addData": function(data)
{
data.formationInfo = Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", {"templateName": data.item});
@@ -125,9 +247,13 @@ g_SelectionPanels.Formation = {
});
return true;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { performFormation(data.unitEntState.id, data.item); };
+ },
"setTooltip": function(data)
{
- var tooltip = translate(data.formationInfo.name);
+ var tooltip = translate(data.formationInfo.name);
if (!data.formationOk && data.formationInfo.tooltip)
tooltip += "\n" + "[color=\"red\"]" + translate(data.formationInfo.tooltip) + "[/color]";
data.button.tooltip = tooltip;
@@ -145,16 +271,33 @@ g_SelectionPanels.Formation = {
g_SelectionPanels.Garrison = {
"maxNumberOfItems": 12,
"rowLength": 4,
+ "getItems": function(unitEntState, selection)
+ {
+ if (!unitEntState.garrisonHolder)
+ return [];
+ var groups = new EntityGroups();
+ for (var ent of selection)
+ {
+ var state = GetEntityState(ent);
+ if (state.garrisonHolder)
+ groups.add(state.garrisonHolder.entities)
+ }
+ return groups.getEntsGrouped();
+ },
"addData": function(data)
{
- data.entType = data.item;
+ data.entType = data.item.template;
data.template = GetTemplateData(data.entType);
if (!data.template)
return false;
data.name = getEntityNames(data.template);
- data.count = data.garrisonGroups.getCount(data.item);
+ data.count = data.item.ents.length;
return true;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { unloadTemplate(data.item.template); };
+ },
"setTooltip": function(data)
{
var tooltip = sprintf(translate("Unload %(name)s"), { name: data.name }) + "\n";
@@ -168,7 +311,7 @@ g_SelectionPanels.Garrison = {
"setGraphics": function(data)
{
var grayscale = "";
- var ents = data.garrisonGroups.getEntsByName(data.item);
+ var ents = data.item.ents;
var entplayer = GetEntityState(ents[0]).player;
data.button.sprite = "colour: " + rgbToGuiColor(g_Players[entplayer].color);
@@ -188,6 +331,70 @@ g_SelectionPanels.Garrison = {
// GATE
g_SelectionPanels.Gate = {
"maxNumberOfItems": 8,
+ "conflictsWith": ["Construction", "Pack", "Training"],
+ "getItems": function(unitEntState, selection)
+ {
+ if (unitEntState.foundation)
+ return [];
+ if (!hasClass(unitEntState, "LongWall") && !unitEntState.gate)
+ return [];
+ // Allow long wall pieces to be converted to gates
+ var longWallTypes = {};
+ var walls = [];
+ var gates = [];
+ for (var i in selection)
+ {
+ var state = GetEntityState(selection[i]);
+ if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
+ {
+ var gateTemplate = getWallGateTemplate(state.id);
+ if (gateTemplate)
+ {
+ var tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
+ if (!tooltipString)
+ {
+ warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
+ tooltipString = "";
+ }
+ walls.push({
+ "tooltip": translate(tooltipString),
+ "template": gateTemplate,
+ "callback": function (item) { transformWallToGate(item.template); }
+ });
+ }
+
+ // We only need one entity per type.
+ longWallTypes[state.template] = true;
+ }
+ else if (state.gate && !gates.length)
+ {
+ gates.push({
+ "gate": state.gate,
+ "tooltip": translate("Lock Gate"),
+ "locked": true,
+ "callback": function (item) { lockGate(item.locked); }
+ });
+ gates.push({
+ "gate": state.gate,
+ "tooltip": translate("Unlock Gate"),
+ "locked": false,
+ "callback": function (item) { lockGate(item.locked); }
+ });
+ }
+ // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
+ else if (state.gate && state.gate.locked != gates[0].gate.locked)
+ for (var j = 0; j < gates.length; ++j)
+ delete gates[j].gate.locked;
+ }
+
+ // Place wall conversion options after gate lock/unlock icons.
+ var items = gates.concat(walls);
+ return items;
+ },
+ "setAction": function(data)
+ {
+ data.button.onPress = function() {data.item.callback(data.item); };
+ },
"setTooltip": function(data)
{
var tooltip = data.item.tooltip;
@@ -229,7 +436,7 @@ g_SelectionPanels.Gate = {
var template = GetTemplateData(data.item.template);
if (!template)
return;
- gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
+ gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
data.guiSelection.hidden = true;
}
@@ -240,6 +447,48 @@ g_SelectionPanels.Gate = {
// PACK
g_SelectionPanels.Pack = {
"maxNumberOfItems": 8,
+ "conflictsWith": ["Construction", "Gate", "Training"],
+ "getItems": function(unitEntState, selection)
+ {
+ if (!unitEntState.pack)
+ return [];
+ var checks = {};
+ for (var ent of selection)
+ {
+ var state = GetEntityState(ent);
+ if (!state.pack)
+ continue;
+ if (state.pack.progress == 0)
+ {
+ if (!state.pack.packed)
+ checks.packButton = true;
+ else if (state.pack.packed)
+ checks.unpackButton = true;
+ }
+ else
+ {
+ // Already un/packing - show cancel button
+ if (!state.pack.packed)
+ checks.packCancelButton = true;
+ else if (state.pack.packed)
+ checks.unpackCancelButton = true;
+ }
+ }
+ var items = [];
+ if (checks.packButton)
+ items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
+ if (checks.unpackButton)
+ items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
+ if (checks.packCancelButton)
+ items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
+ if (checks.unpackCancelButton)
+ items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
+ return items;
+ },
+ "setAction": function(data)
+ {
+ data.button.onPress = function() {data.item.callback(data.item); };
+ },
"setTooltip": function(data)
{
data.button.tooltip = data.item.tooltip;
@@ -258,6 +507,20 @@ g_SelectionPanels.Pack = {
// QUEUE
g_SelectionPanels.Queue = {
"maxNumberOfItems": 16,
+ "getItems": function(unitEntState)
+ {
+ if (!unitEntState.production)
+ return [];
+ return unitEntState.production.queue;
+ },
+ "resizePanel": function(numberOfItems, rowLength)
+ {
+ var numRows = Math.ceil(numberOfItems / rowLength);
+ var panel = Engine.GetGUIObjectByName("unitQueuePanel");
+ var size = panel.size;
+ size.top = (UNIT_PANEL_BASE - ((numRows-1)*UNIT_PANEL_HEIGHT));
+ panel.size = size;
+ },
"addData": function(data)
{
// differentiate between units and techs
@@ -275,6 +538,10 @@ g_SelectionPanels.Queue = {
data.progress = Math.round(data.item.progress*100) + "%";
return data.template;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { removeFromProductionQueue(data.unitEntState.id, data.item.id); };
+ },
"setTooltip": function(data)
{
var tooltip = getEntityNames(data.template);
@@ -313,6 +580,15 @@ g_SelectionPanels.Queue = {
// RESEARCH
g_SelectionPanels.Research = {
"maxNumberOfItems": 8,
+ "getItems": function(unitEntState, selection)
+ {
+ if (!unitEntState.production)
+ return [];
+ // TODO 8 is the row lenght, make variable
+ if (getNumberOfRightPanelButtons() > 8 && selection.length > 1)
+ return [];
+ return unitEntState.production.technologies;
+ },
"hideItem": function(i, rowLength) // called when no item is found
{
Engine.GetGUIObjectByName("unitResearchButton["+i+"]").hidden = true;
@@ -393,7 +669,7 @@ g_SelectionPanels.Research = {
data.button[i].tooltip = tooltip;
}
},
- "setActions": function(data)
+ "setAction": function(data)
{
for (var i in data.entType)
{
@@ -401,7 +677,9 @@ g_SelectionPanels.Research = {
var others = Object.keys(data.template);
others.splice(i, 1);
var button = data.button[i];
- button.onpress = (function(e){ return function() { data.callback(e) } })(data.entType[i]);
+ // as we're in a loop, we need to limit the scope with a closure
+ // else the last value of the loop will be taken, rather than the current one
+ button.onpress = (function(template) { return function () { addResearchToQueue(data.unitEntState.id, template); }; })(data.entType[i]);
// on mouse enter, show a cross over the other icons
button.onmouseenter = (function(others, icons) {
return function() {
@@ -445,7 +723,7 @@ g_SelectionPanels.Research = {
if (data.template[i].icon)
data.icon[i].sprite = "stretched:" + grayscale + "session/portraits/" + data.template[i].icon;
}
- for (var button of data.buttonsToHide)
+ for (var button of data.buttonsToHide)
button.hidden = true;
// show the tech connector
data.pair.hidden = data.item.pair == null;
@@ -462,6 +740,12 @@ g_SelectionPanels.Research = {
g_SelectionPanels.Selection = {
"maxNumberOfItems": 16,
"rowLength": 4,
+ "getItems": function(unitEntState, selection)
+ {
+ if (selection.length < 2)
+ return [];
+ return g_Selection.groups.getTemplateNames();
+ },
"addData": function(data)
{
data.entType = data.item;
@@ -480,10 +764,10 @@ g_SelectionPanels.Selection = {
{
data.countDisplay.caption = data.count || "";
},
- "setActions": function(data)
+ "setAction": function(data)
{
- data.button.onpressright = (function(e){return function() {data.callback(e, true) } })(data.item);
- data.button.onpress = (function(e){ return function() {data.callback(e, false) } })(data.item);
+ data.button.onpressright = function() { changePrimarySelectionGroup(data.item, true); };
+ data.button.onpress = function() { changePrimarySelectionGroup(data.item, false); };
},
"setGraphics": function(data)
{
@@ -495,6 +779,12 @@ g_SelectionPanels.Selection = {
// STANCE
g_SelectionPanels.Stance = {
"maxNumberOfItems": 5,
+ "getItems": function(unitEntState)
+ {
+ if (!unitEntState.unitAI || !hasClass(unitEntState, "Unit") || hasClass(unitEntState, "Animal"))
+ return [];
+ return unitEntState.unitAI.possibleStances;
+ },
"addData": function(data)
{
data.stanceSelected = Engine.GuiInterfaceCall("IsStanceSelected", {
@@ -503,6 +793,10 @@ g_SelectionPanels.Stance = {
});
return true;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { performStance(data.unitEntState, data.item); };
+ },
"setTooltip": function(data)
{
data.button.tooltip = getStanceDisplayName(data.item);
@@ -517,6 +811,11 @@ g_SelectionPanels.Stance = {
// TRAINING
g_SelectionPanels.Training = {
"maxNumberOfItems": 24,
+ "conflictsWith": ["Construction", "Gate", "Pack"],
+ "getItems": function()
+ {
+ return getAllTrainableEntitiesFromSelection();
+ },
"addData": function(data)
{
data.entType = data.item;
@@ -542,6 +841,10 @@ g_SelectionPanels.Training = {
return true;
},
+ "setAction": function(data)
+ {
+ data.button.onPress = function() { addTrainingToQueue(data.selection, data.item, data.playerState); };
+ },
"setCountDisplay": function(data)
{
var count = "";
@@ -574,14 +877,9 @@ g_SelectionPanels.Training = {
tooltip += "\n" + getEntityCostTooltip(data.template, data.trainNum, data.unitEntState.id);
- // TODO make the getEntityLimitAndCount method return something nicer than an array
- var limits = getEntityLimitAndCount(data.playerState, data.entType);
- data.entLimit = limits[0];
- data.entCount = limits[1];
- data.canBeAddedCount = limits[2];
- data.entLimitChangers = limits[3];
+ data.limits = getEntityLimitAndCount(data.playerState, data.entType);
- tooltip += formatLimitString(data.entLimit, data.entCount, data.entLimitChangers);
+ tooltip += formatLimitString(data.limits.entLimit, data.limits.entCount, data.limits.entLimitChangers);
if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true")
{
if (data.template.health)
@@ -608,3 +906,30 @@ g_SelectionPanels.Training = {
"setGraphics": g_SelectionPanels.Construction.setGraphics,
};
+
+
+/**
+ * If two panels need the same space, so they collide,
+ * the one appearing first in the order is rendered.
+ *
+ * Note that the panel needs to appear in the list to get rendered.
+ */
+var g_PanelsOrder = [
+ // LEFT PANE
+ "Barter", // must always be visible on markets
+ "Garrison", // more important than Formation, as you want to see the garrisoned units in ships
+ "Formation",
+ "Stance", // normal together with formation
+
+ // RIGHT PANE
+ "Gate", // must always be shown on gates
+ "Pack", // must always be shown on packable entities
+ "Training", // lets hope training and construction never happen together
+ "Construction",
+ "Research", // normal together with training
+
+ // UNIQUE PANES (importance doesn't matter)
+ "Command",
+ "Queue",
+ "Selection",
+];
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index 3f89770a66..3e97c5b96b 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -860,21 +860,18 @@
-