浏览代码

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.
Konstantin 2 年之前
父节点
当前提交
8724181a0f

+ 2 - 2
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)

+ 1 - 2
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<SpellID>
 {
 public:
-	using SchoolCallback = std::function<void(const SchoolInfo &, bool &)>;
+	using SchoolCallback = std::function<void(const ESpellSchool &, bool &)>;
 
 	///calculate spell damage on stack taking caster`s secondary skills into account
 	virtual int64_t calculateDamage(const Caster * caster) const = 0;

+ 29 - 42
lib/CBonusTypeHandler.cpp

@@ -99,60 +99,47 @@ std::string CBonusTypeHandler::bonusToGraphics(const std::shared_ptr<Bonus> & bo
 		fileName = sp->getIconImmune();
 		break;
 	}
-	case BonusType::FIRE_IMMUNITY:
+	case BonusType::SPELL_SCHOOL_IMMUNITY: //for all school
+	{
 		switch(bonus->subtype)
 		{
-		case 0:
+		case SpellSchool(ESpellSchool::AIR):
+			fileName = "E_SPAIR.bmp";
+			break;
+		case SpellSchool(ESpellSchool::FIRE):
 			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:
+			break;
+		case SpellSchool(ESpellSchool::WATER):
 			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 SpellSchool(ESpellSchool::EARTH):
+			fileName = "E_SPEATH.bmp";
+			break;
 		}
 		break;
-	case BonusType::AIR_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_SPAIR.bmp";
-			break;//all
-		case 1:
+		case SpellSchool(ESpellSchool::AIR):
 			fileName = "E_SPAIR1.bmp";
-			break;//not positive
-		case 2:
-			fileName = "E_LIGHT.bmp";
-			break;//direct damage
-		}
-		break;
-	case BonusType::EARTH_IMMUNITY:
-		switch(bonus->subtype)
-		{
-		case 0:
-			fileName = "E_SPEATH.bmp";
-			break;//all
-		case 1:
-		case 2://no specific icon for direct damage immunity
+			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))

+ 12 - 12
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;

+ 2 - 4
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 */
 
 

+ 7 - 7
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<ui8>(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<int64_t>(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;
 		}

+ 5 - 9
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<void(const spells::SchoolInfo &, bool &)>& cb) const
+void CSpell::forEachSchool(const std::function<void(const ESpellSchool &, bool &)>& cb) const
 {
 	bool stop = false;
 	for(auto iter : SpellConfig::SCHOOL_ORDER)
@@ -161,7 +157,7 @@ void CSpell::forEachSchool(const std::function<void(const spells::SchoolInfo &,
 		const spells::SchoolInfo & cnf = SpellConfig::SCHOOL[iter];
 		if(school.at(cnf.id))
 		{
-			cb(cnf, stop);
+			cb(cnf.id.toEnum(), stop);
 
 			if(stop)
 				break;
@@ -385,11 +381,11 @@ int64_t CSpell::adjustRawDamage(const spells::Caster * caster, const battle::Uni
 	{
 		const auto * bearer = affectedCreature->getBonusBearer();
 		//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
 			}

+ 1 - 2
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<void(const spells::SchoolInfo &, bool &)> & cb) const override;
+	void forEachSchool(const std::function<void(const ESpellSchool &, bool &)> & cb) const override;
 
 	spells::AimType getTargetType() const;
 

+ 0 - 12
lib/spells/ISpellMechanics.cpp

@@ -620,18 +620,6 @@ int64_t BaseMechanics::calculateRawEffectValue(int32_t basePowerMultiplier, int3
 	return owner->calculateRawEffectValue(getEffectLevel(), basePowerMultiplier, levelPowerMultiplier);
 }
 
-std::vector<BonusType> BaseMechanics::getElementalImmunity() const
-{
-	std::vector<BonusType> 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());

+ 0 - 4
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<BonusType> 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<BonusType> getElementalImmunity() const override;
-
 	bool ownerMatches(const battle::Unit * unit) const override;
 	bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override;
 

+ 10 - 8
lib/spells/TargetCondition.cpp

@@ -23,6 +23,8 @@
 #include "../serializer/JsonSerializeFormat.h"
 #include "../VCMI_Lib.h"
 
+#include <vcmi/spells/Spell.h>
+
 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;
 	}
 };

+ 2 - 2
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;

+ 3 - 1
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)
 			)
 	{

+ 0 - 2
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<BonusType>());
-
 	MOCK_CONST_METHOD1(ownerMatches, bool(const battle::Unit *));
 	MOCK_CONST_METHOD2(ownerMatches, bool(const battle::Unit *, const boost::logic::tribool));
 

+ 1 - 1
test/spells/targetConditions/BonusConditionTest.cpp

@@ -49,7 +49,7 @@ TEST_F(BonusConditionTest, ReceptiveIfMatchesType)
 TEST_F(BonusConditionTest, ImmuneIfTypeMismatch)
 {
 	setDefaultExpectations();
-	unitBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::FIRE_IMMUNITY, BonusSource::OTHER, 0, 0));
+	unitBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::OTHER, 0, SpellSchool(ESpellSchool::FIRE)));
 	EXPECT_FALSE(subject->isReceptive(&mechanicsMock, &unitMock));
 }
 

+ 19 - 9
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<BonusType> 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<Bonus>(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 0));
+	unitBonuses.addNewBonus(std::make_shared<Bonus>(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<Bonus>(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<Bonus>(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 1));
+	unitBonuses.addNewBonus(std::make_shared<Bonus>(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<Bonus>(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 0));
-	unitBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::AIR_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, 1));
+	unitBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::SPELL_SCHOOL_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR)));
+	unitBonuses.addNewBonus(std::make_shared<Bonus>(BonusDuration::ONE_BATTLE, BonusType::NEGATIVE_EFFECTS_IMMUNITY, BonusSource::SPELL_EFFECT, 0, 0, SpellSchool(ESpellSchool::AIR)));
 
 	EXPECT_FALSE(subject->isReceptive(&mechanicsMock, &unitMock));
 }

+ 3 - 0
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<spells::MechanicsMock> mechanicsMock;
 	::testing::StrictMock<UnitMock> unitMock;
+	::testing::StrictMock<spells::SpellMock> spellMock;
+
 	BonusBearerMock unitBonuses;
 protected:
 	void SetUp() override