mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-07-04 05:55:47 -07:00
Add a type argument for CanAttack method of Attack component. Patch by bb. Fixes #4220.
Differential Revision: https://code.wildfiregames.com/D122 This was SVN commit r19528.
This commit is contained in:
parent
ca89fa35b9
commit
1c64742edc
4 changed files with 109 additions and 51 deletions
|
|
@ -136,9 +136,10 @@ var unitActions =
|
|||
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 @@ var unitActions =
|
|||
return {
|
||||
"possible": Engine.GuiInterfaceCall("CanAttack", {
|
||||
"entity": entState.id,
|
||||
"target": targetState.id
|
||||
"target": targetState.id,
|
||||
"types": ["!Capture"]
|
||||
})
|
||||
};
|
||||
},
|
||||
|
|
@ -443,7 +445,7 @@ var unitActions =
|
|||
|
||||
return true;
|
||||
},
|
||||
"getActionInfo": function(entState, targetState)
|
||||
"getActionInfo": function(entState, targetState)
|
||||
{
|
||||
if (!targetState.resourceSupply)
|
||||
return false;
|
||||
|
|
@ -1059,6 +1061,7 @@ var g_EntityCommands =
|
|||
unloadAll();
|
||||
},
|
||||
},
|
||||
|
||||
"delete": {
|
||||
"getInfo": function(entState)
|
||||
{
|
||||
|
|
@ -1097,6 +1100,7 @@ var g_EntityCommands =
|
|||
openDeleteDialog(selection);
|
||||
},
|
||||
},
|
||||
|
||||
"stop": {
|
||||
"getInfo": function(entState)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
function Attack() {}
|
||||
|
||||
var g_AttackTypes = ["Melee", "Ranged", "Capture"];
|
||||
|
||||
Attack.prototype.bonusesSchema =
|
||||
"<optional>" +
|
||||
"<element name='Bonuses'>" +
|
||||
|
|
@ -188,9 +190,15 @@ Attack.prototype.Init = function()
|
|||
|
||||
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 @@ Attack.prototype.GetRestrictedClasses = function(type)
|
|||
return [];
|
||||
};
|
||||
|
||||
Attack.prototype.CanAttack = function(target)
|
||||
Attack.prototype.CanAttack = function(target, wantedTypes)
|
||||
{
|
||||
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
|
||||
if (cmpFormation)
|
||||
|
|
@ -222,20 +230,36 @@ Attack.prototype.CanAttack = function(target)
|
|||
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
|
||||
return false;
|
||||
|
||||
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
return false;
|
||||
|
||||
let targetClasses = cmpIdentity.GetClassesList();
|
||||
if (targetClasses.indexOf("Domestic") != -1 && this.template.Slaughter &&
|
||||
(!wantedTypes || !wantedTypes.filter(wType => wType.indexOf("!") != 0).length))
|
||||
return true;
|
||||
|
||||
let cmpEntityPlayer = QueryOwnerInterface(this.entity);
|
||||
let cmpTargetPlayer = QueryOwnerInterface(target);
|
||||
if (!cmpTargetPlayer || !cmpEntityPlayer)
|
||||
return false;
|
||||
|
||||
let types = this.GetAttackTypes(wantedTypes);
|
||||
let entityOwner = cmpEntityPlayer.GetPlayerID();
|
||||
let targetOwner = cmpTargetPlayer.GetPlayerID();
|
||||
let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
|
||||
|
||||
// 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" && !cmpEntityPlayer.IsEnemy(targetOwner))
|
||||
continue;
|
||||
|
||||
if (type == "Capture" && (!cmpCapturable || !cmpCapturable.CanCapture(entityOwner)))
|
||||
continue;
|
||||
|
||||
if (heightDiff > this.GetRange(type).max)
|
||||
|
|
@ -301,7 +325,7 @@ Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
|
|||
{
|
||||
// TODO: Formation against formation needs review
|
||||
let types = this.GetAttackTypes();
|
||||
return ["Ranged", "Melee", "Capture"].find(attack => types.indexOf(attack) != -1);
|
||||
return g_AttackTypes.find(attack => types.indexOf(attack) != -1);
|
||||
}
|
||||
|
||||
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
|
|||
let types = cmpAttack.GetAttackTypes();
|
||||
if (types.length)
|
||||
ret.attack = {};
|
||||
|
||||
for (let type of types)
|
||||
{
|
||||
ret.attack[type] = cmpAttack.GetAttackStrengths(type);
|
||||
|
|
@ -1843,37 +1844,10 @@ GuiInterface.prototype.GetTradingDetails = function(player, data)
|
|||
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);
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -2018,7 +1992,6 @@ let exposedFunctions = {
|
|||
"HasIdleUnits": 1,
|
||||
"GetTradingRouteGain": 1,
|
||||
"GetTradingDetails": 1,
|
||||
"CanCapture": 1,
|
||||
"CanAttack": 1,
|
||||
"GetBatchTime": 1,
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Engine.LoadComponentScript("Attack.js");
|
|||
|
||||
let entityID = 903;
|
||||
|
||||
function attackComponentTest(defenderClass, test_function)
|
||||
function attackComponentTest(defenderClass, isEnemy, test_function)
|
||||
{
|
||||
ResetState();
|
||||
|
||||
|
|
@ -22,7 +22,8 @@ function attackComponentTest(defenderClass, test_function)
|
|||
});
|
||||
|
||||
AddMock(playerEnt1, IID_Player, {
|
||||
"GetPlayerID": () => 1
|
||||
"GetPlayerID": () => 1,
|
||||
"IsEnemy": () => isEnemy
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +88,10 @@ function attackComponentTest(defenderClass, test_function)
|
|||
"HasClass": className => className == defenderClass
|
||||
});
|
||||
|
||||
AddMock(defender, IID_Ownership, {
|
||||
"GetOwner": () => 1
|
||||
});
|
||||
|
||||
AddMock(defender, IID_Position, {
|
||||
"IsInWorld": () => true,
|
||||
"GetHeightOffset": () => 0
|
||||
|
|
@ -96,9 +101,19 @@ function attackComponentTest(defenderClass, test_function)
|
|||
}
|
||||
|
||||
// Validate template getter functions
|
||||
attackComponentTest(undefined, (attacker, cmpAttack, defender) => {
|
||||
attackComponentTest(undefined, true ,(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 });
|
||||
|
|
@ -122,7 +137,7 @@ attackComponentTest(undefined, (attacker, cmpAttack, defender) => {
|
|||
});
|
||||
|
||||
for (let className of ["Infantry", "Cavalry"])
|
||||
attackComponentTest(className, (attacker, cmpAttack, defender) => {
|
||||
attackComponentTest(className, true, (attacker, cmpAttack, defender) => {
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Ranged", defender), 1);
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Capture", defender), 1);
|
||||
|
|
@ -130,13 +145,13 @@ for (let className of ["Infantry", "Cavalry"])
|
|||
});
|
||||
|
||||
// CanAttack rejects elephant attack due to RestrictedClasses
|
||||
attackComponentTest("Elephant", (attacker, cmpAttack, defender) => {
|
||||
attackComponentTest("Elephant", true, (attacker, cmpAttack, defender) => {
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), false);
|
||||
});
|
||||
|
||||
function testGetBestAttackAgainst(defenderClass, bestAttack, isBuilding = false)
|
||||
{
|
||||
attackComponentTest(defenderClass, (attacker, cmpAttack, defender) => {
|
||||
attackComponentTest(defenderClass, true, (attacker, cmpAttack, defender) => {
|
||||
|
||||
if (isBuilding)
|
||||
AddMock(defender, IID_Capturable, {
|
||||
|
|
@ -147,6 +162,14 @@ function testGetBestAttackAgainst(defenderClass, bestAttack, isBuilding = false)
|
|||
});
|
||||
|
||||
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 || defenderClass == "Domestic");
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
|
||||
|
||||
let allowCapturing = [true];
|
||||
if (!isBuilding)
|
||||
|
|
@ -155,6 +178,40 @@ function testGetBestAttackAgainst(defenderClass, bestAttack, isBuilding = false)
|
|||
for (let ac of allowCapturing)
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
|
||||
});
|
||||
|
||||
attackComponentTest(defenderClass, false, (attacker, cmpAttack, defender) => {
|
||||
|
||||
if (isBuilding)
|
||||
AddMock(defender, IID_Capturable, {
|
||||
"CanCapture": playerID => {
|
||||
TS_ASSERT_EQUALS(playerID, 1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), isBuilding || defenderClass == "Domestic");
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), isBuilding || defenderClass == "Domestic");
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), false);
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), isBuilding || defenderClass == "Domestic");
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), isBuilding);
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), isBuilding);
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding || defenderClass == "Domestic");
|
||||
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
|
||||
|
||||
let allowCapturing = [true];
|
||||
if (!isBuilding)
|
||||
allowCapturing.push(false);
|
||||
|
||||
let attack = undefined;
|
||||
if (defenderClass == "Domestic")
|
||||
attack = "Slaughter";
|
||||
else if (defenderClass == "Structure")
|
||||
attack = "Capture";
|
||||
|
||||
for (let ac of allowCapturing)
|
||||
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
|
||||
});
|
||||
}
|
||||
|
||||
testGetBestAttackAgainst("FemaleCitizen", "Melee");
|
||||
|
|
|
|||
Loading…
Reference in a new issue