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