|
@@ -0,0 +1,302 @@
|
|
|
+/*
|
|
|
+ * BattleHexTest.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 "../lib/bonuses/CBonusSystemNode.h"
|
|
|
+#include "../lib/bonuses/BonusEnum.h"
|
|
|
+#include "../lib/bonuses/Limiters.h"
|
|
|
+#include "../lib/bonuses/Propagators.h"
|
|
|
+#include "../lib/bonuses/Updaters.h"
|
|
|
+
|
|
|
+namespace test
|
|
|
+{
|
|
|
+
|
|
|
+using namespace ::testing;
|
|
|
+
|
|
|
+class TestBonusSystemNode : public CBonusSystemNode
|
|
|
+{
|
|
|
+public:
|
|
|
+ using CBonusSystemNode::CBonusSystemNode;
|
|
|
+
|
|
|
+ MOCK_CONST_METHOD0(getOwner, PlayerColor());
|
|
|
+
|
|
|
+ void setPlayer(PlayerColor player)
|
|
|
+ {
|
|
|
+ EXPECT_CALL(*this, getOwner()).WillRepeatedly(Return(player));
|
|
|
+ }
|
|
|
+
|
|
|
+ void giveIntimidationSkill()
|
|
|
+ {
|
|
|
+ auto intimidationBonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::SECONDARY_SKILL, -1, SecondarySkill(0));
|
|
|
+ intimidationBonus->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE);
|
|
|
+ intimidationBonus->propagationUpdater = std::make_shared<OwnerUpdater>();
|
|
|
+ intimidationBonus->limiter = std::make_shared<OppositeSideLimiter>();
|
|
|
+
|
|
|
+ addNewBonus(intimidationBonus);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+class BonusSystemTest : public Test
|
|
|
+{
|
|
|
+protected:
|
|
|
+
|
|
|
+ CBonusSystemNode global{BonusNodeType::GLOBAL_EFFECTS};
|
|
|
+ CBonusSystemNode battle{BonusNodeType::BATTLE_WIDE};
|
|
|
+
|
|
|
+ TestBonusSystemNode playerRed{BonusNodeType::PLAYER};
|
|
|
+ TestBonusSystemNode playerBlue{BonusNodeType::PLAYER};
|
|
|
+
|
|
|
+ TestBonusSystemNode heroAine{BonusNodeType::HERO};
|
|
|
+ TestBonusSystemNode heroBron{BonusNodeType::HERO};
|
|
|
+
|
|
|
+ CBonusSystemNode artifactTypeRing{BonusNodeType::ARTIFACT};
|
|
|
+ CBonusSystemNode artifactTypeSword{BonusNodeType::ARTIFACT};
|
|
|
+ CBonusSystemNode artifactTypeArmor{BonusNodeType::ARTIFACT};
|
|
|
+ CBonusSystemNode artifactTypeOrb{BonusNodeType::ARTIFACT};
|
|
|
+ CBonusSystemNode artifactTypeLegion{BonusNodeType::ARTIFACT};
|
|
|
+
|
|
|
+ CBonusSystemNode creatureAngel{BonusNodeType::CREATURE};
|
|
|
+ CBonusSystemNode creatureDevil{BonusNodeType::CREATURE};
|
|
|
+ CBonusSystemNode creaturePikeman{BonusNodeType::CREATURE};
|
|
|
+
|
|
|
+ void startBattle()
|
|
|
+ {
|
|
|
+ heroAine.attachTo(battle);
|
|
|
+ heroBron.attachTo(battle);
|
|
|
+ }
|
|
|
+
|
|
|
+ void SetUp() override
|
|
|
+ {
|
|
|
+ playerRed.attachTo(global);
|
|
|
+ playerBlue.attachTo(global);
|
|
|
+
|
|
|
+ heroAine.attachTo(playerRed);
|
|
|
+ heroBron.attachTo(playerBlue);
|
|
|
+
|
|
|
+ playerRed.setPlayer(PlayerColor(0));
|
|
|
+ playerBlue.setPlayer(PlayerColor(1));
|
|
|
+ heroAine.setPlayer(PlayerColor(0));
|
|
|
+ heroBron.setPlayer(PlayerColor(1));
|
|
|
+
|
|
|
+ auto spellpowerBonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::ARTIFACT, 1, ArtifactID(0), PrimarySkill::SPELL_POWER);
|
|
|
+ auto spellpowerBonus4 = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::ARTIFACT, 4, ArtifactID(1), PrimarySkill::SPELL_POWER);
|
|
|
+ auto attackBonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::PRIMARY_SKILL, BonusSource::ARTIFACT, 1, ArtifactID(2), PrimarySkill::ATTACK);
|
|
|
+ auto blockMagicBonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::BLOCK_ALL_MAGIC, BonusSource::ARTIFACT, 1, ArtifactID(3));
|
|
|
+ auto legionBonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::CREATURE_GROWTH, BonusSource::ARTIFACT, 4, ArtifactID(4), BonusCustomSubtype::creatureLevel(3));
|
|
|
+
|
|
|
+ auto luckBonusEnemies = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::LUCK, BonusSource::CREATURE_ABILITY, -1, CreatureID(0));
|
|
|
+ auto moraleBonusAllies = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::CREATURE_ABILITY, 1, CreatureID(1));
|
|
|
+
|
|
|
+ luckBonusEnemies->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE);
|
|
|
+ luckBonusEnemies->propagationUpdater = std::make_shared<OwnerUpdater>();
|
|
|
+ luckBonusEnemies->limiter = std::make_shared<OppositeSideLimiter>();
|
|
|
+ luckBonusEnemies->stacking = "devil";
|
|
|
+ moraleBonusAllies->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::ARMY);
|
|
|
+ moraleBonusAllies->stacking = "angel";
|
|
|
+ blockMagicBonus->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::BATTLE_WIDE);
|
|
|
+ legionBonus->propagator = std::make_shared<CPropagatorNodeType>(BonusNodeType::TOWN_AND_VISITOR);
|
|
|
+
|
|
|
+ artifactTypeRing.addNewBonus(spellpowerBonus);
|
|
|
+ artifactTypeSword.addNewBonus(attackBonus);
|
|
|
+ artifactTypeArmor.addNewBonus(spellpowerBonus4);
|
|
|
+ artifactTypeOrb.addNewBonus(blockMagicBonus);
|
|
|
+ artifactTypeLegion.addNewBonus(legionBonus);
|
|
|
+ creatureAngel.addNewBonus(moraleBonusAllies);
|
|
|
+ creatureDevil.addNewBonus(luckBonusEnemies);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, multipleBonusSources)
|
|
|
+{
|
|
|
+ CBonusSystemNode ring1{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+ CBonusSystemNode ring2{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+ CBonusSystemNode armor{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+
|
|
|
+ ring1.attachToSource(artifactTypeRing);
|
|
|
+ ring2.attachToSource(artifactTypeRing);
|
|
|
+ armor.attachToSource(artifactTypeArmor);
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER), 0);
|
|
|
+
|
|
|
+ // single bonus produces single effect
|
|
|
+ heroAine.attachToSource(ring1);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER), 1);
|
|
|
+
|
|
|
+ // same bonus applied twice produces single effect
|
|
|
+ heroAine.attachToSource(ring2);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER), 1);
|
|
|
+
|
|
|
+ // different bonuses stack
|
|
|
+ heroAine.attachToSource(armor);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::PRIMARY_SKILL, PrimarySkill::SPELL_POWER), 1+4);
|
|
|
+
|
|
|
+ heroAine.detachFromSource(ring1);
|
|
|
+ heroAine.detachFromSource(ring2);
|
|
|
+ heroAine.detachFromSource(armor);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, battlewidePropagationToAllies)
|
|
|
+{
|
|
|
+ CBonusSystemNode angel1{BonusNodeType::STACK_INSTANCE};
|
|
|
+ CBonusSystemNode angel2{BonusNodeType::STACK_INSTANCE};
|
|
|
+
|
|
|
+ CBonusSystemNode pikemanAlly{BonusNodeType::STACK_INSTANCE};
|
|
|
+ CBonusSystemNode pikemanEnemy{BonusNodeType::STACK_INSTANCE};
|
|
|
+
|
|
|
+ angel1.attachToSource(creatureAngel);
|
|
|
+ angel2.attachToSource(creatureAngel);
|
|
|
+
|
|
|
+ pikemanAlly.attachToSource(creaturePikeman);
|
|
|
+ pikemanEnemy.attachToSource(creaturePikeman);
|
|
|
+
|
|
|
+ pikemanAlly.attachTo(heroAine);
|
|
|
+ pikemanEnemy.attachTo(heroBron);
|
|
|
+
|
|
|
+ startBattle();
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+
|
|
|
+ angel1.attachTo(heroAine);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::MORALE), 1);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::MORALE), 1);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+
|
|
|
+ angel2.attachTo(heroAine);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::MORALE), 1);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::MORALE), 1);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, battlewidePropagationToAll)
|
|
|
+{
|
|
|
+ CBonusSystemNode orb{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+ orb.attachToSource(artifactTypeOrb);
|
|
|
+
|
|
|
+ heroAine.attachToSource(orb);
|
|
|
+ startBattle();
|
|
|
+
|
|
|
+ EXPECT_TRUE(heroAine.hasBonusOfType(BonusType::BLOCK_ALL_MAGIC));
|
|
|
+ EXPECT_TRUE(heroBron.hasBonusOfType(BonusType::BLOCK_ALL_MAGIC));
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, battlewidePropagationToEnemies)
|
|
|
+{
|
|
|
+ TestBonusSystemNode devil1{BonusNodeType::STACK_INSTANCE};
|
|
|
+ TestBonusSystemNode devil2{BonusNodeType::STACK_INSTANCE};
|
|
|
+
|
|
|
+ TestBonusSystemNode pikemanAlly{BonusNodeType::STACK_INSTANCE};
|
|
|
+ TestBonusSystemNode pikemanEnemy{BonusNodeType::STACK_INSTANCE};
|
|
|
+
|
|
|
+ devil1.setPlayer(PlayerColor(0));
|
|
|
+ devil2.setPlayer(PlayerColor(0));
|
|
|
+ pikemanAlly.setPlayer(PlayerColor(0));
|
|
|
+ pikemanEnemy.setPlayer(PlayerColor(1));
|
|
|
+
|
|
|
+ devil1.attachToSource(creatureDevil);
|
|
|
+ devil2.attachToSource(creatureDevil);
|
|
|
+
|
|
|
+ pikemanAlly.attachToSource(creaturePikeman);
|
|
|
+ pikemanEnemy.attachToSource(creaturePikeman);
|
|
|
+
|
|
|
+ pikemanAlly.attachTo(heroAine);
|
|
|
+ pikemanEnemy.attachTo(heroBron);
|
|
|
+
|
|
|
+ startBattle();
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+
|
|
|
+ devil1.attachTo(heroAine);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+
|
|
|
+ devil2.attachTo(heroAine);
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::LUCK), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::LUCK), -1);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, battlewideSkillPropagationToEnemies)
|
|
|
+{
|
|
|
+ TestBonusSystemNode pikemanAlly{BonusNodeType::STACK_INSTANCE};
|
|
|
+ TestBonusSystemNode pikemanEnemy{BonusNodeType::STACK_INSTANCE};
|
|
|
+
|
|
|
+ // #5975 - skill that affects only enemies in battle is broken,
|
|
|
+ // but only if hero has *any* artifact (including obligatory catapult/spellbook, so always)
|
|
|
+ CBonusSystemNode armor{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+ armor.attachToSource(artifactTypeArmor);
|
|
|
+ heroAine.attachToSource(armor);
|
|
|
+
|
|
|
+ pikemanAlly.setPlayer(PlayerColor(0));
|
|
|
+ pikemanEnemy.setPlayer(PlayerColor(1));
|
|
|
+
|
|
|
+ pikemanAlly.attachToSource(creaturePikeman);
|
|
|
+ pikemanEnemy.attachToSource(creaturePikeman);
|
|
|
+
|
|
|
+ heroAine.giveIntimidationSkill();
|
|
|
+
|
|
|
+ pikemanAlly.attachTo(heroAine);
|
|
|
+ pikemanEnemy.attachTo(heroBron);
|
|
|
+
|
|
|
+ startBattle();
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::MORALE), -1);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::MORALE), -1);
|
|
|
+
|
|
|
+ heroAine.nodeHasChanged();
|
|
|
+
|
|
|
+ EXPECT_EQ(heroAine.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(heroBron.valOfBonuses(BonusType::MORALE), -1);
|
|
|
+ EXPECT_EQ(pikemanAlly.valOfBonuses(BonusType::MORALE), 0);
|
|
|
+ EXPECT_EQ(pikemanEnemy.valOfBonuses(BonusType::MORALE), -1);
|
|
|
+}
|
|
|
+
|
|
|
+TEST_F(BonusSystemTest, legionPieces)
|
|
|
+{
|
|
|
+ TestBonusSystemNode townAndVisitor{BonusNodeType::TOWN_AND_VISITOR};
|
|
|
+ TestBonusSystemNode town{BonusNodeType::TOWN_AND_VISITOR};
|
|
|
+ town.attachTo(townAndVisitor);
|
|
|
+
|
|
|
+ CBonusSystemNode legion{BonusNodeType::ARTIFACT_INSTANCE};
|
|
|
+ legion.attachToSource(artifactTypeLegion);
|
|
|
+
|
|
|
+ heroAine.attachToSource(legion);
|
|
|
+
|
|
|
+ heroAine.attachTo(townAndVisitor);
|
|
|
+ EXPECT_EQ(town.valOfBonuses(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(3)), 4);
|
|
|
+
|
|
|
+ heroAine.detachFromSource(legion);
|
|
|
+ EXPECT_EQ(town.valOfBonuses(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(3)), 0);
|
|
|
+
|
|
|
+ heroAine.attachToSource(legion);
|
|
|
+ EXPECT_EQ(town.valOfBonuses(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(3)), 4);
|
|
|
+
|
|
|
+ heroAine.detachFrom(townAndVisitor);
|
|
|
+ EXPECT_EQ(town.valOfBonuses(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(3)), 0);
|
|
|
+}
|
|
|
+
|
|
|
+}
|