Files
vcmi/test/spells/effects/ObstacleTest.cpp
T

368 lines
10 KiB
C++

/*
* ObstacleTest.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 "EffectFixture.h"
#include "../../../lib/battle/CObstacleInstance.h"
#include "../../../lib/json/JsonNode.h"
#include "../../../lib/networkPacks/PacksForClientBattle.h"
#include "../../mock/mock_CStack.h"
namespace test
{
using namespace ::spells;
using namespace ::spells::effects;
using namespace ::testing;
class ObstacleTest : public Test, public EffectFixture
{
public:
ObstacleTest()
: EffectFixture("core:obstacle")
{
}
protected:
void SetUp() override
{
EffectFixture::setUp();
}
};
TEST_F(ObstacleTest, ApplicableGeneralByDefault)
{
setupEffect(JsonNode());
EXPECT_CALL(mechanicsMock, adaptProblem(_, _)).Times(0);
EXPECT_TRUE(subject->applicableGeneral(problemMock, &mechanicsMock));
}
TEST_F(ObstacleTest, ApplicableTargetWhenHexClear)
{
setupEffect(JsonNode());
battleFake->setupEmptyBattlefield();
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, requiresClearTiles()).WillRepeatedly(Return(true));
EXPECT_CALL(*battleFake, getUnitsIf(_)).Times(AtLeast(0));
Target target;
target.emplace_back(BattleHex(5, 5));
EXPECT_TRUE(subject->applicableTarget(problemMock, &mechanicsMock, target));
}
TEST_F(ObstacleTest, NotApplicableTargetWhenHexBlockedByUnit)
{
setupEffect(JsonNode());
battleFake->setupEmptyBattlefield();
const BattleHex hex(5, 5);
auto & unit = unitsFake.add(BattleSide::ATTACKER);
EXPECT_CALL(unit, getPosition()).WillRepeatedly(Return(hex));
EXPECT_CALL(unit, doubleWide()).WillRepeatedly(Return(false));
EXPECT_CALL(unit, alive()).WillRepeatedly(Return(true));
EXPECT_CALL(unit, isGhost()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, requiresClearTiles()).WillRepeatedly(Return(true));
EXPECT_CALL(mechanicsMock, adaptProblem(Eq(ESpellCastProblem::NO_APPROPRIATE_TARGET), _))
.WillRepeatedly(Return(false));
EXPECT_CALL(*battleFake, getUnitsIf(_)).Times(AtLeast(0));
Target target;
target.emplace_back(hex);
EXPECT_FALSE(subject->applicableTarget(problemMock, &mechanicsMock, target));
}
TEST_F(ObstacleTest, NotApplicableTargetWhenEmptyTarget)
{
setupEffect(JsonNode());
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, requiresClearTiles()).WillRepeatedly(Return(true));
EXPECT_CALL(mechanicsMock, adaptProblem(Eq(ESpellCastProblem::NO_APPROPRIATE_TARGET), _))
.WillRepeatedly(Return(false));
EXPECT_FALSE(subject->applicableTarget(problemMock, &mechanicsMock, Target()));
}
TEST_F(ObstacleTest, NotApplicableWhenHiddenAndHideNativeWithEnemyNativeStack)
{
JsonNode config;
config["hidden"].Bool() = true;
config["hideNative"].Bool() = true;
setupEffect(config);
MockCStack nativeStack;
nativeStack.makeNativeOnSide(BattleSide::DEFENDER);
battleFake->setupNativeStacks({ &nativeStack }, TerrainId(0));
mechanicsMock.casterSide = BattleSide::ATTACKER;
EXPECT_CALL(mechanicsMock, adaptProblem(Eq(ESpellCastProblem::NO_APPROPRIATE_TARGET), _))
.WillOnce(Return(false));
EXPECT_FALSE(subject->applicableGeneral(problemMock, &mechanicsMock));
}
TEST_F(ObstacleTest, ApplicableWhenHiddenAndHideNativeWithoutEnemyNativeStack)
{
JsonNode config;
config["hidden"].Bool() = true;
config["hideNative"].Bool() = true;
setupEffect(config);
battleFake->setupNativeStacks(TStacks(), TerrainId(0));
EXPECT_CALL(mechanicsMock, adaptProblem(_, _)).Times(0);
EXPECT_TRUE(subject->applicableGeneral(problemMock, &mechanicsMock));
}
TEST_F(ObstacleTest, MassiveSkipsHexCheck)
{
setupEffect(JsonNode());
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(true));
EXPECT_TRUE(subject->applicableTarget(problemMock, &mechanicsMock, Target()));
}
class ObstacleApplyTest : public Test, public EffectFixture
{
public:
ObstacleApplyTest()
: EffectFixture("core:obstacle")
{
}
BattleObstaclesChanged capturedPack;
void captureObstaclePack()
{
EXPECT_CALL(serverMock, apply(Matcher<BattleObstaclesChanged &>(_)))
.Times(AnyNumber())
.WillRepeatedly(Invoke([this](const BattleObstaclesChanged & pack)
{
for(const auto & change : pack.changes)
capturedPack.changes.push_back(change);
}));
}
void setDefaultApplyExpectations()
{
EXPECT_CALL(mechanicsMock, getEffectPower()).WillRepeatedly(Return(5));
EXPECT_CALL(mechanicsMock, getEffectLevel()).WillRepeatedly(Return(2));
EXPECT_CALL(mechanicsMock, getSpell()).WillRepeatedly(Return(&spellStub));
EXPECT_CALL(spellStub, getId()).WillRepeatedly(Return(SpellID(13)));
}
protected:
void SetUp() override
{
EffectFixture::setUp();
}
};
TEST_F(ObstacleApplyTest, PlacesSingleObstacleAtTarget)
{
JsonNode config;
config["turnsRemaining"].Integer() = 5;
setupEffect(config);
battleFake->setupEmptyBattlefield();
setDefaultApplyExpectations();
const BattleHex hex(7, 5);
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
captureObstaclePack();
Target target;
target.emplace_back(hex);
subject->apply(&serverMock, &mechanicsMock, target);
ASSERT_EQ(capturedPack.changes.size(), 1u);
EXPECT_EQ(capturedPack.changes[0].operation, BattleChanges::EOperation::ADD);
SpellCreatedObstacle deserialized;
deserialized.fromInfo(capturedPack.changes[0]);
EXPECT_EQ(deserialized.pos, hex);
EXPECT_EQ(deserialized.casterSide, BattleSide::ATTACKER);
EXPECT_EQ(deserialized.turnsRemaining, 5);
EXPECT_EQ(deserialized.customSize.size(), 1u);
EXPECT_TRUE(deserialized.customSize.contains(hex));
}
TEST_F(ObstacleApplyTest, PlacesObstacleWithMultiHexShape)
{
JsonNode config;
JsonNode shape;
JsonNode firstShapeEntry;
firstShapeEntry.Vector().emplace_back(); // empty direction list ⇒ NONE
JsonNode secondShapeEntry;
JsonNode trDir;
trDir.String() = "TR";
secondShapeEntry.Vector().push_back(trDir);
shape.Vector().push_back(firstShapeEntry);
shape.Vector().push_back(secondShapeEntry);
config["attacker"]["shape"] = shape;
setupEffect(config);
battleFake->setupEmptyBattlefield();
setDefaultApplyExpectations();
const BattleHex hex(7, 5);
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
captureObstaclePack();
Target target;
target.emplace_back(hex);
subject->apply(&serverMock, &mechanicsMock, target);
ASSERT_EQ(capturedPack.changes.size(), 1u);
SpellCreatedObstacle deserialized;
deserialized.fromInfo(capturedPack.changes[0]);
BattleHex trNeighbour = hex;
trNeighbour.moveInDirection(BattleHex::EDir::TOP_RIGHT, false);
EXPECT_EQ(deserialized.customSize.size(), 2u);
EXPECT_TRUE(deserialized.customSize.contains(hex));
EXPECT_TRUE(deserialized.customSize.contains(trNeighbour));
}
TEST_F(ObstacleApplyTest, PlacesOneObstaclePerTargetHex)
{
setupEffect(JsonNode());
battleFake->setupEmptyBattlefield();
setDefaultApplyExpectations();
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
captureObstaclePack();
Target target;
target.emplace_back(BattleHex(5, 5));
target.emplace_back(BattleHex(6, 5));
target.emplace_back(BattleHex(7, 5));
subject->apply(&serverMock, &mechanicsMock, target);
ASSERT_EQ(capturedPack.changes.size(), 3u);
for(const auto & change : capturedPack.changes)
EXPECT_EQ(change.operation, BattleChanges::EOperation::ADD);
}
TEST_F(ObstacleApplyTest, PatchCountLimitedByAvailableTiles)
{
JsonNode config;
config["patchCount"].Integer() = 10;
setupEffect(config);
battleFake->setupEmptyBattlefield();
setDefaultApplyExpectations();
const BattleHex blockedHex(5, 5);
auto & unit = unitsFake.add(BattleSide::ATTACKER);
EXPECT_CALL(unit, getPosition()).WillRepeatedly(Return(blockedHex));
EXPECT_CALL(unit, doubleWide()).WillRepeatedly(Return(false));
EXPECT_CALL(unit, alive()).WillRepeatedly(Return(true));
EXPECT_CALL(unit, isGhost()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
EXPECT_CALL(*battleFake, getUnitsIf(_)).Times(AtLeast(0));
setupDefaultRNG();
captureObstaclePack();
Target target;
target.emplace_back(blockedHex);
target.emplace_back(BattleHex(6, 5));
target.emplace_back(BattleHex(7, 5));
subject->apply(&serverMock, &mechanicsMock, target);
ASSERT_EQ(capturedPack.changes.size(), 2u);
}
TEST_F(ObstacleApplyTest, NoServerCallWhenNoAvailableTiles)
{
JsonNode config;
config["patchCount"].Integer() = 4;
setupEffect(config);
battleFake->setupEmptyBattlefield();
const BattleHex blockedHex(5, 5);
auto & unit = unitsFake.add(BattleSide::ATTACKER);
EXPECT_CALL(unit, getPosition()).WillRepeatedly(Return(blockedHex));
EXPECT_CALL(unit, doubleWide()).WillRepeatedly(Return(false));
EXPECT_CALL(unit, alive()).WillRepeatedly(Return(true));
EXPECT_CALL(unit, isGhost()).WillRepeatedly(Return(false));
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
EXPECT_CALL(*battleFake, getUnitsIf(_)).Times(AtLeast(0));
setupDefaultRNG();
EXPECT_CALL(serverMock, apply(Matcher<BattleObstaclesChanged &>(_))).Times(0);
Target target;
target.emplace_back(blockedHex);
subject->apply(&serverMock, &mechanicsMock, target);
}
TEST_F(ObstacleApplyTest, UsesDefenderSideOptions)
{
JsonNode config;
config["attacker"]["shape"].Vector().emplace_back();
config["attacker"]["shape"].Vector()[0].Vector().emplace_back();
config["attacker"]["shape"].Vector()[0].Vector()[0].String() = "TL";
config["defender"]["shape"].Vector().emplace_back();
config["defender"]["shape"].Vector()[0].Vector().emplace_back();
config["defender"]["shape"].Vector()[0].Vector()[0].String() = "TR";
setupEffect(config);
battleFake->setupEmptyBattlefield();
setDefaultApplyExpectations();
mechanicsMock.casterSide = BattleSide::DEFENDER;
const BattleHex hex(7, 5);
EXPECT_CALL(mechanicsMock, isMassive()).WillRepeatedly(Return(false));
captureObstaclePack();
Target target;
target.emplace_back(hex);
subject->apply(&serverMock, &mechanicsMock, target);
ASSERT_EQ(capturedPack.changes.size(), 1u);
EXPECT_EQ(capturedPack.changes[0].operation, BattleChanges::EOperation::ADD);
SpellCreatedObstacle deserialized;
deserialized.fromInfo(capturedPack.changes[0]);
BattleHex defenderShapeNeighbour = hex;
defenderShapeNeighbour.moveInDirection(BattleHex::EDir::TOP_RIGHT, false);
EXPECT_EQ(deserialized.casterSide, BattleSide::DEFENDER);
EXPECT_EQ(deserialized.customSize.size(), 1u);
EXPECT_TRUE(deserialized.customSize.contains(defenderShapeNeighbour));
}
}