浏览代码

Move getAffectedCreatures to CSpell. + more drafts

AlexVinS 11 年之前
父节点
当前提交
a06dae1f96
共有 9 个文件被更改,包括 275 次插入201 次删除
  1. 2 6
      AI/BattleAI/BattleAI.cpp
  2. 4 117
      lib/CBattleCallback.cpp
  3. 0 1
      lib/CBattleCallback.h
  4. 143 4
      lib/CSpellHandler.cpp
  5. 57 25
      lib/CSpellHandler.h
  6. 6 2
      lib/GameConstants.h
  7. 37 9
      lib/SpellMechanics.cpp
  8. 20 10
      lib/SpellMechanics.h
  9. 6 27
      server/CGameHandler.cpp

+ 2 - 6
AI/BattleAI/BattleAI.cpp

@@ -457,12 +457,8 @@ void CBattleAI::attemptCastingSpell()
 		case OFFENSIVE_SPELL:
 			{
 				int damageDealt = 0, damageReceived = 0;
-
-				auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest);
-				vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool
-				{
-					return  ESpellCastProblem::OK !=  ps.spell->isImmuneByStack(hero, ECastingMode::HERO_CASTING, s);
-				});
+				
+				auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
 
 				if(stacksSuffering.empty())
 					return -1;

+ 4 - 117
lib/CBattleCallback.cpp

@@ -1582,7 +1582,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C
 		
 		for(auto s : stacks)
 		{
-			ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,mode,s);
+			ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,s);
 			
 			if(res == ESpellCastProblem::OK)
 			{
@@ -1661,7 +1661,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
 		for(auto stack : stacks)
 		{
-			if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, mode, stack))
+			if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
 			{
 				allStacksImmune = false;
 				break;
@@ -1705,7 +1705,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 
             for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
 			{
-				bool immune =  ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack);
+				bool immune =  ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
                 if(spell->id == SpellID::SACRIFICE)
@@ -1763,8 +1763,6 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 	std::vector<BattleHex> ret;
 	RETURN_IF_NOT_BATTLE(ret);
 
-	auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this!
-
 	switch(spell->getTargetType())
 	{
 	case CSpell::CREATURE:
@@ -1774,7 +1772,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 			
 			for(const CStack * stack : battleAliveStacks())
 			{
-				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack);
+				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
 				if(!immune)
@@ -1908,117 +1906,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		return battleIsImmune(nullptr, spell, mode, dest);
 }
 
-std::set<const CStack*> CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile)
-{
-	std::set<const CStack*> attackedCres; //std::set to exclude multiple occurrences of two hex creatures
-
-	const ui8 attackerSide = playerToSide(attackerOwner) == 1;
-	const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide);
-	
-	const CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel);
-	//TODO: more generic solution for mass spells
-	if (spell->id == SpellID::CHAIN_LIGHTNING)
-	{
-		std::set<BattleHex> possibleHexes;
-		for (auto stack : battleGetAllStacks())
-		{
-			if (stack->isValidTarget())
-			{
-				for (auto hex : stack->getHexes())
-				{
-					possibleHexes.insert (hex);
-				}
-			}
-		}
-		int targetsOnLevel[4] = {4, 4, 5, 5};
-
-		BattleHex lightningHex =  destinationTile;
-		for (int i = 0; i < targetsOnLevel[skillLevel]; ++i)
-		{
-			auto stack = battleGetStackByPos (lightningHex, true);
-			if (!stack)
-				break;
-			attackedCres.insert (stack);
-			for (auto hex : stack->getHexes())
-			{
-				possibleHexes.erase (hex); //can't hit same place twice
-			}
-			if (possibleHexes.empty()) //not enough targets
-				break;
-			lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes);
-		}
-	}
-	else if (spell->getLevelInfo(skillLevel).range.size() > 1) //custom many-hex range
-	{
-		for(BattleHex hex : attackedHexes)
-		{
-			if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
-			{
-				if (spell->id == SpellID::DEATH_CLOUD) //Death Cloud //TODO: fireball and fire immunity
-				{
-					if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive
-					{
-						attackedCres.insert(st);
-					}
-				}
-				else
-					attackedCres.insert(st);
-			}
-		}
-	}
-	else if(spell->getTargetType() == CSpell::CREATURE)
-	{
-		auto predicate = [=](const CStack * s){
-			const bool positiveToAlly = spell->isPositive() && s->owner == attackerOwner;
-			const bool negativeToEnemy = spell->isNegative() && s->owner != attackerOwner;
-			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
-	
-			//for single target spells select stacks covering destination tile
-			const bool rangeCovers = ti.massive || s->coversPos(destinationTile);
-			//handle smart targeting
-			const bool positivenessFlag = !ti.smart || spell->isNeutral() || positiveToAlly || negativeToEnemy;
-			
-			return rangeCovers  && positivenessFlag && validTarget;		
-		};
-		
-		TStacks stacks = battleGetStacksIf(predicate);
-		
-		if (ti.massive)
-		{
-			//for massive spells add all targets
-			for (auto stack : stacks)
-				attackedCres.insert(stack);
-
-		}
-		else
-		{
-			//for single target spells we must select one target. Alive stack is preferred (issue #1763)
-			for(auto stack : stacks)
-			{
-				if(stack->alive())
-				{
-					attackedCres.insert(stack);
-					break;
-				}				
-			}	
-			
-			if(attackedCres.empty() && !stacks.empty())
-			{
-				attackedCres.insert(stacks.front());
-			}						
-		}
-	}
-	else //custom range from attackedHexes
-	{
-		for(BattleHex hex : attackedHexes)
-		{
-			if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive))
-				attackedCres.insert(st);
-		}
-	}
-	return attackedCres;
-}
-
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
 {
 	RETURN_IF_NOT_BATTLE(nullptr);

+ 0 - 1
lib/CBattleCallback.h

@@ -283,7 +283,6 @@ public:
 	std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
 	ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel
 	ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks
-	std::set<const CStack*> getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell
 
 	SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
 	SpellID getRandomBeneficialSpell(const CStack * subject) const;

+ 143 - 4
lib/CSpellHandler.cpp

@@ -12,6 +12,7 @@
 
 #include "mapObjects/CGHeroInstance.h"
 #include "BattleState.h"
+#include "CBattleCallback.h"
 
 #include "SpellMechanics.h"
 
@@ -380,12 +381,130 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
 	return ret;
 }
 
+std::set<const CStack* > CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
+{
+	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
+	
+	const ui8 attackerSide = cb->playerToSide(casterColor) == 1;
+	const auto attackedHexes = rangeInHexes(destination, spellLvl, attackerSide);
+	
+	const CSpell::TargetInfo ti = getTargetInfoEx(spellLvl, mode);
+	
+	
+	//TODO: more generic solution for mass spells
+	if (id == SpellID::CHAIN_LIGHTNING)
+	{
+		std::set<BattleHex> possibleHexes;
+		for (auto stack : cb->battleGetAllStacks())
+		{
+			if (stack->isValidTarget())
+			{
+				for (auto hex : stack->getHexes())
+				{
+					possibleHexes.insert (hex);
+				}
+			}
+		}
+		int targetsOnLevel[4] = {4, 4, 5, 5};
+
+		BattleHex lightningHex =  destination;
+		for (int i = 0; i < targetsOnLevel[spellLvl]; ++i)
+		{
+			auto stack = cb->battleGetStackByPos (lightningHex, true);
+			if (!stack)
+				break;
+			attackedCres.insert (stack);
+			for (auto hex : stack->getHexes())
+			{
+				possibleHexes.erase (hex); //can't hit same place twice
+			}
+			if (possibleHexes.empty()) //not enough targets
+				break;
+			lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destination, possibleHexes);
+		}
+	}
+	else if (getLevelInfo(spellLvl).range.size() > 1) //custom many-hex range
+	{
+		for(BattleHex hex : attackedHexes)
+		{
+			if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive))
+			{
+				attackedCres.insert(st);
+			}
+		}
+	}
+	else if(getTargetType() == CSpell::CREATURE)
+	{
+		auto predicate = [=](const CStack * s){
+			const bool positiveToAlly = isPositive() && s->owner == casterColor;
+			const bool negativeToEnemy = isNegative() && s->owner != casterColor;
+			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
+	
+			//for single target spells select stacks covering destination tile
+			const bool rangeCovers = ti.massive || s->coversPos(destination);
+			//handle smart targeting
+			const bool positivenessFlag = !ti.smart || isNeutral() || positiveToAlly || negativeToEnemy;
+			
+			return rangeCovers  && positivenessFlag && validTarget;		
+		};
+		
+		TStacks stacks = cb->battleGetStacksIf(predicate);
+		
+		if (ti.massive)
+		{
+			//for massive spells add all targets
+			for (auto stack : stacks)
+				attackedCres.insert(stack);
+
+		}
+		else
+		{
+			//for single target spells we must select one target. Alive stack is preferred (issue #1763)
+			for(auto stack : stacks)
+			{
+				if(stack->alive())
+				{
+					attackedCres.insert(stack);
+					break;
+				}				
+			}	
+			
+			if(attackedCres.empty() && !stacks.empty())
+			{
+				attackedCres.insert(stacks.front());
+			}						
+		}
+	}
+	else //custom range from attackedHexes
+	{
+		for(BattleHex hex : attackedHexes)
+		{
+			if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive))
+				attackedCres.insert(st);
+		}
+	}	
+	
+	//now handle immunities		
+	auto predicate = [&, this](const CStack * s)->bool
+	{
+		bool hitDirectly = ti.alwaysHitDirectly && s->coversPos(destination);
+		bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
+		
+		return !(hitDirectly || notImmune);  
+	};
+	
+	vstd::erase_if(attackedCres, predicate);
+	
+	return attackedCres;
+}
+
+
 CSpell::ETargetType CSpell::getTargetType() const
 {
 	return targetType;
 }
 
-const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
+CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
 {
 	TargetInfo info;
 
@@ -395,7 +514,25 @@ const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
 	info.smart = levelInfo.smartTarget;
 	info.massive = levelInfo.range == "X";
 	info.onlyAlive = !isRisingSpell();
+	info.alwaysHitDirectly = false;
+
+	return info;
+}
 
+CSpell::TargetInfo CSpell::getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const
+{
+	TargetInfo info = getTargetInfo(level);
+
+	if(mode == ECastingMode::ENCHANTER_CASTING)
+	{
+		info.smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
+		info.massive = true;
+	}
+	else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
+	{
+		info.alwaysHitDirectly = true;
+	}
+	
 	return info;
 }
 
@@ -595,9 +732,9 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
 	return ESpellCastProblem::NOT_DECIDED;
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
 {
-	const auto immuneResult = mechanics->isImmuneByStack(caster,mode,obj);
+	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
 	
 	if (ESpellCastProblem::NOT_DECIDED != immuneResult) 
 		return immuneResult;
@@ -649,7 +786,7 @@ void CSpell::setupMechanics()
 	switch (id)
 	{
 	case SpellID::CLONE:
-		mechanics = new CloneMechnics(this);
+		mechanics = new CloneMechanics(this);
 		break;
 	case SpellID::DISPEL_HELPFUL_SPELLS:
 		mechanics = new DispellHelpfulMechanics(this);
@@ -660,6 +797,8 @@ void CSpell::setupMechanics()
 	default:		
 		if(isRisingSpell())
 			mechanics = new SpecialRisingSpellMechanics(this);
+		else if(isOffensiveSpell())
+			mechanics = new OffenciveSpellMechnics(this);
 		else	
 			mechanics = new DefaultSpellMechanics(this);
 		break;

+ 57 - 25
lib/CSpellHandler.h

@@ -4,6 +4,7 @@
 #include "../lib/ConstTransitivePtr.h"
 #include "int3.h"
 #include "GameConstants.h"
+#include "BattleHex.h"
 #include "HeroBonus.h"
 
 
@@ -17,12 +18,16 @@
  *
  */
 
-class CLegacyConfigParser;
-struct BattleHex;
 class CSpell;
+class ISpellMechanics;
+
+class CLegacyConfigParser;
+
 class CGHeroInstance;
 class CStack;
 
+class CBattleInfoCallback;
+
 struct CPackForClient;
 
 struct SpellSchoolInfo
@@ -73,29 +78,20 @@ struct DLL_LINKAGE SpellCastContext
 {
 public:
 	SpellCastEnvironment * env;
-};
-
-class DLL_LINKAGE ISpellMechanics
-{
-public:
-	ISpellMechanics(CSpell * s);
-	virtual ~ISpellMechanics(){};	
-	
-	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) = 0;
 	
-    /** \brief 
-     *
-     * \param 
-     * \return true if no error
-     *
-     */                           
-	virtual bool adventureCast(SpellCastContext & context) = 0; 
-	virtual bool battleCast(SpellCastContext & context) = 0; 	
-	
-protected:
-	CSpell * owner;	
+	int spellLvl;
+//	BattleHex destination;
+	ui8 casterSide;
+	PlayerColor casterColor;
+	CGHeroInstance * caster;
+	CGHeroInstance * secHero;
+	int usedSpellPower;
+	ECastingMode::ECastingMode mode;
+	CStack * targetStack;
+	CStack * selectedStack;	
 };
 
+
 class DLL_LINKAGE CSpell
 {
 public:
@@ -137,6 +133,8 @@ public:
 		bool smart;
 		bool massive;
 		bool onlyAlive;
+		///no immunity on primary target (mostly spell-like attack)
+		bool alwaysHitDirectly;
 	};
 
 	SpellID id;
@@ -168,7 +166,8 @@ public:
 	si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none)
 	ETargetType getTargetType() const; //deprecated
 
-	const CSpell::TargetInfo getTargetInfo(const int level) const;
+	CSpell::TargetInfo getTargetInfo(const int level) const;
+	CSpell::TargetInfo getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const;
 
 	bool isCombatSpell() const;
 	bool isAdventureSpell() const;
@@ -192,7 +191,7 @@ public:
 	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
 	
 	//checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
 	
 	//internal, for use only by Mechanics classes. applying secondary skills
 	ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const;
@@ -202,7 +201,8 @@ public:
 	///calculate healed HP for all spells casted by hero
 	ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const;
 	
-	
+	///selects from allStacks actually affected stacks
+	std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
 
 	si32 getCost(const int skillLevel) const;
 
@@ -289,6 +289,38 @@ private:
 	ISpellMechanics * mechanics;//(!) do not serialize
 };
 
+class DLL_LINKAGE ISpellMechanics
+{
+public:
+	
+	struct SpellTargetingContext
+	{
+		CBattleInfoCallback * cb;
+		
+		CSpell::TargetInfo ti;
+	};
+	
+public:
+	ISpellMechanics(CSpell * s);
+	virtual ~ISpellMechanics(){};	
+	
+	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
+	
+	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
+	
+	
+    /** \brief 
+     *
+     * \param 
+     * \return true if no error
+     *
+     */                           
+	virtual bool adventureCast(SpellCastContext & context) const = 0; 
+	virtual bool battleCast(SpellCastContext & context) const = 0; 	
+	
+protected:
+	CSpell * owner;	
+};
 
 
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door

+ 6 - 2
lib/GameConstants.h

@@ -402,8 +402,12 @@ namespace ESpellCastProblem
 
 namespace ECastingMode
 {
-	enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
-		MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING};
+	enum ECastingMode 
+	{
+		HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
+		MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
+		SPELL_LIKE_ATTACK	
+	};
 }
 
 namespace EMarketMode

+ 37 - 9
lib/SpellMechanics.cpp

@@ -14,15 +14,43 @@
 #include "mapObjects/CGHeroInstance.h"
 #include "BattleState.h"
 
+#include "NetPacks.h"
+
 ///DefaultSpellMechanics
-ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj)
+
+std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+{
+	
+}
+
+
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
 {
 	//by default use general algorithm
 	return owner->isImmuneBy(obj);
 }
 
+bool DefaultSpellMechanics::adventureCast(SpellCastContext& context) const
+{
+	return false; //there is no general algorithm for castind adventure spells
+}
+
+bool DefaultSpellMechanics::battleCast(SpellCastContext& context) const
+{
+	return false; //todo; DefaultSpellMechanics::battleCast
+}
+
+///OffenciveSpellMechnics
+bool OffenciveSpellMechnics::battleCast(SpellCastContext& context) const
+{
+	assert(owner->isOffensiveSpell());
+	
+	//todo:OffenciveSpellMechnics::battleCast
+}
+
+
 ///CloneMechanics
-ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj)
+ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack * obj) const
 {
 	//can't clone already cloned creature
 	if (vstd::contains(obj->state, EBattleStackState::CLONED))
@@ -47,11 +75,11 @@ ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHero
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 	//use default algorithm only if there is no mechanics-related problem		
-	return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);	
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
 }
 
 ///DispellHelpfulMechanics
-ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
+ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster,  const CStack* obj) const
 {
 	TBonusListPtr spellBon = obj->getSpellBonuses();
 	bool hasPositiveSpell = false;
@@ -69,11 +97,11 @@ ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(co
 	}
 	
 	//use default algorithm only if there is no mechanics-related problem		
-	return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);	
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
 }
 
 ///HypnotizeMechanics
-ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
+ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
 {
 	if(nullptr != caster) //do not resist hypnotize casted after attack, for example
 	{
@@ -85,12 +113,12 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
 		if (subjectHealth > maxHealth)
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}			
-	return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);
 }
 
 
 ///SpecialRisingSpellMechanics
-ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj)
+ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
 {
 	// following does apply to resurrect and animate dead(?) only
 	// for sacrifice health calculation and health limit check don't matter
@@ -105,7 +133,7 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}	
 	
-	return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj);	
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
 }
 
 	

+ 20 - 10
lib/SpellMechanics.h

@@ -16,32 +16,42 @@ class DefaultSpellMechanics: public ISpellMechanics
 {
 public:
 	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
 	
-	bool adventureCast(SpellCastContext & context) override; 
-	bool battleCast(SpellCastContext & context) override; 
+	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	
+	bool adventureCast(SpellCastContext & context) const override; 
+	bool battleCast(SpellCastContext & context) const override; 
 };
 
-class CloneMechnics: public DefaultSpellMechanics
+class OffenciveSpellMechnics: public DefaultSpellMechanics
 {
 public:
-	CloneMechnics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;
+	OffenciveSpellMechnics(CSpell * s): DefaultSpellMechanics(s){};	
+	bool battleCast(SpellCastContext & context) const override;
+};
+
+class CloneMechanics: public DefaultSpellMechanics
+{
+public:
+	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
 };
 
 class DispellHelpfulMechanics: public DefaultSpellMechanics
 {
 public:
 	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
 };
 
 class HypnotizeMechanics: public DefaultSpellMechanics
 {
 public:
 	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;	
-};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
+}; 
 
 ///all rising spells
 class RisingSpellMechanics: public DefaultSpellMechanics
@@ -56,7 +66,7 @@ class SpecialRisingSpellMechanics: public RisingSpellMechanics
 {
 public:
 	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override;						
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;						
 };
 
 class SacrificeMechanics: public RisingSpellMechanics

+ 6 - 27
server/CGameHandler.cpp

@@ -787,7 +787,8 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 		bat.bsa.front().flags |= BattleStackAttacked::EFFECT;
 		bat.bsa.front().effect = VLC->spellh->objects.at(bonus->subtype)->mainEffectAnim; //hopefully it does not interfere with any other effect?
 
-		std::set<const CStack*> attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex);
+		std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex);
+	
 		//TODO: get exact attacked hex for defender
 
 		for(const CStack * stack : attackedCreatures)
@@ -4018,31 +4019,9 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 	//must be vector, as in Chain Lightning order matters
 	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
 
-	if (mode != ECastingMode::ENCHANTER_CASTING)
-	{
-		auto creatures = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination);
-		std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
-	}
-	else //enchanter - hit all possible stacks
-	{
-		for (const CStack * stack : gs->curB->stacks)
-		{
-			/*if it's non negative spell and our unit or non positive spell and hostile unit */
-			if((!spell->isNegative() && stack->owner == casterColor)
-				|| (!spell->isPositive() && stack->owner != casterColor))
-			{
-				if(stack->isValidTarget()) //TODO: allow dead targets somewhere in the future
-				{
-					attackedCres.push_back(stack);
-				}
-			}
-		}
-	}
-
-	vstd::erase_if(attackedCres,[=](const CStack * s){
-		return ESpellCastProblem::OK != spell->isImmuneByStack(caster,mode,s);		
-	});
-
+	auto creatures = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster);
+	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
+	
 	for (auto cre : attackedCres)
 	{
 		sc.affectedCres.insert (cre->ID);
@@ -4453,7 +4432,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex
 				{
 					if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
 					{
-						if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, ECastingMode::MAGIC_MIRROR, battleStack))
+						if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, battleStack))
 							mirrorTargets.push_back(battleStack);
 					}
 				}