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 @@ -1457,10 +1457,11 @@ if (entState.resourceSupply && entState.resourceSupply.killBeforeGather) return translate("The entity has to be killed before it can be gathered from"); + // Keep in sync with Controllability.js-component. if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2) return translate("You cannot destroy this entity as you own less than half the capture points"); - if (!entState.identity.canDelete) + if (entState.controllability && !entState.controllability.Deletable) return translate("This entity is undeletable"); return false; 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.controllability || entState.controllability.Controllable))) { for (let guiName of g_PanelsOrder) { Index: binaries/data/mods/public/simulation/components/Capturable.js =================================================================== --- binaries/data/mods/public/simulation/components/Capturable.js +++ binaries/data/mods/public/simulation/components/Capturable.js @@ -160,6 +160,7 @@ Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); var owner = cmpOwnership.GetOwner(); + if (owner == INVALID_PLAYER || this.cp[owner] > 0) return; Index: binaries/data/mods/public/simulation/components/Controllability.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/Controllability.js @@ -0,0 +1,65 @@ +class Controllability +{ + get Schema() + { + return "Deals with the controllability of structures and units." + + "" + + "true" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + } + + /** + * The initialisation of the component. + */ + Init() + { + this.controllability = {}; + for (let entry in this.template) + this.controllability[entry] = this.template[entry] == "true"; + } + + /** + * Whether the entity is controllable in some way. + * + * @param {string} type - The type to check controllability for. + * + * @return {boolean} - Whether the entity can be given the order. + */ + GetControllability(type) + { + return this.controllability; + } + + /** + * Whether the entity is controllable in some way. + * + * @param {string[]} types - The types to set the controllability for. + * @param {boolean} bool - The boolean to set the controllability to. + */ + SetControllability(types, bool) + { + for (let type of types) + this.controllability[type] = bool; + } +} + +Controllability.prototype.OnCapturePointsChanged = function(msg) +{ + let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + + let owner = cmpOwnership.GetOwner(); + let totalCapturePoints = msg.capturePoints.reduce((a, b) => a + b, 0); + + // Keep in sync with gui/session/unit_actions.js. + this.SetControllability(["Deletable"], msg.capturePoints[owner] >= totalCapturePoints / 2); +} + +Engine.RegisterComponentType(IID_Controllability, "Controllability", Controllability); 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 @@ -262,8 +262,7 @@ "rank": cmpIdentity.GetRank(), "classes": cmpIdentity.GetClassesList(), "visibleClasses": cmpIdentity.GetVisibleClassesList(), - "selectionGroupName": cmpIdentity.GetSelectionGroupName(), - "canDelete": !cmpIdentity.IsUndeletable() + "selectionGroupName": cmpIdentity.GetSelectionGroupName() }; let cmpPosition = Engine.QueryInterface(ent, IID_Position); @@ -286,6 +285,12 @@ ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints(); } + let cmpControllability = Engine.QueryInterface(ent, IID_Controllability); + if (cmpControllability) + { + ret.controllability = cmpControllability.GetControllability(); + } + let cmpBuilder = Engine.QueryInterface(ent, IID_Builder); if (cmpBuilder) ret.builder = true; 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 @@ -89,10 +89,7 @@ "" + "" + "" + - "" + - "" + - "" + - ""; + ""; Identity.prototype.Init = function() { @@ -174,9 +171,4 @@ return this.template.GenericName; }; -Identity.prototype.IsUndeletable = function() -{ - return this.template.Undeletable == "true"; -}; - 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 @@ -187,8 +187,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.IsTurret()) + if (this.IsTurret()) { this.FinishOrder(); return; @@ -234,13 +233,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 +246,7 @@ }, "Order.Walk": function(msg) { - // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) + if (this.IsTurret()) { this.FinishOrder(); return; @@ -280,8 +271,7 @@ }, "Order.WalkAndFight": function(msg) { - // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) + if (this.IsTurret()) { this.FinishOrder(); return; @@ -307,8 +297,7 @@ "Order.WalkToTarget": function(msg) { - // Let players move captured domestic animals around - if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret()) + if (this.IsTurret()) { this.FinishOrder(); return; @@ -455,7 +444,7 @@ }, "Order.Patrol": function(msg) { - if (this.IsAnimal() || this.IsTurret()) + if (this.IsTurret()) { this.FinishOrder(); return; Index: binaries/data/mods/public/simulation/components/interfaces/Controllability.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/interfaces/Controllability.js @@ -0,0 +1,6 @@ +Engine.RegisterInterface("Controllability"); + +/** + * Message sent from Controllability component. + */ +Engine.RegisterMessageType("ControllabilityChanged"); Index: binaries/data/mods/public/simulation/components/tests/test_Capturable.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Capturable.js +++ binaries/data/mods/public/simulation/components/tests/test_Capturable.js @@ -2,6 +2,7 @@ Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); +Engine.LoadComponentScript("interfaces/Controllability.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js"); 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 @@ -6,6 +6,7 @@ Engine.LoadComponentScript("interfaces/Builder.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/CeasefireManager.js"); +Engine.LoadComponentScript("interfaces/Controllability.js"); Engine.LoadComponentScript("interfaces/Resistance.js"); Engine.LoadComponentScript("interfaces/DeathDamage.js"); Engine.LoadComponentScript("interfaces/EndGameManager.js"); @@ -543,7 +544,6 @@ GetRank: function() { return "foo"; }, GetSelectionGroupName: function() { return "Selection Group Name"; }, HasClass: function() { return true; }, - IsUndeletable: function() { return false; } }); AddMock(10, IID_Position, { @@ -572,8 +572,7 @@ "rank": "foo", "classes": ["class1", "class2"], "visibleClasses": ["class3", "class4"], - "selectionGroupName": "Selection Group Name", - "canDelete": true + "selectionGroupName": "Selection Group Name" }, "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 @@ -388,15 +388,11 @@ { for (let ent of data.entities) { + // Keep in sync with gui/session/unit_actions.js. if (!data.controlAllUnits) { - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (cmpIdentity && cmpIdentity.IsUndeletable()) - continue; - - let cmpCapturable = QueryMiragedInterface(ent, IID_Capturable); - if (cmpCapturable && - cmpCapturable.GetCapturePoints()[player] < cmpCapturable.GetMaxCapturePoints() / 2) + let cmpControllability = Engine.QueryInterface(ent, IID_Controllability); + if (cmpControllability && !cmpControllability.GetControllability("Deletable")) continue; let cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply); @@ -867,6 +863,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. */ @@ -1655,7 +1672,13 @@ */ function CanControlUnit(entity, player, controlAll) { - return IsOwnedByPlayer(player, entity) || controlAll; + let cmpControllability = Engine.QueryInterface(entity, IID_Controllability); + let CanBeControlled = IsOwnedByPlayer(player, entity) && (!cmpControllability || cmpControllability.GetControllability("Controllable")) || controlAll; + + if (!CanBeControlled) + notifyOrderFailure(entity, player); + + return CanBeControlled; } /** Index: binaries/data/mods/public/simulation/templates/special/filter/mirage.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/filter/mirage.xml +++ binaries/data/mods/public/simulation/templates/special/filter/mirage.xml @@ -8,7 +8,6 @@ - Index: binaries/data/mods/public/simulation/templates/special/filter/undeletable.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/filter/undeletable.xml +++ binaries/data/mods/public/simulation/templates/special/filter/undeletable.xml @@ -1,6 +1,6 @@ - - true - + + false + Index: binaries/data/mods/public/simulation/templates/special/player/player.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/player/player.xml +++ binaries/data/mods/public/simulation/templates/special/player/player.xml @@ -63,7 +63,6 @@ Player Player - true Index: binaries/data/mods/public/simulation/templates/special/spy.xml =================================================================== --- binaries/data/mods/public/simulation/templates/special/spy.xml +++ binaries/data/mods/public/simulation/templates/special/spy.xml @@ -16,7 +16,6 @@ Spy Spy unlock_spies - true false 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 @@ -3,7 +3,6 @@