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
@@ -135,7 +135,10 @@
let playerStates = GetSimState().players;
let playerState = playerStates[Engine.GetPlayerID()];
- if (g_IsObserver || entStates.every(entState => controlsPlayer(entState.player)))
+ // Always show selection.
+ setupUnitPanel("Selection", entStates, playerStates[entStates[0].player]);
+
+ if (g_IsObserver || entStates.every(entState => controlsPlayer(entState.player) && (!entState.unitAI || entState.unitAI.isControllable)))
{
for (let guiName of g_PanelsOrder)
{
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
@@ -355,6 +355,7 @@
"canPatrol": cmpUnitAI.CanPatrol(),
"selectableStances": cmpUnitAI.GetSelectableStances(),
"isIdle": cmpUnitAI.IsIdle(),
+ "isControllable": cmpUnitAI.IsControllable()
};
let cmpGuard = Engine.QueryInterface(ent, IID_Guard);
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
@@ -24,6 +24,9 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -187,8 +190,8 @@
// Called when being told to walk as part of a formation
"Order.FormationWalk": function(msg) {
- // Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ // Turrets can't be ordered to walk.
+ if (this.IsTurret())
{
this.FinishOrder();
return;
@@ -234,13 +237,6 @@
// (these will switch the unit out of formation mode)
"Order.Stop": function(msg) {
- // We have no control over non-domestic animals.
- if (this.IsAnimal() && !this.IsDomestic())
- {
- this.FinishOrder();
- return;
- }
-
// Stop moving immediately.
this.StopMoving();
this.FinishOrder();
@@ -254,8 +250,8 @@
},
"Order.Walk": function(msg) {
- // Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ // Turrets can't be ordered to walk.
+ if (this.IsTurret())
{
this.FinishOrder();
return;
@@ -280,8 +276,8 @@
},
"Order.WalkAndFight": function(msg) {
- // Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ // Turrets can't be ordered to walk.
+ if (this.IsTurret())
{
this.FinishOrder();
return;
@@ -307,8 +303,8 @@
"Order.WalkToTarget": function(msg) {
- // Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
+ // Turrets can't be ordered to walk.
+ if (this.IsTurret())
{
this.FinishOrder();
return;
@@ -455,7 +451,8 @@
},
"Order.Patrol": function(msg) {
- if (this.IsAnimal() || this.IsTurret())
+ // Turrets can't be ordered to walk.
+ if (this.IsTurret())
{
this.FinishOrder();
return;
@@ -3232,6 +3229,7 @@
this.isGarrisoned = false;
this.isIdle = false;
this.finishedOrder = false; // used to find if all formation members finished the order
+ this.isControllable = this.template.Controllable == "true";
this.heldPosition = undefined;
@@ -3340,6 +3338,20 @@
return (state == "WALKING");
};
+UnitAI.prototype.IsControllable = function()
+{
+ return this.isControllable;
+};
+
+/**
+ * Set whether the entity is controllable.
+ * @param {boolean} bool - Q.e.d.
+ */
+UnitAI.prototype.SetControllable = function(bool)
+{
+ this.isControllable = bool;
+};
+
/**
* Return true if the current order is WalkAndFight or Patrol.
*/
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
@@ -68,7 +68,7 @@
"IsInTargetRange": (ent, target, min, max, opposite) => true
});
- var unitAI = ConstructComponent(unit, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" });
+ var unitAI = ConstructComponent(unit, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive", "Controllable": "true" });
AddMock(unit, IID_Identity, {
GetClassesList: function() { return []; },
@@ -128,7 +128,7 @@
});
var controllerFormation = ConstructComponent(controller, "Formation", {"FormationName": "Line Closed", "FormationShape": "square", "ShiftRows": "false", "SortingClasses": "", "WidthDepthRatio": 1, "UnitSeparationWidthMultiplier": 1, "UnitSeparationDepthMultiplier": 1, "SpeedMultiplier": 1, "Sloppyness": 0});
- var controllerAI = ConstructComponent(controller, "UnitAI", { "FormationController": "true", "DefaultStance": "aggressive" });
+ var controllerAI = ConstructComponent(controller, "UnitAI", { "FormationController": "true", "DefaultStance": "aggressive", "Controllable": "true" });
AddMock(controller, IID_Position, {
JumpTo: function(x, z) { this.x = x; this.z = z; },
@@ -225,7 +225,7 @@
units.push(unit + i);
- var unitAI = ConstructComponent(unit + i, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" });
+ var unitAI = ConstructComponent(unit + i, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive", "Controllable": "true" });
AddMock(unit + i, IID_Identity, {
GetClassesList: function() { return []; },
@@ -277,7 +277,7 @@
});
var controllerFormation = ConstructComponent(controller, "Formation", {"FormationName": "Line Closed", "FormationShape": "square", "ShiftRows": "false", "SortingClasses": "", "WidthDepthRatio": 1, "UnitSeparationWidthMultiplier": 1, "UnitSeparationDepthMultiplier": 1, "SpeedMultiplier": 1, "Sloppyness": 0});
- var controllerAI = ConstructComponent(controller, "UnitAI", { "FormationController": "true", "DefaultStance": "aggressive" });
+ var controllerAI = ConstructComponent(controller, "UnitAI", { "FormationController": "true", "DefaultStance": "aggressive", "Controllable": "true" });
AddMock(controller, IID_Position, {
GetTurretParent: function() { return INVALID_ENTITY; },
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
@@ -877,6 +877,25 @@
}
/**
+ * Sends a GUI notification about entities that can't be controlled.
+ * @param {number} entity - The entity-ID of the unit that cannot be controlled.
+ * @param {number} player - The player-ID of the player that needs to receive this message.
+ */
+function notifyOrderFailure(entity, player)
+{
+ let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGUIInterface.PushNotification({
+ "type": "text",
+ "players": [player],
+ "message": sprintf(markForTranslation("Some %(unit)s can't be controlled."), {
+ "unit": cmpIdentity.GetGenericName()
+ }),
+ "translateMessage": true
+ });
+}
+
+/**
* Get some information about the formations used by entities.
* The entities must have a UnitAI component.
*/
@@ -1660,12 +1679,20 @@
/**
* Check if player can control this entity
- * returns: true if the entity is valid and owned by the player
- * or control all units is activated, else false
+ * @param {number} entity - The ID of the entity.
+ * @param {number} player - The ID of the player.
+ * @param {boolean} controlAll - Whether the controlAll-cheat is enabled.
+ * @return {boolean} - Whether the entity can be controlled.
*/
function CanControlUnit(entity, player, controlAll)
{
- return IsOwnedByPlayer(player, entity) || controlAll;
+ let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
+ let CanBeControlled = IsOwnedByPlayer(player, entity) &&
+ (cmpUnitAI && cmpUnitAI.IsControllable() || !cmpUnitAI) ||
+ controlAll;
+ if (!CanBeControlled)
+ notifyOrderFailure(entity, player);
+ return CanBeControlled;
}
/**
Index: binaries/data/mods/public/simulation/templates/template_bird.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_bird.xml
+++ binaries/data/mods/public/simulation/templates/template_bird.xml
@@ -26,6 +26,7 @@
false
false
false
+ false
1000.0
10.0
10000
Index: binaries/data/mods/public/simulation/templates/template_formation.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_formation.xml
+++ binaries/data/mods/public/simulation/templates/template_formation.xml
@@ -64,6 +64,7 @@
12.0
true
true
+ true
true
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml
+++ binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -119,6 +119,7 @@
false
true
true
+ true
false