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 @@ -135,10 +135,10 @@ if (!entState.attack || !targetState.hitpoints) return false; - return { - "possible": Engine.GuiInterfaceCall("CanCapture", { + return { "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, - "target": targetState.id + "target": targetState.id, + "types": ["Capture"] }) }; }, @@ -183,7 +183,8 @@ return { "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, - "target": targetState.id + "target": targetState.id, + "types": ["!Capture"] }) }; }, @@ -440,7 +441,7 @@ return true; }, - "getActionInfo": function(entState, targetState) + "getActionInfo": function(entState, targetState) { if (!targetState.resourceSupply) return false; @@ -1056,6 +1057,7 @@ unloadAll(); }, }, + "delete": { "getInfo": function(entState) { @@ -1094,6 +1096,7 @@ openDeleteDialog(selection); }, }, + "stop": { "getInfo": function(entState) { Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -190,6 +190,17 @@ return ["Melee", "Ranged", "Capture"].filter(type => !!this.template[type]); }; +Attack.prototype.GetWantedAttackTypes = function(wantedTypes) +{ + let types = this.GetAttackTypes(); + if (!wantedTypes) + return types; + + let wantedTypesReal = wantedTypes.filter(wtype => wtype.indexOf("!") != 0); + return types.filter(type => wantedTypes.indexOf("!" + type) == -1 && + (!wantedTypesReal || !wantedTypesReal.length || wantedTypesReal.indexOf(type) != -1)); +}; + Attack.prototype.GetPreferredClasses = function(type) { if (this.template[type] && this.template[type].PreferredClasses && @@ -208,7 +219,7 @@ return []; }; -Attack.prototype.CanAttack = function(target) +Attack.prototype.CanAttack = function(target, wantedTypes) { let cmpFormation = Engine.QueryInterface(target, IID_Formation); if (cmpFormation) @@ -219,20 +230,32 @@ if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) return false; + let cmpIdentity = Engine.QueryInterface(target, IID_Identity); + if (!cmpIdentity) + return false; + + let cmpEntityPlayer = QueryOwnerInterface(this.entity); + let cmpTargetPlayer = QueryOwnerInterface(target); + if (!cmpTargetPlayer || !cmpEntityPlayer) + return false; + + let types = this.GetWantedAttackTypes(wantedTypes); + + let targetOwner = cmpEntityPlayer.GetPlayerID(); + let targetClasses = cmpIdentity.GetClassesList(); + let cmpCapturable = QueryMiragedInterface(target, IID_Capturable); + if (targetClasses.indexOf("Domestic") == -1 && !cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()) && + (!cmpCapturable || !cmpCapturable.CanCapture(targetOwner) || types.indexOf("Capture"))) + return false; + // Check if the relative height difference is larger than the attack range // If the relative height is bigger, it means they will never be able to // reach each other, no matter how close they come. let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset()); - const cmpIdentity = Engine.QueryInterface(target, IID_Identity); - if (!cmpIdentity) - return undefined; - - const targetClasses = cmpIdentity.GetClassesList(); - - for (let type of this.GetAttackTypes()) + for (let type of types) { - if (type == "Capture" && !QueryMiragedInterface(target, IID_Capturable)) + if (type == "Capture" && (!cmpCapturable || !cmpCapturable.CanCapture(targetOwner))) continue; if (heightDiff > this.GetRange(type).max) 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 @@ -447,6 +447,7 @@ let types = cmpAttack.GetAttackTypes(); if (types.length) ret.attack = {}; + for (let type of types) { ret.attack[type] = cmpAttack.GetAttackStrengths(type); @@ -1819,37 +1820,10 @@ return result; }; -GuiInterface.prototype.CanCapture = function(player, data) -{ - let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); - if (!cmpAttack) - return false; - - let owner = QueryOwnerInterface(data.entity).GetPlayerID(); - - let cmpCapturable = QueryMiragedInterface(data.target, IID_Capturable); - if (cmpCapturable && cmpCapturable.CanCapture(owner) && cmpAttack.GetAttackTypes().indexOf("Capture") != -1) - return cmpAttack.CanAttack(data.target); - - return false; -}; - GuiInterface.prototype.CanAttack = function(player, data) { let cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); - if (!cmpAttack) - return false; - - let cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player); - let cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player); - if (!cmpEntityPlayer || !cmpTargetPlayer) - return false; - - // if the owner is an enemy, it's up to the attack component to decide - if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID())) - return cmpAttack.CanAttack(data.target); - - return false; + return cmpAttack && cmpAttack.CanAttack(data.target, data.types || undefined); }; /* @@ -1994,7 +1968,6 @@ "HasIdleUnits": 1, "GetTradingRouteGain": 1, "GetTradingDetails": 1, - "CanCapture": 1, "CanAttack": 1, "GetBatchTime": 1, Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Attack.js +++ binaries/data/mods/public/simulation/components/tests/test_Attack.js @@ -22,7 +22,8 @@ }); AddMock(playerEnt1, IID_Player, { - "GetPlayerID": () => 1 + "GetPlayerID": () => 1, + "IsEnemy": () => true }); } @@ -87,6 +88,10 @@ "HasClass": className => className == defenderClass }); + AddMock(defender, IID_Ownership, { + "GetOwner": () => 1 + }); + AddMock(defender, IID_Position, { "IsInWorld": () => true, "GetHeightOffset": () => 0 @@ -104,6 +109,17 @@ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 }); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), { "value": 8 }); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes([]), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Melee", "Ranged", "Capture"]), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Melee", "Ranged"]), ["Melee", "Ranged"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Capture"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Melee", "!Melee"]), []); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["!Melee"]), ["Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["!Melee", "!Ranged"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Capture", "!Ranged"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetWantedAttackTypes(["Capture", "Melee", "!Ranged"]), ["Melee", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), { "hack": 0, "pierce": 10, @@ -147,6 +163,14 @@ }); TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), true); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), true); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), true); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), defenderClass != "Archer"); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), true); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding); + TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false); let allowCapturing = [true]; if (!isBuilding)