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
@@ -48,6 +48,7 @@
{
this.groups = {};
this.ents = {};
+ this.selectedUniqueTemplates = [];
};
EntityGroups.prototype.add = function(ents)
@@ -208,6 +209,23 @@
};
/**
+ * Get a list of the unique templates names.
+ * @return - An array of template names which occur one or more times in the selection.
+ */
+EntitySelection.prototype.getUniqueTemplateNames = function()
+{
+ let templateNames = [];
+
+ for (let ent in this.selected)
+ {
+ let entState = GetEntityState(+ent);
+ if (entState && templateNames.indexOf(entState.template) == -1)
+ templateNames.push(entState.template);
+ }
+ return templateNames;
+};
+
+/**
* Update the selection to take care of changes (like units that have been killed)
*/
EntitySelection.prototype.update = function()
@@ -354,6 +372,7 @@
_setStatusBars(this.toList(), false);
_setMotionOverlay(this.toList(), false);
this.selected = {};
+ this.selectedUniqueTemplates = [];
this.groups.reset();
this.onChange();
};
@@ -445,6 +464,7 @@
g_canMoveIntoFormation = {};
g_allBuildableEntities = undefined;
g_allTrainableEntities = undefined;
+ g_Selection.selectedUniqueTemplates = g_Selection.getUniqueTemplateNames();
}
/**
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
@@ -921,6 +921,45 @@
}
};
+g_SelectionPanels.PrefAttack = {
+ "getMaxNumberOfItems": function()
+ {
+ return 5;
+ },
+ "getItems": function(entStates)
+ {
+ if (entStates.some(state => !state.unitAI || !hasClass(state, "Unit") || !state.attack))
+ return [];
+
+ let items = ["Default"];
+ for (let template of g_Selection.selectedUniqueTemplates)
+ items = items.concat(Object.keys(GetTemplateData(template).attack).filter(
+ type => items.indexOf(type) == -1 && type != "Slaughter"
+ ));
+
+ // No point in showing only "Default" and one extra attack type.
+ return items.length > 2 ? items : "";
+ },
+ "setupButton": function(data)
+ {
+ let entIds = data.unitEntStates.map(state => state.id);
+ data.button.onPress = function() { performPrefAttack(entIds, data.item); };
+
+ data.button.tooltip = getPrefAttackDisplay(data.item).name + "\n" +
+ "[font=\"sans-13\"]" + getPrefAttackDisplay(data.item).tooltip + "[/font]";
+
+ data.guiSelection.hidden = !Engine.GuiInterfaceCall("IsPrefAttackSelected", {
+ "ents": entIds,
+ "prefAttack": data.item
+ });
+ data.icon.sprite = "stretched:session/icons/attacks/" + data.item.toLowerCase() + ".png";
+ data.button.enabled = controlsPlayer(data.player);
+
+ setPanelObjectPosition(data.button, data.i, data.rowLength);
+ return true;
+ }
+};
+
g_SelectionPanels.Training = {
"getMaxNumberOfItems": function()
{
@@ -1162,7 +1201,8 @@
"Garrison", // More important than Formation, as you want to see the garrisoned units in ships
"Alert",
"Formation",
- "Stance", // Normal together with formation
+ "Stance", // Normal together with formation and preferred attack.
+ "PrefAttack", // Normally together with formation and stance.
// RIGHT PANE
"Gate", // Must always be shown on gates
Index: binaries/data/mods/public/gui/session/selection_panels_helpers.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_panels_helpers.js
+++ binaries/data/mods/public/gui/session/selection_panels_helpers.js
@@ -87,6 +87,19 @@
}
}
+function getPrefAttackDisplay(name)
+{
+ let text = {};
+ text.name = sprintf(translateWithContext("attack type", "%(name)s"), {
+ "name": name
+ });
+ text.tooltip = name == "Default" ? translate("Let the unit decide.") :
+ sprintf(translate("Prefer %(name)s attack."), {
+ "name": translateWithContext("attack type", name)
+ });
+ return text;
+}
+
/**
* Format entity count/limit message for the tooltip
*/
@@ -290,6 +303,18 @@
});
}
+function performPrefAttack(entities, prefAttackType)
+{
+ if (!entities)
+ return;
+
+ Engine.PostNetworkCommand({
+ "type": "prefAttackType",
+ "entities": entities,
+ "name": prefAttackType
+ });
+}
+
function lockGate(lock)
{
Engine.PostNetworkCommand({
Index: binaries/data/mods/public/gui/session/selection_panels_left/attack_panel.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/session/selection_panels_left/attack_panel.xml
@@ -0,0 +1,16 @@
+
+
+
Index: binaries/data/mods/public/gui/session/session.xml
===================================================================
--- binaries/data/mods/public/gui/session/session.xml
+++ binaries/data/mods/public/gui/session/session.xml
@@ -120,7 +120,7 @@
{
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ return cmpUnitAI && cmpUnitAI.GetPrefAttack() == data.prefAttack;
+ });
+};
+
GuiInterface.prototype.GetAllBuildableEntities = function(player, cmd)
{
let buildableEnts = [];
@@ -1934,6 +1942,7 @@
"IsFormationSelected": 1,
"GetFormationInfoFromTemplate": 1,
"IsStanceSelected": 1,
+ "IsPrefAttackSelected": 1,
"UpdateDisplayedPlayerColors": 1,
"SetSelectionHighlight": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -3212,6 +3212,7 @@
this.lastHealed = undefined;
this.SetStance(this.template.DefaultStance);
+ this.prefAttack = "Default";
};
UnitAI.prototype.IsTurret = function()
@@ -4561,9 +4562,15 @@
UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
{
- var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
+
+ // First try to use a preferred attack type.
+ let prefAttack = this.GetPrefAttack();
+ if (prefAttack != "Default" && cmpAttack.CanAttack(target, [prefAttack]))
+ return prefAttack;
+
return cmpAttack.GetBestAttackAgainst(target, allowCapture);
};
@@ -5632,6 +5639,30 @@
return this.stance;
};
+UnitAI.prototype.SetPrefAttack = function(attackType)
+{
+ if (this.GetSelectableAttacks().indexOf(attackType) != -1)
+ {
+ this.prefAttack = attackType;
+ Engine.PostMessage(this.entity, MT_UnitPrefAttackChanged, { "to": this.prefAttack });
+
+ // Reset the range queries, since the range depends on attack type.
+ this.SetupRangeQueries();
+ }
+};
+
+UnitAI.prototype.GetSelectableAttacks = function()
+{
+ let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+ if (cmpAttack)
+ return cmpAttack.GetAttackTypes().concat(["Default"]);
+};
+
+UnitAI.prototype.GetPrefAttack = function()
+{
+ return this.prefAttack;
+};
+
/*
* Make the unit walk at its normal pace.
*/
Index: binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
+++ binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
@@ -38,3 +38,10 @@
* sent from UnitAI whenever a pickup is aborted.
*/
Engine.RegisterMessageType("PickupCanceled");
+
+/**
+ * Message of the form { "to": string }
+ * where "to" value is a UnitAI preferred attack type,
+ * sent from UnitAI whenever the unit's pref. attack changes.
+ */
+Engine.RegisterMessageType("UnitPrefAttackChanged");
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -627,6 +627,16 @@
}
},
+ "prefAttackType": function(player, cmd, data)
+ {
+ for (let ent of data.entities)
+ {
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (cmpUnitAI)
+ cmpUnitAI.SetPrefAttack(cmd.name);
+ }
+ },
+
"lock-gate": function(player, cmd, data)
{
for (let ent of data.entities)