2021-01-01 23:09:08 -08:00
|
|
|
AttackEffects = class AttackEffects
|
|
|
|
|
{
|
|
|
|
|
constructor() {}
|
|
|
|
|
Receivers()
|
|
|
|
|
{
|
|
|
|
|
return [{
|
|
|
|
|
"type": "Damage",
|
|
|
|
|
"IID": "IID_Health",
|
|
|
|
|
"method": "TakeDamage"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "Capture",
|
|
|
|
|
"IID": "IID_Capturable",
|
|
|
|
|
"method": "Capture"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"type": "ApplyStatus",
|
|
|
|
|
"IID": "IID_StatusEffectsReceiver",
|
|
|
|
|
"method": "ApplyStatus"
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-04-15 23:55:23 -07:00
|
|
|
Engine.LoadHelperScript("Attack.js");
|
2016-08-05 11:44:00 -07:00
|
|
|
Engine.LoadHelperScript("Player.js");
|
|
|
|
|
Engine.LoadHelperScript("ValueModification.js");
|
2017-12-23 07:53:00 -08:00
|
|
|
Engine.LoadComponentScript("interfaces/Auras.js");
|
2016-08-05 11:44:00 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/Capturable.js");
|
2023-06-18 23:33:33 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/Diplomacy.js");
|
Add a system component to handle stat modifiers, make technologies and auras use this common interface.
The ModifiersManager system component provides an interface to add and
remove modifiers, and get modified stats.
The goal is to merge all the different stat-modifying systems 0 A.D. has
implemented over the years.
This commit makes technologies and auras use ModifiersManager. Some
cheats and AI bonuses also have a similar stat-modifying effect that
have not yet been updated.
Further, this system component makes it possible for e.g. triggers to
easily add modifiers, enabling the writing of Castle Blood Automatic,
RPG or Tower Defense maps without the need for mods or hacks.
The 'Modifier' name was preferred over 'Modification' as it is shorter
and more readable, along with the logic that 'modifiers' store
'modifications' and this stores modifiers. Renaming of other functions
and classes has been left for future work for now.
Internally, this uses a JS data structure. If performance issues arise
with it in the future, this data structure or the whole component could
be moved to C++.
The performance has been tested to be about as fast as the current
implementations (and specifically much faster for global auras with no
icons). Testing showed that sending value modification messages was by
far the slowest part.
Comments by: leper, Stan, elexis
Differential Revision: https://code.wildfiregames.com/D274
This was SVN commit r22767.
2019-08-24 00:37:07 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
2016-08-05 11:44:00 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/Formation.js");
|
2017-12-23 07:53:00 -08:00
|
|
|
Engine.LoadComponentScript("interfaces/Health.js");
|
2021-05-19 05:36:22 -07:00
|
|
|
Engine.LoadComponentScript("interfaces/Resistance.js");
|
2017-12-23 07:53:00 -08:00
|
|
|
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
|
2016-08-05 11:44:00 -07:00
|
|
|
Engine.LoadComponentScript("Attack.js");
|
|
|
|
|
|
|
|
|
|
let entityID = 903;
|
|
|
|
|
|
2017-05-07 12:32:59 -07:00
|
|
|
function attackComponentTest(defenderClass, isEnemy, test_function)
|
2016-08-05 11:44:00 -07:00
|
|
|
{
|
2025-05-11 00:04:38 -07:00
|
|
|
const playerEnt1 = 5;
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2020-11-04 10:56:45 -08:00
|
|
|
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
|
|
|
|
"GetPlayerByID": () => playerEnt1
|
|
|
|
|
});
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2020-11-04 10:56:45 -08:00
|
|
|
AddMock(playerEnt1, IID_Player, {
|
|
|
|
|
"GetPlayerID": () => 1,
|
2023-06-18 23:33:33 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(playerEnt1, IID_Diplomacy, {
|
2020-11-04 10:56:45 -08:00
|
|
|
"IsEnemy": () => isEnemy
|
|
|
|
|
});
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const attacker = entityID;
|
2016-08-05 11:44:00 -07:00
|
|
|
|
|
|
|
|
AddMock(attacker, IID_Position, {
|
|
|
|
|
"IsInWorld": () => true,
|
2017-08-07 05:38:57 -07:00
|
|
|
"GetHeightOffset": () => 5,
|
|
|
|
|
"GetPosition2D": () => new Vector2D(1, 2)
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(attacker, IID_Ownership, {
|
2016-08-11 06:20:31 -07:00
|
|
|
"GetOwner": () => 1
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const cmpAttack = ConstructComponent(attacker, "Attack", {
|
2019-06-16 10:08:27 -07:00
|
|
|
"Melee": {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 11,
|
|
|
|
|
"Pierce": 5,
|
|
|
|
|
"Crush": 0
|
|
|
|
|
},
|
2016-08-05 11:44:00 -07:00
|
|
|
"MinRange": 3,
|
|
|
|
|
"MaxRange": 5,
|
|
|
|
|
"PreferredClasses": {
|
2025-06-10 11:00:31 -07:00
|
|
|
"_string": "Civilian"
|
2016-08-05 11:44:00 -07:00
|
|
|
},
|
|
|
|
|
"RestrictedClasses": {
|
|
|
|
|
"_string": "Elephant Archer"
|
|
|
|
|
},
|
|
|
|
|
"Bonuses":
|
|
|
|
|
{
|
|
|
|
|
"BonusCav": {
|
|
|
|
|
"Classes": "Cavalry",
|
|
|
|
|
"Multiplier": 2
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2019-06-16 10:08:27 -07:00
|
|
|
"Ranged": {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 0,
|
|
|
|
|
"Pierce": 10,
|
|
|
|
|
"Crush": 0
|
|
|
|
|
},
|
2016-08-05 11:44:00 -07:00
|
|
|
"MinRange": 10,
|
|
|
|
|
"MaxRange": 80,
|
|
|
|
|
"PrepareTime": 300,
|
|
|
|
|
"RepeatTime": 500,
|
2019-04-13 02:27:14 -07:00
|
|
|
"Projectile": {
|
|
|
|
|
"Speed": 10,
|
|
|
|
|
"Spread": 2,
|
2020-03-07 02:39:05 -08:00
|
|
|
"Gravity": 1,
|
|
|
|
|
"FriendlyFire": "false"
|
2019-04-13 05:49:33 -07:00
|
|
|
},
|
2016-08-05 11:44:00 -07:00
|
|
|
"PreferredClasses": {
|
|
|
|
|
"_string": "Archer"
|
|
|
|
|
},
|
|
|
|
|
"RestrictedClasses": {
|
|
|
|
|
"_string": "Elephant"
|
2017-08-07 05:38:57 -07:00
|
|
|
},
|
2019-06-16 10:08:27 -07:00
|
|
|
"Splash": {
|
2017-08-07 05:38:57 -07:00
|
|
|
"Shape": "Circular",
|
|
|
|
|
"Range": 10,
|
|
|
|
|
"FriendlyFire": "false",
|
2019-06-16 10:08:27 -07:00
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 0.0,
|
|
|
|
|
"Pierce": 15.0,
|
|
|
|
|
"Crush": 35.0
|
|
|
|
|
},
|
2017-08-14 08:57:56 -07:00
|
|
|
"Bonuses": {
|
|
|
|
|
"BonusCav": {
|
|
|
|
|
"Classes": "Cavalry",
|
2017-08-20 23:52:37 -07:00
|
|
|
"Multiplier": 3
|
2017-08-14 08:57:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-08-05 11:44:00 -07:00
|
|
|
}
|
|
|
|
|
},
|
2019-06-16 10:08:27 -07:00
|
|
|
"Capture": {
|
2019-08-22 11:00:33 -07:00
|
|
|
"Capture": 8,
|
2016-08-05 11:44:00 -07:00
|
|
|
"MaxRange": 10,
|
|
|
|
|
},
|
2020-06-09 04:47:16 -07:00
|
|
|
"Slaughter": {},
|
|
|
|
|
"StatusEffect": {
|
|
|
|
|
"ApplyStatus": {
|
|
|
|
|
"StatusInternalName": {
|
|
|
|
|
"StatusName": "StatusShownName",
|
|
|
|
|
"ApplierTooltip": "ApplierTooltip",
|
|
|
|
|
"ReceiverTooltip": "ReceiverTooltip",
|
|
|
|
|
"Duration": 5000,
|
|
|
|
|
"Stackability": "Stacks",
|
|
|
|
|
"Modifiers": {
|
|
|
|
|
"SE": {
|
|
|
|
|
"Paths": {
|
|
|
|
|
"_string": "Health/Max"
|
|
|
|
|
},
|
|
|
|
|
"Affects": {
|
|
|
|
|
"_string": "Unit"
|
|
|
|
|
},
|
|
|
|
|
"Add": 10
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"MinRange": "10",
|
|
|
|
|
"MaxRange": "80"
|
|
|
|
|
}
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const defender = ++entityID;
|
2016-08-05 11:44:00 -07:00
|
|
|
|
|
|
|
|
AddMock(defender, IID_Identity, {
|
|
|
|
|
"GetClassesList": () => [defenderClass],
|
2020-08-24 08:17:50 -07:00
|
|
|
"HasClass": className => className == defenderClass,
|
|
|
|
|
"GetCiv": () => "civ"
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
2017-05-07 12:32:59 -07:00
|
|
|
AddMock(defender, IID_Ownership, {
|
|
|
|
|
"GetOwner": () => 1
|
|
|
|
|
});
|
|
|
|
|
|
2016-08-05 11:44:00 -07:00
|
|
|
AddMock(defender, IID_Position, {
|
|
|
|
|
"IsInWorld": () => true,
|
|
|
|
|
"GetHeightOffset": () => 0
|
|
|
|
|
});
|
|
|
|
|
|
2017-12-23 07:53:00 -08:00
|
|
|
AddMock(defender, IID_Health, {
|
|
|
|
|
"GetHitpoints": () => 100
|
|
|
|
|
});
|
|
|
|
|
|
2021-05-19 05:36:22 -07:00
|
|
|
AddMock(defender, IID_Resistance, {
|
|
|
|
|
});
|
|
|
|
|
|
2016-08-05 11:44:00 -07:00
|
|
|
test_function(attacker, cmpAttack, defender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate template getter functions
|
2025-12-30 00:57:37 -08:00
|
|
|
attackComponentTest(undefined, true, (attacker, cmpAttack, defender) =>
|
|
|
|
|
{
|
2016-08-05 11:44:00 -07:00
|
|
|
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(), ["Melee", "Ranged", "Capture"]);
|
2017-05-07 12:32:59 -07:00
|
|
|
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"]);
|
|
|
|
|
|
2025-06-10 11:00:31 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["Civilian"]);
|
2016-08-05 11:44:00 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
|
2026-05-01 05:42:14 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80, "parabolic": true });
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Capture"), { "Capture": 8 });
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged"), {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 0,
|
|
|
|
|
"Pierce": 10,
|
|
|
|
|
"Crush": 0
|
|
|
|
|
}
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
2019-06-16 10:08:27 -07:00
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged", true), {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 0.0,
|
|
|
|
|
"Pierce": 15.0,
|
|
|
|
|
"Crush": 35.0
|
|
|
|
|
},
|
|
|
|
|
"Bonuses": {
|
|
|
|
|
"BonusCav": {
|
|
|
|
|
"Classes": "Cavalry",
|
|
|
|
|
"Multiplier": 3
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-14 08:57:56 -07:00
|
|
|
});
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2020-06-09 04:47:16 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("StatusEffect"), {
|
|
|
|
|
"ApplyStatus": {
|
|
|
|
|
"StatusInternalName": {
|
|
|
|
|
"Duration": 5000,
|
|
|
|
|
"Interval": 0,
|
|
|
|
|
"Stackability": "Stacks",
|
|
|
|
|
"Modifiers": {
|
|
|
|
|
"SE": {
|
|
|
|
|
"Paths": {
|
|
|
|
|
"_string": "Health/Max"
|
|
|
|
|
},
|
|
|
|
|
"Affects": {
|
|
|
|
|
"_string": "Unit"
|
|
|
|
|
},
|
|
|
|
|
"Add": 10
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2016-08-05 11:44:00 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
|
|
|
|
|
"prepare": 300,
|
2016-08-11 07:35:50 -07:00
|
|
|
"repeat": 500
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
2020-04-21 14:44:05 -07:00
|
|
|
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("Ranged"), 500);
|
|
|
|
|
|
2016-08-05 11:44:00 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Capture"), {
|
|
|
|
|
"prepare": 0,
|
2016-08-11 07:35:50 -07:00
|
|
|
"repeat": 1000
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
2017-08-07 05:38:57 -07:00
|
|
|
|
2020-04-21 14:44:05 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRepeatTime("Capture"), 1000);
|
|
|
|
|
|
2020-01-12 05:37:14 -08:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashData("Ranged"), {
|
2019-08-22 11:00:33 -07:00
|
|
|
"attackData": {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Hack": 0,
|
|
|
|
|
"Pierce": 15,
|
|
|
|
|
"Crush": 35,
|
|
|
|
|
},
|
|
|
|
|
"Bonuses": {
|
|
|
|
|
"BonusCav": {
|
|
|
|
|
"Classes": "Cavalry",
|
|
|
|
|
"Multiplier": 3
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-16 10:08:27 -07:00
|
|
|
},
|
2017-09-18 09:33:56 -07:00
|
|
|
"friendlyFire": false,
|
2020-01-12 05:37:14 -08:00
|
|
|
"radius": 10,
|
2017-09-18 09:33:56 -07:00
|
|
|
"shape": "Circular"
|
|
|
|
|
});
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
for (const className of ["Infantry", "Cavalry"])
|
2025-12-30 00:57:37 -08:00
|
|
|
attackComponentTest(className, true, (attacker, cmpAttack, defender) =>
|
|
|
|
|
{
|
2017-08-20 23:52:37 -07:00
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Melee").Bonuses.BonusCav.Multiplier, 2);
|
2017-08-20 23:52:37 -07:00
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Capture").Bonuses || null, null);
|
2017-08-20 23:52:37 -07:00
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const getAttackBonus = (s, t, e, splash) => AttackHelper.GetAttackBonus(s, e, t, cmpAttack.GetAttackEffectsData(t, splash).Bonuses || null);
|
2019-06-06 13:44:30 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Melee", defender), className == "Cavalry" ? 2 : 1);
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender), 1);
|
2019-08-22 11:00:33 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender, true), className == "Cavalry" ? 3 : 1);
|
2019-06-06 13:44:30 -07:00
|
|
|
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Capture", defender), 1);
|
|
|
|
|
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Slaughter", defender), 1);
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// CanAttack rejects elephant attack due to RestrictedClasses
|
2025-12-30 00:57:37 -08:00
|
|
|
attackComponentTest("Elephant", true, (attacker, cmpAttack, defender) =>
|
|
|
|
|
{
|
2016-08-05 11:44:00 -07:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), false);
|
|
|
|
|
});
|
|
|
|
|
|
2019-07-28 07:58:24 -07:00
|
|
|
function testGetBestAttackAgainst(defenderClass, bestAttack, bestAllyAttack, isBuilding = false)
|
2016-08-05 11:44:00 -07:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
attackComponentTest(defenderClass, true, (attacker, cmpAttack, defender) =>
|
|
|
|
|
{
|
2016-08-05 11:44:00 -07:00
|
|
|
|
|
|
|
|
if (isBuilding)
|
|
|
|
|
AddMock(defender, IID_Capturable, {
|
2025-12-30 00:57:37 -08:00
|
|
|
"CanCapture": playerID =>
|
|
|
|
|
{
|
2016-08-05 11:44:00 -07:00
|
|
|
TS_ASSERT_EQUALS(playerID, 1);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
|
2017-05-07 12:32:59 -07:00
|
|
|
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);
|
2016-08-05 11:44:00 -07:00
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const allowCapturing = [true];
|
2016-08-05 11:44:00 -07:00
|
|
|
if (!isBuilding)
|
|
|
|
|
allowCapturing.push(false);
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
for (const ac of allowCapturing)
|
2016-11-18 01:43:54 -08:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
|
2016-08-05 11:44:00 -07:00
|
|
|
});
|
2017-05-07 12:32:59 -07:00
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
attackComponentTest(defenderClass, false, (attacker, cmpAttack, defender) =>
|
|
|
|
|
{
|
2017-05-07 12:32:59 -07:00
|
|
|
|
|
|
|
|
if (isBuilding)
|
|
|
|
|
AddMock(defender, IID_Capturable, {
|
2025-12-30 00:57:37 -08:00
|
|
|
"CanCapture": playerID =>
|
|
|
|
|
{
|
2017-05-07 12:32:59 -07:00
|
|
|
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);
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const allowCapturing = [true];
|
2017-05-07 12:32:59 -07:00
|
|
|
if (!isBuilding)
|
|
|
|
|
allowCapturing.push(false);
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
for (const ac of allowCapturing)
|
2019-07-28 07:58:24 -07:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAllyAttack);
|
2017-05-07 12:32:59 -07:00
|
|
|
});
|
2016-08-05 11:44:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-10 11:00:31 -07:00
|
|
|
testGetBestAttackAgainst("Civilian", "Melee", undefined);
|
2019-07-28 07:58:24 -07:00
|
|
|
testGetBestAttackAgainst("Archer", "Ranged", undefined);
|
|
|
|
|
testGetBestAttackAgainst("Domestic", "Slaughter", "Slaughter");
|
|
|
|
|
testGetBestAttackAgainst("Structure", "Capture", "Capture", true);
|
2019-07-29 11:51:50 -07:00
|
|
|
testGetBestAttackAgainst("Structure", "Ranged", undefined, false);
|
2021-01-21 06:34:08 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
function testAttackPreference()
|
|
|
|
|
{
|
|
|
|
|
const attacker = 5;
|
|
|
|
|
|
2025-05-11 00:04:38 -07:00
|
|
|
const cmpAttack = ConstructComponent(attacker, "Attack", {
|
2021-01-21 06:34:08 -08:00
|
|
|
"Melee": {
|
|
|
|
|
"Damage": {
|
|
|
|
|
"Crush": 0
|
|
|
|
|
},
|
|
|
|
|
"MinRange": 3,
|
|
|
|
|
"MaxRange": 5,
|
|
|
|
|
"PreferredClasses": {
|
2025-06-10 11:00:31 -07:00
|
|
|
"_string": "Civilian Unit+!Ship"
|
2021-01-21 06:34:08 -08:00
|
|
|
},
|
|
|
|
|
"RestrictedClasses": {
|
|
|
|
|
"_string": "Elephant Archer"
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(attacker+1, IID_Identity, {
|
2025-06-10 11:00:31 -07:00
|
|
|
"GetClassesList": () => ["Civilian", "Unit"]
|
2021-01-21 06:34:08 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(attacker+2, IID_Identity, {
|
|
|
|
|
"GetClassesList": () => ["Unit"]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(attacker+3, IID_Identity, {
|
|
|
|
|
"GetClassesList": () => ["Unit", "Ship"]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AddMock(attacker+4, IID_Identity, {
|
|
|
|
|
"GetClassesList": () => ["SomethingElse"]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+1), 0);
|
|
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+2), 1);
|
2021-03-22 06:27:33 -07:00
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+3), undefined);
|
|
|
|
|
TS_ASSERT_EQUALS(cmpAttack.GetPreference(attacker+4), undefined);
|
2021-01-21 06:34:08 -08:00
|
|
|
}
|
|
|
|
|
testAttackPreference();
|