Index: binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- binaries/data/mods/public/gui/session/unit_actions.js +++ binaries/data/mods/public/gui/session/unit_actions.js @@ -1091,6 +1091,33 @@ "specificness": 11, }, + // This is a "fake" action to show a failure cursor + // when only uncontrollable entities are selected. + "uncontrollable": + { + "execute": function(target, action, selection, queued) + { + return true; + }, + "actionCheck": function(target, selection) + { + // Only show this action if all entities are marked uncontrollable. + let playerState = g_SimState.players[g_ViewedPlayer]; + if (playerState && playerState.controlsAll && selection.some(ent => { + let entState = GetEntityState(ent); + return entState && entState.identity && entState.identity.controllable; + })) + return false; + + return { + "type": "none", + "cursor": "cursor-no", + "tooltip": translatePlural("This entity cannot be controlled.", "These entities cannot be controlled.", selection.length) + }; + }, + "specificness": 100, + }, + "none": { "execute": function(target, action, selection, queued) @@ -1586,14 +1613,19 @@ function getActionInfo(action, target, selection) { - let simState = GetSimState(); - - // If the selection doesn't exist, no action - if (!GetEntityState(selection[0])) + if (!selection || !selection.length || !GetEntityState(selection[0])) return { "possible": false }; if (!target) // TODO move these non-target actions to an object like unit_actions.js { + // Ensure one entity at least is controllable. + let playerState = g_SimState.players[g_ViewedPlayer]; + if (playerState && !playerState.controlsAll && !selection.some(ent => { + let entState = GetEntityState(ent); + return entState && entState.identity && entState.identity.controllable; + })) + return { "possible": false }; + if (action == "set-rallypoint") { let cursor = ""; @@ -1630,6 +1662,9 @@ if (!targetState) return { "possible": false }; + let simState = GetSimState(); + let playerState = g_SimState.players[g_ViewedPlayer]; + // Check if any entities in the selection can do some of the available actions with target for (let entityID of selection) { @@ -1637,6 +1672,9 @@ if (!entState) continue; + if (playerState && !playerState.controlsAll && !entState.identity.controllable) + continue; + if (g_UnitActions[action] && g_UnitActions[action].getActionInfo) { let r = g_UnitActions[action].getActionInfo(entState, targetState, simState); 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)) || + playerState.controlsAll) { 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(controllability) +{ + this.controllable = controllability; +}; + 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 @@ -208,8 +208,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; @@ -246,13 +245,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(); @@ -268,8 +260,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; @@ -294,8 +285,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; @@ -321,8 +311,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("%(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 Index: binaries/data/mods/public/simulation/templates/template_unit_fauna_herd_domestic.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_fauna_herd_domestic.xml +++ binaries/data/mods/public/simulation/templates/template_unit_fauna_herd_domestic.xml @@ -2,6 +2,7 @@ Domestic + true domestic