/* * SpellTargetsEvaluatorTest.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "../mock/BattleFake.h" #include "../mock/mock_battle_Unit.h" #include "../mock/mock_spells_Mechanics.h" #include "AI/BattleAI/SpellTargetsEvaluator.h" #include "lib/CStack.h" #include "lib/battle/CBattleInfoCallback.h" namespace test { using namespace ::testing; using namespace spells; using namespace battle; using PossiblePositions = std::vector; class CBattleInfoCallbackMock : public CBattleInfoCallback { public: MOCK_CONST_METHOD1(battleGetAllUnits, battle::Units(bool)); MOCK_CONST_METHOD0(getBattle, IBattleInfo *()); MOCK_CONST_METHOD0(getPlayerID, std::optional()); #if SCRIPTING_ENABLED MOCK_CONST_METHOD0(getContextPool, scripting::Pool *()); #endif }; class CStackMock : public CStack { public: MOCK_CONST_METHOD0(unitSide, BattleSide()); }; class SpellTargetEvaluatorTest : public ::testing::Test { public: MechanicsMock mechMock; CBattleInfoCallbackMock battleMock; battle::Units allUnits; TStacks allStacks; BattleSide casterSide = BattleSide::ATTACKER; BattleSide enemySide = BattleSide::DEFENDER; void SetUp() override { mechMock.casterSide = casterSide; ON_CALL(mechMock, battle()).WillByDefault(Return(&battleMock)); ON_CALL(mechMock, canBeCastAt(_, _)).WillByDefault(Return(true)); } void TearDown() override { for(const auto * unit : allUnits) delete unit; allUnits.clear(); for(const auto * stack : allStacks) delete stack; allStacks.clear(); } void spellTargetTypes(std::vector targetTypes) { ON_CALL(mechMock, getTargetTypes()).WillByDefault(Return(std::move(targetTypes))); } CStackMock * addStack(BattleHex position, BattleSide battleSide, bool isSuspectible = true) { auto * stack = new CStackMock(); ON_CALL(*stack, unitSide()).WillByDefault(Return(battleSide)); allStacks.push_back(stack); auto * unit = new UnitMock(); ON_CALL(*unit, getPosition()).WillByDefault(Return(position)); allUnits.push_back(unit); if(!isSuspectible) ON_CALL(mechMock, canBeCastAt(Contains(Field(&Destination::hexValue, position)), _)).WillByDefault(Return(false)); return stack; } void setAffectedStacksForCast(BattleHex position, std::vector stacks) { ON_CALL(mechMock, getAffectedStacks(Contains(Field(&Destination::hexValue, position)))).WillByDefault(Return(stacks)); } void confirmResults(std::vector allRequiredCasts) { std::vector result = SpellTargetEvaluator::getViableTargets(&mechMock); basicCheck(result); ASSERT_EQ(result.size(), allRequiredCasts.size()); std::vector targetedHexes; targetedHexes.reserve(result.size()); for(Target target : result) targetedHexes.push_back(target.front().hexValue); for(const PossiblePositions & requiredCast : allRequiredCasts) EXPECT_TRUE(containCommonValue(requiredCast, targetedHexes)); } void basicCheck(std::vector & result) { for(const Target & target : result) EXPECT_EQ(target.size(), 1); //multi-destination spells are not handled by targetEvaluator } template bool containCommonValue(const std::vector & v1, const std::vector & v2) { for(T val1 : v1) { for(T val2 : v2) { if(val1 == val2) return true; } } return false; } }; TEST_F(SpellTargetEvaluatorTest, ReturnsEmptyIfMultiDestinationSpell) { spellTargetTypes({AimType::CREATURE, AimType::LOCATION}); std::vector result = SpellTargetEvaluator::getViableTargets(&mechMock); EXPECT_TRUE(result.empty()); } TEST_F(SpellTargetEvaluatorTest, ReturnSingleEmptyDestinationIfTargetIsNone) { spellTargetTypes({AimType::NO_TARGET}); std::vector result = SpellTargetEvaluator::getViableTargets(&mechMock); EXPECT_EQ(result.size(), 1); EXPECT_TRUE(result.front().empty()); } TEST_F(SpellTargetEvaluatorTest, ReturnsSuspectibleCreaturePositionsIfSpellTargetsCreatures) { spellTargetTypes({AimType::CREATURE}); addStack(BattleHex(1), casterSide); addStack(BattleHex(2), enemySide, false); addStack(BattleHex(3), casterSide); ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits)); confirmResults({{BattleHex(1)}, {BattleHex(3)}}); } TEST_F(SpellTargetEvaluatorTest, ReturnsSuspectibleCreaturePositionsAndSingleRandomSurroundingHexForEachStackIfNeutralLocationSpell) { spellTargetTypes({AimType::LOCATION}); ON_CALL(mechMock, isNeutralSpell()).WillByDefault(Return(true)); addStack(BattleHex(72), casterSide); addStack(BattleHex(159), enemySide, false); addStack(BattleHex(23), casterSide); ON_CALL(battleMock, battleGetAllUnits(Eq(false))).WillByDefault(Return(allUnits)); confirmResults( {{BattleHex(72)}, BattleHex(72).getAllNeighbouringTiles().toVector(), BattleHex(159).getAllNeighbouringTiles().toVector(), {BattleHex(23)}, BattleHex(23).getAllNeighbouringTiles().toVector()} ); } TEST_F(SpellTargetEvaluatorTest, ReturnsOneCaseOfEachOptimalCastIfNegativeLocationSpell) { spellTargetTypes({AimType::LOCATION}); ON_CALL(mechMock, isNegativeSpell()).WillByDefault(Return(true)); auto * enemyStack1 = addStack(BattleHex(90), enemySide); auto * enemyStack2 = addStack(BattleHex(106), enemySide); auto * enemyStack3 = addStack(BattleHex(1), enemySide); auto * enemyStack4 = addStack(BattleHex(37), enemySide); auto * enemyStack5 = addStack(BattleHex(41), enemySide); auto * alliedStack1 = addStack(BattleHex(107), casterSide); auto * alliedStack2 = addStack(BattleHex(19), casterSide); //optimal setAffectedStacksForCast(BattleHex(71), {enemyStack1, enemyStack2}); setAffectedStacksForCast(BattleHex(88), {enemyStack1, enemyStack2}); //suboptimal setAffectedStacksForCast(BattleHex(55), {enemyStack1}); setAffectedStacksForCast(BattleHex(89), {enemyStack1, enemyStack2, alliedStack1}); //optimal setAffectedStacksForCast(BattleHex(18), {enemyStack3, alliedStack2}); setAffectedStacksForCast(BattleHex(2), {enemyStack3, alliedStack2}); //suboptimal setAffectedStacksForCast(BattleHex(53), {alliedStack2}); //optimal setAffectedStacksForCast(BattleHex(39), {enemyStack4, enemyStack5}); //suboptimal setAffectedStacksForCast(BattleHex(21), {enemyStack4}); setAffectedStacksForCast(BattleHex(25), {enemyStack5}); confirmResults({ {BattleHex(71), BattleHex(88)}, {BattleHex(2), BattleHex(18)}, {BattleHex(39)} }); } }