From 8724181a0f9d4fecffecaa38634bdee95625ecd9 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Sat, 19 Aug 2023 20:19:59 +0300 Subject: [PATCH] vcmi: spell resistance rework Now instead of XXX_IMMUNITY bonuses we have 2 bonuses with spellSchool subtype: SPELL_SCHOOL_IMMUNITY and NEGATIVE_EFFECT_IMMUNITY. All previous bonuses of subtype 0 is covered by SPELL_SCHOOL_IMMUNITY, and all previous bonuses of subtype 1 is covered by NEGATIVE_EFFECT_IMMUNITY. Unit tests are updated accordingly. --- client/windows/CSpellWindow.cpp | 4 +- include/vcmi/spells/Spell.h | 3 +- lib/CBonusTypeHandler.cpp | 75 ++++++++----------- lib/CCreatureHandler.cpp | 24 +++--- lib/bonuses/BonusEnum.h | 6 +- lib/mapObjects/CGHeroInstance.cpp | 14 ++-- lib/spells/CSpellHandler.cpp | 14 ++-- lib/spells/CSpellHandler.h | 3 +- lib/spells/ISpellMechanics.cpp | 12 --- lib/spells/ISpellMechanics.h | 4 - lib/spells/TargetCondition.cpp | 18 +++-- lib/spells/effects/Damage.cpp | 4 +- server/battles/BattleActionProcessor.cpp | 4 +- test/mock/mock_spells_Mechanics.h | 2 - .../targetConditions/BonusConditionTest.cpp | 2 +- .../ElementalConditionTest.cpp | 28 ++++--- .../TargetConditionItemFixture.h | 3 + 17 files changed, 99 insertions(+), 121 deletions(-) diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 1ed8e0153..9184b58ab 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -122,9 +122,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m ++sitesPerOurTab[4]; - spell->forEachSchool([&sitesPerOurTab](const spells::SchoolInfo & school, bool & stop) + spell->forEachSchool([&sitesPerOurTab](const ESpellSchool & school, bool & stop) { - ++sitesPerOurTab[(ui8)school.id]; + ++sitesPerOurTab[(ui8)school]; }); } if(sitesPerTabAdv[4] % 12 == 0) diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index ba16ad105..4b3eb0d27 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -19,13 +19,12 @@ enum class ESpellSchool: int8_t; namespace spells { -struct SchoolInfo; class Caster; class DLL_LINKAGE Spell: public EntityT { public: - using SchoolCallback = std::function; + using SchoolCallback = std::function; ///calculate spell damage on stack taking caster`s secondary skills into account virtual int64_t calculateDamage(const Caster * caster) const = 0; diff --git a/lib/CBonusTypeHandler.cpp b/lib/CBonusTypeHandler.cpp index 4d5479dd2..1810cdc9b 100644 --- a/lib/CBonusTypeHandler.cpp +++ b/lib/CBonusTypeHandler.cpp @@ -99,60 +99,47 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr & bo fileName = sp->getIconImmune(); break; } - case BonusType::FIRE_IMMUNITY: + case BonusType::SPELL_SCHOOL_IMMUNITY: //for all school + { switch(bonus->subtype) { - case 0: - fileName = "E_SPFIRE.bmp"; - break;//all - case 1: - fileName = "E_SPFIRE1.bmp"; - break;//not positive - case 2: - fileName = "E_FIRE.bmp"; - break;//direct damage - } - break; - case BonusType::WATER_IMMUNITY: - switch(bonus->subtype) - { - case 0: - fileName = "E_SPWATER.bmp"; - break;//all - case 1: - fileName = "E_SPWATER1.bmp"; - break;//not positive - case 2: - fileName = "E_SPCOLD.bmp"; - break;//direct damage - } - break; - case BonusType::AIR_IMMUNITY: - switch(bonus->subtype) - { - case 0: + case SpellSchool(ESpellSchool::AIR): fileName = "E_SPAIR.bmp"; - break;//all - case 1: - fileName = "E_SPAIR1.bmp"; - break;//not positive - case 2: - fileName = "E_LIGHT.bmp"; - break;//direct damage + break; + case SpellSchool(ESpellSchool::FIRE): + fileName = "E_SPFIRE.bmp"; + break; + case SpellSchool(ESpellSchool::WATER): + fileName = "E_SPWATER.bmp"; + break; + case SpellSchool(ESpellSchool::EARTH): + fileName = "E_SPEATH.bmp"; + break; } break; - case BonusType::EARTH_IMMUNITY: + } + // fileName = "E_FIRE.bmp"; //fire damage + // fileName = "E_COLD.bmp"; //cold damage + // fileName = "E_LIGHT.bmp"; //lightning damage + case BonusType::NEGATIVE_EFFECTS_IMMUNITY: + { switch(bonus->subtype) { - case 0: - fileName = "E_SPEATH.bmp"; - break;//all - case 1: - case 2://no specific icon for direct damage immunity + case SpellSchool(ESpellSchool::AIR): + fileName = "E_SPAIR1.bmp"; + break; + case SpellSchool(ESpellSchool::FIRE): + fileName = "E_SPFIRE1.bmp"; + break; + case SpellSchool(ESpellSchool::WATER): + fileName = "E_SPWATER1.bmp"; + break; + case SpellSchool(ESpellSchool::EARTH): fileName = "E_SPEATH1.bmp"; - break;//not positive + break; } break; + } case BonusType::LEVEL_SPELL_IMMUNITY: { if(vstd::iswithin(bonus->val, 1, 5)) diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 1e3802408..9640eb199 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1181,8 +1181,8 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars b.val = GameConstants::SPELL_LEVELS; //in case someone adds higher level spells? break; case 'F': - b.type = BonusType::FIRE_IMMUNITY; - b.subtype = 1; //not positive + b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::FIRE); break; case 'O': b.type = BonusType::SPELL_DAMAGE_REDUCTION; @@ -1190,12 +1190,12 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars b.val = 100; //Full damage immunity break; case 'f': - b.type = BonusType::FIRE_IMMUNITY; - b.subtype = 0; //all + b.type = BonusType::SPELL_SCHOOL_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::FIRE); break; case 'C': - b.type = BonusType::WATER_IMMUNITY; - b.subtype = 1; //not positive + b.type = BonusType::NEGATIVE_EFFECTS_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::WATER); break; case 'W': b.type = BonusType::SPELL_DAMAGE_REDUCTION; @@ -1203,8 +1203,8 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars b.val = 100; //Full damage immunity break; case 'w': - b.type = BonusType::WATER_IMMUNITY; - b.subtype = 0; //all + b.type = BonusType::SPELL_SCHOOL_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::WATER); break; case 'E': b.type = BonusType::SPELL_DAMAGE_REDUCTION; @@ -1212,8 +1212,8 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars b.val = 100; //Full damage immunity break; case 'e': - b.type = BonusType::EARTH_IMMUNITY; - b.subtype = 0; //all + b.type = BonusType::SPELL_SCHOOL_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::EARTH); break; case 'A': b.type = BonusType::SPELL_DAMAGE_REDUCTION; @@ -1221,8 +1221,8 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars b.val = 100; //Full damage immunity break; case 'a': - b.type = BonusType::AIR_IMMUNITY; - b.subtype = 0; //all + b.type = BonusType::SPELL_SCHOOL_IMMUNITY; + b.subtype = SpellSchool(ESpellSchool::AIR); break; case 'D': b.type = BonusType::SPELL_DAMAGE_REDUCTION; diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index 8d43a48ac..7f1489e79 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -74,10 +74,6 @@ class JsonNode; BONUS_NAME(SPELL_LIKE_ATTACK) /*subtype - spell, value - spell level; range is taken from spell, but damage from creature; eg. magog*/ \ BONUS_NAME(THREE_HEADED_ATTACK) /*eg. cerberus*/ \ BONUS_NAME(GENERAL_DAMAGE_PREMY) \ - BONUS_NAME(FIRE_IMMUNITY) /*subtype 0 - all, 1 - all except positive*/ \ - BONUS_NAME(WATER_IMMUNITY) \ - BONUS_NAME(EARTH_IMMUNITY) \ - BONUS_NAME(AIR_IMMUNITY) \ BONUS_NAME(MIND_IMMUNITY) \ BONUS_NAME(FIRE_SHIELD) \ BONUS_NAME(UNDEAD) \ @@ -175,6 +171,8 @@ class JsonNode; BONUS_NAME(BONUS_DAMAGE_PERCENTAGE) /*If hero can grant conditional damage to creature, value is percentage, subtype is creatureID*/\ BONUS_NAME(BONUS_DAMAGE_CHANCE) /*If hero can grant additional damage to creature, value is chance, subtype is creatureID*/\ BONUS_NAME(MAX_LEARNABLE_SPELL_LEVEL) /*This can work as wisdom before. val = max learnable spell level*/\ + BONUS_NAME(SPELL_SCHOOL_IMMUNITY) /*This bonus will work as spell school immunity for all spells, subtype - spell school: 0 - air, 1 - fire, 2 - water, 3 - earth. Any is not handled for reducing overlap from LEVEL_SPELL_IMMUNITY*/\ + BONUS_NAME(NEGATIVE_EFFECTS_IMMUNITY) /*This bonus will work as spell school immunity for negative effects from spells of school, subtype - spell school: -1 - any, 0 - air, 1 - fire, 2 - water, 3 - earth*/\ /* end of list */ diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 218dd4575..81ec80015 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -620,14 +620,14 @@ int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, int32_t { int32_t skill = -1; //skill level - spell->forEachSchool([&, this](const spells::SchoolInfo & cnf, bool & stop) + spell->forEachSchool([&, this](const ESpellSchool & cnf, bool & stop) { - int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, cnf.id); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies) + int32_t thisSchool = valOfBonuses(BonusType::MAGIC_SCHOOL_SKILL, SpellSchool(cnf)); //FIXME: Bonus shouldn't be additive (Witchking Artifacts : Crown of Skies) if(thisSchool > skill) { skill = thisSchool; if(outSelectedSchool) - *outSelectedSchool = static_cast(cnf.id); + *outSelectedSchool = SpellSchool(cnf); } }); @@ -650,9 +650,9 @@ int64_t CGHeroInstance::getSpellBonus(const spells::Spell * spell, int64_t base, int maxSchoolBonus = 0; - spell->forEachSchool([&maxSchoolBonus, this](const spells::SchoolInfo & cnf, bool & stop) + spell->forEachSchool([&maxSchoolBonus, this](const ESpellSchool & cnf, bool & stop) { - vstd::amax(maxSchoolBonus, valOfBonuses(BonusType::SPELL_DAMAGE, cnf.id)); + vstd::amax(maxSchoolBonus, valOfBonuses(BonusType::SPELL_DAMAGE, SpellSchool(cnf))); }); base = static_cast(base * (100 + maxSchoolBonus) / 100.0); @@ -739,9 +739,9 @@ bool CGHeroInstance::canCastThisSpell(const spells::Spell * spell) const bool schoolBonus = false; - spell->forEachSchool([this, &schoolBonus](const spells::SchoolInfo & cnf, bool & stop) + spell->forEachSchool([this, &schoolBonus](const ESpellSchool & cnf, bool & stop) { - if(hasBonusOfType(BonusType::SPELLS_OF_SCHOOL, cnf.id)) + if(hasBonusOfType(BonusType::SPELLS_OF_SCHOOL, SpellSchool(cnf))) { schoolBonus = stop = true; } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index c53af381f..085f95df9 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -43,22 +43,18 @@ const spells::SchoolInfo SCHOOL[4] = { { ESpellSchool::AIR, - BonusType::AIR_IMMUNITY, "air" }, { ESpellSchool::FIRE, - BonusType::FIRE_IMMUNITY, "fire" }, { ESpellSchool::WATER, - BonusType::WATER_IMMUNITY, "water" }, { ESpellSchool::EARTH, - BonusType::EARTH_IMMUNITY, "earth" } }; @@ -153,7 +149,7 @@ spells::AimType CSpell::getTargetType() const return targetType; } -void CSpell::forEachSchool(const std::function& cb) const +void CSpell::forEachSchool(const std::function& cb) const { bool stop = false; for(auto iter : SpellConfig::SCHOOL_ORDER) @@ -161,7 +157,7 @@ void CSpell::forEachSchool(const std::functiongetBonusBearer(); //applying protections - when spell has more then one elements, only one protection should be applied (I think) - forEachSchool([&](const spells::SchoolInfo & cnf, bool & stop) + forEachSchool([&](const ESpellSchool & cnf, bool & stop) { - if(bearer->hasBonusOfType(BonusType::SPELL_DAMAGE_REDUCTION, cnf.id)) + if(bearer->hasBonusOfType(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(cnf))) { - ret *= 100 - bearer->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, cnf.id); + ret *= 100 - bearer->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(cnf)); ret /= 100; stop = true; //only bonus from one school is used } diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 769a3ebb1..ff0ba6998 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -44,7 +44,6 @@ class IBattleCast; struct SchoolInfo { SpellSchool id; //backlink - BonusType immunityBonus; std::string jsonName; }; @@ -216,7 +215,7 @@ public: * * Set stop to true to abort looping */ - void forEachSchool(const std::function & cb) const override; + void forEachSchool(const std::function & cb) const override; spells::AimType getTargetType() const; diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index f8709b882..003b3dc6a 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -620,18 +620,6 @@ int64_t BaseMechanics::calculateRawEffectValue(int32_t basePowerMultiplier, int3 return owner->calculateRawEffectValue(getEffectLevel(), basePowerMultiplier, levelPowerMultiplier); } -std::vector BaseMechanics::getElementalImmunity() const -{ - std::vector ret; - - owner->forEachSchool([&](const SchoolInfo & cnf, bool & stop) - { - ret.push_back(cnf.immunityBonus); - }); - - return ret; -} - bool BaseMechanics::ownerMatches(const battle::Unit * unit) const { return ownerMatches(unit, owner->getPositiveness()); diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index e540bf20d..efbbc53b8 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -235,8 +235,6 @@ public: virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; - virtual std::vector getElementalImmunity() const = 0; - //Battle facade virtual bool ownerMatches(const battle::Unit * unit) const = 0; virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; @@ -296,8 +294,6 @@ public: int64_t applySpecificSpellBonus(int64_t value) const override; int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; - std::vector getElementalImmunity() const override; - bool ownerMatches(const battle::Unit * unit) const override; bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index ab495ba60..05d9cb39a 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -23,6 +23,8 @@ #include "../serializer/JsonSerializeFormat.h" #include "../VCMI_Lib.h" +#include + VCMI_LIB_NAMESPACE_BEGIN @@ -173,25 +175,25 @@ protected: bool check(const Mechanics * m, const battle::Unit * target) const override { bool elementalImmune = false; + auto bearer = target->getBonusBearer(); - auto filter = m->getElementalImmunity(); - - for(auto element : filter) + m->getSpell()->forEachSchool([&](const ESpellSchool & cnf, bool & stop) { - if(target->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether + if (bearer->hasBonusOfType(BonusType::SPELL_SCHOOL_IMMUNITY, SpellSchool(cnf))) { elementalImmune = true; - break; + stop = true; //only bonus from one school is used } else if(!m->isPositiveSpell()) //negative or indifferent { - if(target->hasBonusOfType(element, 1)) + if (bearer->hasBonusOfType(BonusType::NEGATIVE_EFFECTS_IMMUNITY, SpellSchool(cnf))) { elementalImmune = true; - break; + stop = true; //only bonus from one school is used } } - } + }); + return elementalImmune; } }; diff --git a/lib/spells/effects/Damage.cpp b/lib/spells/effects/Damage.cpp index 93295f93e..fb068a860 100644 --- a/lib/spells/effects/Damage.cpp +++ b/lib/spells/effects/Damage.cpp @@ -87,9 +87,9 @@ bool Damage::isReceptive(const Mechanics * m, const battle::Unit * unit) const bool isImmune = m->getSpell()->isMagical() && (unit->getBonusBearer()->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(ESpellSchool::ANY)) >= 100); //General spell damage immunity //elemental immunity for damage - m->getSpell()->forEachSchool([&](const SchoolInfo & cnf, bool & stop) + m->getSpell()->forEachSchool([&](const ESpellSchool & cnf, bool & stop) { - isImmune |= (unit->getBonusBearer()->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, cnf.id) >= 100); //100% reduction is immunity + isImmune |= (unit->getBonusBearer()->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(cnf)) >= 100); //100% reduction is immunity }); return !isImmune; diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index e32d082f3..e280673e6 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -1331,7 +1331,9 @@ int64_t BattleActionProcessor::applyBattleEffects(BattleAttack & bat, std::share if(!bat.shot() && !def->isClone() && def->hasBonusOfType(BonusType::FIRE_SHIELD) && - !attackerState->hasBonusOfType(BonusType::FIRE_IMMUNITY) && + !attackerState->hasBonusOfType(BonusType::SPELL_SCHOOL_IMMUNITY, SpellSchool(ESpellSchool::FIRE)) && + !attackerState->hasBonusOfType(BonusType::NEGATIVE_EFFECTS_IMMUNITY, SpellSchool(ESpellSchool::FIRE)) && + attackerState->valOfBonuses(BonusType::SPELL_DAMAGE_REDUCTION, SpellSchool(ESpellSchool::FIRE)) < 100 && CStack::isMeleeAttackPossible(attackerState.get(), def) // attacked needs to be adjacent to defender for fire shield to trigger (e.g. Dragon Breath attack) ) { diff --git a/test/mock/mock_spells_Mechanics.h b/test/mock/mock_spells_Mechanics.h index e3efe9950..10b4b2a57 100644 --- a/test/mock/mock_spells_Mechanics.h +++ b/test/mock/mock_spells_Mechanics.h @@ -64,8 +64,6 @@ public: MOCK_CONST_METHOD1(applySpecificSpellBonus,int64_t(int64_t)); MOCK_CONST_METHOD2(calculateRawEffectValue, int64_t(int32_t, int32_t)); - MOCK_CONST_METHOD0(getElementalImmunity, std::vector()); - MOCK_CONST_METHOD1(ownerMatches, bool(const battle::Unit *)); MOCK_CONST_METHOD2(ownerMatches, bool(const battle::Unit *, const boost::logic::tribool)); diff --git a/test/spells/targetConditions/BonusConditionTest.cpp b/test/spells/targetConditions/BonusConditionTest.cpp index 402e38e1d..91fa9b411 100644 --- a/test/spells/targetConditions/BonusConditionTest.cpp +++ b/test/spells/targetConditions/BonusConditionTest.cpp @@ -49,7 +49,7 @@ TEST_F(BonusConditionTest, ReceptiveIfMatchesType) TEST_F(BonusConditionTest, ImmuneIfTypeMismatch) { setDefaultExpectations(); - unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::FIRE_IMMUNITY, BonusSource::OTHER, 0, 0)); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::OTHER, 0, SpellSchool(ESpellSchool::FIRE))); EXPECT_FALSE(subject->isReceptive(&mechanicsMock, &unitMock)); } diff --git a/test/spells/targetConditions/ElementalConditionTest.cpp b/test/spells/targetConditions/ElementalConditionTest.cpp index 0b7fd5262..f57b92d34 100644 --- a/test/spells/targetConditions/ElementalConditionTest.cpp +++ b/test/spells/targetConditions/ElementalConditionTest.cpp @@ -20,18 +20,20 @@ class ElementalConditionTest : public TargetConditionItemTest, public WithParamI { public: bool isPositive; + void setDefaultExpectations() { EXPECT_CALL(unitMock, getAllBonuses(_, _, _, _)).Times(AtLeast(1)); EXPECT_CALL(unitMock, getTreeVersion()).Times(AtLeast(0)); - std::vector immunityList = + EXPECT_CALL(mechanicsMock, getSpell()).Times(AtLeast(1)).WillRepeatedly(Return(&spellMock)); + EXPECT_CALL(spellMock, forEachSchool(NotNull())).Times(AtLeast(1)).WillRepeatedly([](const spells::Spell::SchoolCallback & cb) { - BonusType::AIR_IMMUNITY, - BonusType::FIRE_IMMUNITY, - }; + bool stop = false; + cb(ESpellSchool::AIR, stop); + cb(ESpellSchool::FIRE, stop); + }); - EXPECT_CALL(mechanicsMock, getElementalImmunity()).Times(AtLeast(1)).WillRepeatedly(Return(immunityList)); EXPECT_CALL(mechanicsMock, isPositiveSpell()).WillRepeatedly(Return(isPositive)); } @@ -54,15 +56,23 @@ TEST_P(ElementalConditionTest, ReceptiveIfNoBonus) TEST_P(ElementalConditionTest, ImmuneIfBonusMatches) { setDefaultExpectations(); - unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 0)); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR))); EXPECT_FALSE(subject->isReceptive(&mechanicsMock, &unitMock)); } +TEST_P(ElementalConditionTest, NotImmuneIfBonusMismatches) +{ + setDefaultExpectations(); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::WATER))); + + EXPECT_TRUE(subject->isReceptive(&mechanicsMock, &unitMock)); +} + TEST_P(ElementalConditionTest, DependsOnPositivness) { setDefaultExpectations(); - unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 1)); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::NEGATIVE_EFFECTS_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR))); EXPECT_EQ(isPositive, subject->isReceptive(&mechanicsMock, &unitMock)); } @@ -70,8 +80,8 @@ TEST_P(ElementalConditionTest, DependsOnPositivness) TEST_P(ElementalConditionTest, ImmuneIfBothBonusesPresent) { setDefaultExpectations(); - unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 0)); - unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 1)); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR))); + unitBonuses.addNewBonus(std::make_shared(BonusDuration::ONE_BATTLE, BonusType::NEGATIVE_EFFECTS_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR))); EXPECT_FALSE(subject->isReceptive(&mechanicsMock, &unitMock)); } diff --git a/test/spells/targetConditions/TargetConditionItemFixture.h b/test/spells/targetConditions/TargetConditionItemFixture.h index b67714eda..060731fb5 100644 --- a/test/spells/targetConditions/TargetConditionItemFixture.h +++ b/test/spells/targetConditions/TargetConditionItemFixture.h @@ -17,6 +17,7 @@ #include "mock/mock_spells_Mechanics.h" +#include "mock/mock_spells_Spell.h" #include "mock/mock_BonusBearer.h" #include "mock/mock_battle_Unit.h" @@ -30,6 +31,8 @@ public: ::testing::StrictMock mechanicsMock; ::testing::StrictMock unitMock; + ::testing::StrictMock spellMock; + BonusBearerMock unitBonuses; protected: void SetUp() override