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 @@ -136,9 +136,10 @@ return false; return { - "possible": Engine.GuiInterfaceCall("CanCapture", { + "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, - "target": targetState.id + "target": targetState.id, + "types": ["Capture"] }) }; }, @@ -183,7 +184,8 @@ return { "possible": Engine.GuiInterfaceCall("CanAttack", { "entity": entState.id, - "target": targetState.id + "target": targetState.id, + "types": ["!Capture"] }) }; }, @@ -443,7 +445,7 @@ return true; }, - "getActionInfo": function(entState, targetState) + "getActionInfo": function(entState, targetState) { if (!targetState.resourceSupply) return false; @@ -1059,6 +1061,7 @@ unloadAll(); }, }, + "delete": { "getInfo": function(entState) { @@ -1097,6 +1100,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 @@ -1,5 +1,7 @@ function Attack() {} +var g_AttackTypes = ["Melee", "Ranged", "Capture"]; + Attack.prototype.bonusesSchema = "" + "" + @@ -188,9 +190,15 @@ Attack.prototype.Serialize = null; // we have no dynamic state to save -Attack.prototype.GetAttackTypes = function() +Attack.prototype.GetAttackTypes = function(wantedTypes) { - return ["Melee", "Ranged", "Capture"].filter(type => !!this.template[type]); + let types = g_AttackTypes.filter(type => !!this.template[type]); + 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) @@ -211,7 +219,7 @@ return []; }; -Attack.prototype.CanAttack = function(target) +Attack.prototype.CanAttack = function(target, wantedTypes) { let cmpFormation = Engine.QueryInterface(target, IID_Formation); if (cmpFormation) @@ -222,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.GetAttackTypes(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 @@ -449,6 +449,7 @@ let types = cmpAttack.GetAttackTypes(); if (types.length) ret.attack = {}; + for (let type of types) { ret.attack[type] = cmpAttack.GetAttackStrengths(type); @@ -1832,37 +1833,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); }; /* @@ -2007,7 +1981,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 @@ -99,6 +104,16 @@ attackComponentTest(undefined, (attacker, cmpAttack, defender) => { TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes([]), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged", "Capture"]), ["Melee", "Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged"]), ["Melee", "Ranged"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "!Melee"]), []); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee"]), ["Ranged", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee", "!Ranged"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "!Ranged"]), ["Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "Melee", "!Ranged"]), ["Melee", "Capture"]); + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["FemaleCitizen"]); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]); TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 }); @@ -147,6 +162,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)