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,13 @@
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.identity || entState.identity.controllable)))
{
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
@@ -258,7 +258,8 @@
"selectionGroupName": cmpIdentity.GetSelectionGroupName(),
"canDelete": !cmpIdentity.IsUndeletable(),
"hasSomeFormation": cmpIdentity.HasSomeFormation(),
- "formations": cmpIdentity.GetFormationsList()
+ "formations": cmpIdentity.GetFormationsList(),
+ "controllable": cmpIdentity.IsControllable()
};
let cmpPosition = Engine.QueryInterface(ent, IID_Position);
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js
+++ binaries/data/mods/public/simulation/components/Identity.js
@@ -90,6 +90,11 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"";
@@ -102,6 +107,8 @@
this.phenotype = pickRandom(this.GetPossiblePhenotypes());
else
this.phenotype = "default";
+
+ this.controllable = "Controllable" in this.template ? this.template.Controllable == "true" : true;
};
Identity.prototype.HasSomeFormation = function()
@@ -184,4 +191,14 @@
return this.template.Undeletable == "true";
};
+Identity.prototype.IsControllable = function()
+{
+ return this.controllable;
+};
+
+Identity.prototype.SetControllable = function(bool)
+{
+ this.controllable = bool;
+};
+
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
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
@@ -202,7 +202,7 @@
// 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.AbleToMove())
+ if (!this.AbleToMove())
{
this.FinishOrder();
return;
@@ -239,13 +239,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();
@@ -262,7 +255,7 @@
"Order.Walk": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
+ if (!this.AbleToMove())
{
this.FinishOrder();
return;
@@ -288,7 +281,7 @@
"Order.WalkAndFight": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
+ if (!this.AbleToMove())
{
this.FinishOrder();
return;
@@ -315,7 +308,7 @@
"Order.WalkToTarget": function(msg) {
// Let players move captured domestic animals around
- if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
+ if (!this.AbleToMove())
{
this.FinishOrder();
return;
Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -545,6 +545,7 @@
"GetSelectionGroupName": function() { return "Selection Group Name"; },
"HasClass": function() { return true; },
"IsUndeletable": function() { return false; },
+ "IsControllable": function() { return true; },
"HasSomeFormation": function() { return false; },
"GetFormationsList": function() { return []; },
});
@@ -579,6 +580,7 @@
"canDelete": true,
"hasSomeFormation": false,
"formations": [],
+ "controllable": true,
},
"position": { "x": 1, "y": 2, "z": 3 },
"hitpoints": 50,
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
@@ -340,13 +340,6 @@
"research": function(player, cmd, data)
{
- if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
- {
- if (g_DebugCommands)
- warn("Invalid command: research building cannot be controlled by player "+player+": "+uneval(cmd));
- return;
- }
-
var cmpTechnologyManager = QueryOwnerInterface(cmd.entity, IID_TechnologyManager);
if (!cmpTechnologyManager.CanResearch(cmd.template))
{
@@ -362,13 +355,6 @@
"stop-production": function(player, cmd, data)
{
- if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
- {
- if (g_DebugCommands)
- warn("Invalid command: production building cannot be controlled by player "+player+": "+uneval(cmd));
- return;
- }
-
var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue);
if (queue)
queue.RemoveBatch(cmd.id);
@@ -876,6 +862,27 @@
}
/**
+ * Sends a GUI notification about entities that can't 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);
+ if (!cmpIdentity)
+ return;
+
+ 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.
*/
@@ -1661,12 +1668,20 @@
/**
* Check if player can control this entity
- * returns: true if the entity is valid and owned by the player
+ * returns: true if the entity is owned by the player and controllable
* or control all units is activated, else false
*/
function CanControlUnit(entity, player, controlAll)
{
- return IsOwnedByPlayer(player, entity) || controlAll;
+ let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ let canBeControlled = IsOwnedByPlayer(player, entity) &&
+ (!cmpIdentity || cmpIdentity.IsControllable()) ||
+ controlAll;
+
+ if (!canBeControlled)
+ notifyOrderFailure(entity, player);
+
+ return canBeControlled;
}
/**
@@ -1677,7 +1692,7 @@
*/
function CanControlUnitOrIsAlly(entity, player, controlAll)
{
- return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity) || controlAll;
+ return CanControlUnit(player, entity, controlAll) || IsOwnedByMutualAllyOfPlayer(player, entity);
}
/**
Index: binaries/data/mods/public/simulation/templates/template_unit_fauna.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_fauna.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_fauna.xml
@@ -12,6 +12,7 @@
-ConquestCritical Animal
gaia/fauna_generic.png
+ false