Răsfoiți Sursa

Merge pull request #62 from vcmi/SpellsRefactoring4

OK
DjWarmonger 10 ani în urmă
părinte
comite
84b2510aa4

+ 3 - 7
AI/BattleAI/BattleAI.cpp

@@ -457,19 +457,15 @@ 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 != cb->battleStackIsImmune(hero, ps.spell, ECastingMode::HERO_CASTING, s);
-				});
+				
+				auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
 
 				if(stacksSuffering.empty())
 					return -1;
 
 				for(auto stack : stacksSuffering)
 				{
-					const int dmg = cb->calculateSpellDmg(ps.spell, hero, stack, skillLevel, spellPower);
+					const int dmg = ps.spell->calculateDamage(hero, stack, skillLevel, spellPower);
 					if(stack->owner == playerID)
 						damageReceived += dmg;
 					else

+ 16 - 7
client/CPlayerInterface.cpp

@@ -876,17 +876,22 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
-
+	
 	std::vector<StackAttackedInfo> arg;
 	for(auto & elem : bsa)
 	{
 		const CStack *defender = cb->battleGetStackByID(elem.stackAttacked, false);
 		const CStack *attacker = cb->battleGetStackByID(elem.attackerID, false);
-		if(elem.isEffect() && elem.effect != 12) //and not armageddon
+		if(elem.isEffect())
 		{
 			if (defender && !elem.isSecondary())
 				battleInt->displayEffect(elem.effect, defender->position);
 		}
+		if(elem.isSpell())
+		{
+			if (defender)
+				battleInt->displaySpellEffect(elem.spellID, defender->position);			
+		}
 		//FIXME: why action is deleted during enchanter cast?
 		bool remoteAttack = false;
 
@@ -897,11 +902,6 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 		arg.push_back(to_put);
 	}
 
-	if(bsa.begin()->isEffect() && bsa.begin()->effect == 12) //for armageddon - I hope this condition is enough
-	{
-		battleInt->displayEffect(bsa.begin()->effect, -1);
-	}
-
 	battleInt->stacksAreAttacked(arg);
 }
 void CPlayerInterface::battleAttack(const BattleAttack *ba)
@@ -973,6 +973,15 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
 		const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
 		battleInt->stackAttacking( attacker, ba->counter() ? curAction->destinationTile + shift : curAction->additionalInfo, attacked, false);
 	}
+	
+	//battleInt->waitForAnims(); //FIXME: freeze
+	
+	if(ba->spellLike())
+	{
+		//display hit animation		
+		SpellID spellID = ba->spellID;			
+		battleInt->displaySpellHit(spellID,curAction->destinationTile);	
+	}
 }
 void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
 {

+ 70 - 76
client/battle/CBattleAnimations.cpp

@@ -20,6 +20,7 @@
 #include "../../lib/BattleState.h"
 #include "../../lib/CTownHandler.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
+#include "../../lib/CSpellHandler.h"
 
 /*
  * CBattleAnimations.cpp, part of VCMI engine
@@ -865,83 +866,81 @@ void CShootingAnimation::endAnim()
 	delete this;
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _areaEffect)
-:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) , areaEffect(_areaEffect)
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(-1), y(-1), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
 {
 	logAnim->debugStream() << "Created spell anim for effect #" << effect;
 }
 
-CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _areaEffect)
-:CBattleAnimation(_owner), effect(-1), destTile(0), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), areaEffect(_areaEffect)
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(-1), destTile(BattleHex::INVALID), customAnim(_customAnim), x(_x), y(_y), dx(_dx), dy(_dy), Vflip(_Vflip), alignToBottom(_alignToBottom)
 {
 	logAnim->debugStream() << "Created spell anim for " << customAnim;
 }
 
+CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
+	:CBattleAnimation(_owner), effect(-1), destTile(_destTile), customAnim(_customAnim), x(-1), y(-1), dx(0), dy(0), Vflip(_Vflip), alignToBottom(_alignToBottom)
+{
+	logAnim->debugStream() << "Created spell anim for " << customAnim;	
+}
+
+
 bool CSpellEffectAnimation::init()
 {
 	if(!isEarliest(true))
 		return false;
-
-	if(effect == 12) //armageddon
+		
+	if(customAnim.empty() && effect != ui32(-1) && !graphics->battleACToDef[effect].empty())
+	{		
+		customAnim = graphics->battleACToDef[effect][0];
+	}
+	
+	if(customAnim.empty())
 	{
-		if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
-		{
-			CDefHandler * anim;
-			if(customAnim.size())
-				anim = CDefHandler::giveDef(customAnim);
-			else
-				anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
+		endAnim();
+		return false;		
+	}
+	
+	const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
 
-			if (Vflip)
-			{
-				for (auto & elem : anim->ourImages)
-				{
-					CSDL_Ext::VflipSurf(elem.bitmap);
-				}
-			}
+	if(areaEffect) //f.e. armageddon 
+	{
+		CDefHandler * anim = CDefHandler::giveDef(customAnim);
 
-			for(int i=0; i * anim->width < owner->pos.w ; ++i)
+		for(int i=0; i * anim->width < owner->pos.w ; ++i)
+		{
+			for(int j=0; j * anim->height < owner->pos.h ; ++j)
 			{
-				for(int j=0; j * anim->height < owner->pos.h ; ++j)
+				BattleEffect be;
+				be.effectID = ID;
+				be.anim = CDefHandler::giveDef(customAnim);
+				if (Vflip)
 				{
-					BattleEffect be;
-					be.effectID = ID;
-					be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
-					if (Vflip)
+					for (auto & elem : be.anim->ourImages)
 					{
-						for (auto & elem : be.anim->ourImages)
-						{
-							CSDL_Ext::VflipSurf(elem.bitmap);
-						}
+						CSDL_Ext::VflipSurf(elem.bitmap);
 					}
-					be.currentFrame = 0;
-					be.maxFrame = be.anim->ourImages.size();
-					be.x = i * anim->width + owner->pos.x;
-					be.y = j * anim->height + owner->pos.y;
-					be.position = BattleHex::INVALID;
-
-					owner->battleEffects.push_back(be);
 				}
+				be.currentFrame = 0;
+				be.maxFrame = be.anim->ourImages.size();
+				be.x = i * anim->width + owner->pos.x;
+				be.y = j * anim->height + owner->pos.y;
+				be.position = BattleHex::INVALID;
+
+				owner->battleEffects.push_back(be);
 			}
 		}
-		else //there is nothing to play
-		{
-			endAnim();
-			return false;
-		}
+		
+		delete anim;
 	}
 	else // Effects targeted at a specific creature/hex.
 	{
-		if(effect == -1 || graphics->battleACToDef[effect].size() != 0)
-		{
+
 			const CStack* destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
 			Rect &tilePos = owner->bfield[destTile]->pos;
 			BattleEffect be;
 			be.effectID = ID;
-			if(customAnim.size())
-				be.anim = CDefHandler::giveDef(customAnim);
-			else
-				be.anim = CDefHandler::giveDef(graphics->battleACToDef[effect][0]);
+			be.anim = CDefHandler::giveDef(customAnim);
 
 			if (Vflip)
 			{
@@ -953,28 +952,31 @@ bool CSpellEffectAnimation::init()
 
 			be.currentFrame = 0;
 			be.maxFrame = be.anim->ourImages.size();
-			if(effect == 1)
-				be.maxFrame = 3;
+			
+			//todo: lightning anim frame count override
+			
+//			if(effect == 1)
+//				be.maxFrame = 3;
 
-			switch (effect)
+			if(x == -1)
+			{
+				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
+			}
+			else
 			{
-			case ui32(-1):
 				be.x = x;
+			}
+			
+			if(y == -1)
+			{
+				if(alignToBottom)
+					be.y = tilePos.y + tilePos.h - be.anim->height;
+				else
+					be.y = tilePos.y - be.anim->height/2;
+			}
+			else
+			{
 				be.y = y;
-				break;
-			case 0: // Prayer and Lightning Bolt.
-			case 1:
-			case 19: // Slow
-				// Position effect with it's bottom center touching the bottom center of affected tile(s).
-				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
-				be.y = tilePos.y + tilePos.h - be.anim->height;
-				break;
-
-			default:
-				// Position effect with it's center touching the top center of affected tile(s).
-				be.x = tilePos.x + tilePos.w/2 - be.anim->width/2;
-				be.y = tilePos.y - be.anim->height/2;
-				break;
 			}
 
 			// Correction for 2-hex creatures.
@@ -982,18 +984,10 @@ bool CSpellEffectAnimation::init()
 				be.x += (destStack->attackerOwned ? -1 : 1)*tilePos.w/2;
 
 			//Indicate if effect should be drawn on top of everything or just on top of the hex
-			if(areaEffect)
-				be.position = BattleHex::INVALID;
-			else
-				be.position = destTile;
+			be.position = destTile;
 
 			owner->battleEffects.push_back(be);
-		}
-		else //there is nothing to play
-		{
-			endAnim();
-			return false;
-		}
+
 	}
 	//battleEffects 
 	return true;

+ 4 - 3
client/battle/CBattleAnimations.h

@@ -223,13 +223,14 @@ private:
 	std::string	customAnim;
 	int	x, y, dx, dy;
 	bool Vflip;
-	bool areaEffect;
+	bool alignToBottom;
 public:
 	bool init();
 	void nextFrame();
 	void endAnim();
 
-	CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
-	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _areaEffect = true);
+	CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
+	CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
 	virtual ~CSpellEffectAnimation(){};
 };

+ 98 - 102
client/battle/CBattleInterface.cpp

@@ -1226,57 +1226,54 @@ void CBattleInterface::displayBattleFinished()
 
 void CBattleInterface::spellCast( const BattleSpellCast * sc )
 {
-	const CSpell &spell = *CGI->spellh->objects[sc->id];
-
-	std::vector< std::string > anims; //for magic arrow and ice bolt
+	const SpellID spellID(sc->id);
+	const CSpell &spell = * spellID.toSpell();
 
 	const std::string& castSoundPath = spell.getCastSound();
 	
 	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 
-	switch(sc->id)
+	std::string casterCreatureName = "";
+	Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position by default
 	{
-	case SpellID::MAGIC_ARROW:
-		{
-			//initialization of anims
-			anims.push_back("C20SPX0.DEF"); anims.push_back("C20SPX1.DEF"); anims.push_back("C20SPX2.DEF"); anims.push_back("C20SPX3.DEF"); anims.push_back("C20SPX4.DEF");
-		}
-	case SpellID::ICE_BOLT:
+		const auto casterStackID = sc->casterStack;
+
+		if(casterStackID > 0)
 		{
-			if(anims.size() == 0) //initialization of anims
-			{
-				anims.push_back("C08SPW0.DEF"); anims.push_back("C08SPW1.DEF"); anims.push_back("C08SPW2.DEF"); anims.push_back("C08SPW3.DEF"); anims.push_back("C08SPW4.DEF");
-			}
-		} //end of ice bolt only part
-		{ //common ice bolt and magic arrow part
-			//initial variables
-			std::string animToDisplay;
-			Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;
-			Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by arrow
-			destcoord.x += 250; destcoord.y += 240;
-
-			//animation angle
-			double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
-			bool Vflip = false;
-			if (angle < 0)
+			const CStack * casterStack = curInt->cb->battleGetStackByID(casterStackID);
+			if(casterStack != nullptr)
 			{
-				Vflip = true;
-				angle = -angle;
+				casterCreatureName = casterStack->type->namePl;
+				
+				srccoord = CClickableHex::getXYUnitAnim(casterStack->position, casterStack, this); 
+				srccoord.x += 250;
+				srccoord.y += 240;
 			}
+		}
+	}
 
-			//choosing animation by angle
-			if(angle > 1.50)
-				animToDisplay = anims[0];
-			else if(angle > 1.20)
-				animToDisplay = anims[1];
-			else if(angle > 0.90)
-				animToDisplay = anims[2];
-			else if(angle > 0.60)
-				animToDisplay = anims[3];
-			else
-				animToDisplay = anims[4];
-
+	//TODO: play custom cast animation
+	{
+				
+	}
+	
+	//playing projectile animation
+	if(sc->tile.isValid())
+	{
+		Point destcoord = CClickableHex::getXYUnitAnim(sc->tile, curInt->cb->battleGetStackByPos(sc->tile), this); //position attacked by projectile
+		destcoord.x += 250; destcoord.y += 240;
+
+		//animation angle
+		double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
+		bool Vflip = (angle < 0);
+		if(Vflip)
+			angle = -angle;
+		
+		std::string animToDisplay = spell.animationInfo.selectProjectile(angle);
+		
+		if(!animToDisplay.empty())
+		{
 			//displaying animation
 			CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
 			double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
@@ -1288,31 +1285,26 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 			int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
 
 			delete animDef;
-			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
-
-			break; //for 15 and 16 cases
-		}
-	case SpellID::LIGHTNING_BOLT:
-	case SpellID::TITANS_LIGHTNING_BOLT:
-	case SpellID::THUNDERBOLT:
-	case SpellID::CHAIN_LIGHTNING: //TODO: zigzag effect
-		for (auto & elem : sc->affectedCres) //in case we have multiple targets
-		{
-			displayEffect(1, curInt->cb->battleGetStackByID(elem, false)->position);
-			displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
-		}
-		break;
-	case SpellID::DISPEL:
-	case SpellID::CURE:
-	case SpellID::RESURRECTION:
-	case SpellID::ANIMATE_DEAD:
-	case SpellID::DISPEL_HELPFUL_SPELLS:
-	case SpellID::SACRIFICE: //TODO: animation upon killed stack
-		for(auto & elem : sc->affectedCres)
-		{
-			displayEffect(spell.mainEffectAnim, curInt->cb->battleGetStackByID(elem, false)->position);
+			addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));			
 		}
-		break;
+	}	
+	waitForAnims();
+	
+	displaySpellHit(spellID, sc->tile);
+	
+	//queuing affect /resist animation	
+	for (auto & elem : sc->affectedCres) 
+	{
+		BattleHex position = curInt->cb->battleGetStackByID(elem, false)->position;
+		
+		if(vstd::contains(sc->resisted,elem))
+			displayEffect(78, position);
+		else
+			displaySpellEffect(spellID, position);	
+	}
+
+	switch(sc->id)
+	{
 	case SpellID::SUMMON_FIRE_ELEMENTAL:
 	case SpellID::SUMMON_EARTH_ELEMENTAL:
 	case SpellID::SUMMON_WATER_ELEMENTAL:
@@ -1323,19 +1315,6 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 		break;
 	} //switch(sc->id)
 
-	if (spell.isDamageSpell() && sc->affectedCres.empty()) //for example Inferno that causes no BattleStackAttacked
-	{
-		if(sc->tile.isValid() && graphics->battleACToDef.count(spell.mainEffectAnim)) //eg. when casting Lind Mine or Fire Wall
-			displayEffect (spell.mainEffectAnim, sc->tile);
-	}
-
-	//support for resistance
-	for(auto & elem : sc->resisted)
-	{
-		int tile = curInt->cb->battleGetStackByID(elem)->position;
-		displayEffect(78, tile);
-	}
-
 	//displaying message in console
 	bool customSpell = false;
 	if(sc->affectedCres.size() == 1)
@@ -1393,7 +1372,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 					}
 					//The %s shrivel with age, and lose %d hit points."
 					TBonusListPtr bl = curInt->cb->battleGetStackByID(*sc->affectedCres.begin(), false)->getBonuses(Selector::type(Bonus::STACK_HEALTH));
-					bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, 75));
+					bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
 					boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(bl->totalValue()/2));
 				}
 					break;
@@ -1427,15 +1406,15 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 							text = CGI->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
 							boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetStackByID(*sc->affectedCres.begin())->type->nameSing);
 						}
-						boost::algorithm::replace_first(text, "%s", CGI->creh->creatures[sc->attackerType]->namePl); //casting stack
+						boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
 					}
 					else
 						text = "";
 					break;
 				default:
 					text = CGI->generaltexth->allTexts[565]; //The %s casts %s
-					if(auto castingCreature = vstd::atOrDefault(CGI->creh->creatures, sc->attackerType, nullptr))
-						boost::algorithm::replace_first(text, "%s", castingCreature->namePl); //casting stack
+					if(casterCreatureName != "")
+						boost::algorithm::replace_first(text, "%s", casterCreatureName); //casting stack
 					else
 						boost::algorithm::replace_first(text, "%s", "@Unknown caster@"); //should not happen
 			}
@@ -1465,9 +1444,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 		{
 			boost::algorithm::replace_first(text, "%s", curInt->cb->battleGetHeroInfo(sc->side).name);
 		}
-		if(auto castingCreature = vstd::atOrDefault(CGI->creh->creatures, sc->attackerType, nullptr))
+		if(casterCreatureName != "")
 		{
-			boost::algorithm::replace_first(text, "%s", castingCreature->namePl); //creature caster
+			boost::algorithm::replace_first(text, "%s", casterCreatureName); //creature caster
 		}
 		else
 		{
@@ -1486,7 +1465,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	}
 	waitForAnims();
 	//mana absorption
-	if (sc->manaGained)
+	if(sc->manaGained > 0)
 	{
 		Point leftHero = Point(15, 30) + pos;
 		Point rightHero = Point(755, 30) + pos;
@@ -1497,16 +1476,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 
 void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 {
-	int effID = sse.effect.back().sid;
-	if(effID != -1) //can be -1 for defensive stance effect
-	{
-		for(auto & elem : sse.stacks)
-		{
-			bool areaEffect(CGI->spellh->objects[effID]->getTargetType() == CSpell::ETargetType::NO_TARGET);
-			displayEffect(CGI->spellh->objects[effID]->mainEffectAnim, curInt->cb->battleGetStackByID(elem)->position, areaEffect);
-		}
-	}
-	else if (sse.stacks.size() == 1 && sse.effect.size() == 2)
+	if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2)
 	{
 		const Bonus & bns = sse.effect.front();
 		if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
@@ -1552,8 +1522,12 @@ void CBattleInterface::castThisSpell(int spellID)
 	
 	const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
 	
-	if(ti.massive)
+	if(ti.massive || ti.type == CSpell::NO_TARGET)
 		spellSelMode = NO_LOCATION;	
+	else if(ti.type == CSpell::LOCATION && ti.clearAffected)
+	{
+		spellSelMode = FREE_LOCATION;		
+	}
 	else if(ti.type == CSpell::CREATURE)
 	{
 		if(ti.smart)
@@ -1565,11 +1539,6 @@ void CBattleInterface::castThisSpell(int spellID)
 	{
 		spellSelMode = OBSTACLE;
 	} 
-	//todo: move to JSON config
-	if(spellID == SpellID::FIRE_WALL  ||  spellID == SpellID::FORCE_FIELD)
-	{
-		spellSelMode = FREE_LOCATION;
-	}
 
 	if (spellSelMode == NO_LOCATION) //user does not have to select location
 	{
@@ -1587,9 +1556,37 @@ void CBattleInterface::castThisSpell(int spellID)
 
 void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
 {
-	addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false, areaEffect));
+	//todo: recheck areaEffect usage
+	addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
+}
+
+void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
+{
+	const CSpell * spell = spellID.toSpell();
+	
+	if(spell == nullptr)
+		return;
+
+	for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
+	{				
+		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+	}
 }
 
+void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
+{
+	const CSpell * spell = spellID.toSpell();
+	
+	if(spell == nullptr)
+		return;	
+	
+	for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
+	{			
+		addNewAnim(new CSpellEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
+	}
+}
+
+
 void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
 {
 	const CStack * stack = curInt->cb->battleGetStackByID(bte.stackID);
@@ -1792,7 +1789,6 @@ void CBattleInterface::getPossibleActionsForStack(const CStack * stack)
 void CBattleInterface::printConsoleAttacked( const CStack * defender, int dmg, int killed, const CStack * attacker, bool multiple )
 {
 	boost::format txt;
-	int end = 0;
 	if (attacker) //ignore if stacks were killed by spell
 	{
 		txt = boost::format (CGI->generaltexth->allTexts[attacker->count > 1 ? 377 : 376]) %

+ 4 - 1
client/battle/CBattleInterface.h

@@ -318,7 +318,10 @@ public:
 	void spellCast(const BattleSpellCast * sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(int spellID); //called when player has chosen a spell from spellbook
-	void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays effect of a spell on the battlefield; affected: true - attacker. false - defender
+	void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
+	void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
+	void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
+	
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 	void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
 	void endAction(const BattleAction* action);

+ 1 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -537,7 +537,7 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 		}
 	}
 	//returning
-	return ret +CPlayerInterface::battleInt->pos;
+	return ret + CPlayerInterface::battleInt->pos;
 }
 
 void CClickableHex::hover(bool on)

+ 52 - 52
config/battles_graphics.json

@@ -30,80 +30,80 @@
 
 	// WoG_Ac_format_to_def_names_mapping
 	"ac_mapping": [
-		{ "id": 0, "defnames": [ "C10SPW.DEF" ] },
-		{ "id": 1, "defnames": [ "C03SPA0.DEF" ] },
-		{ "id": 2, "defnames": [ "C01SPA0.DEF" ] },
-		{ "id": 3, "defnames": [ "C02SPA0.DEF" ] },
-		{ "id": 4, "defnames": [ "SP12_.DEF" ] },
-		{ "id": 5, "defnames": [ "C02SPE0.DEF" ] },
-		{ "id": 6, "defnames": [ "C02SPF0.DEF" ] },
-		{ "id": 7, "defnames": [ "C04SPA0.DEF" ] },
-		{ "id": 8, "defnames": [ "C04SPE0.DEF" ] },
-		{ "id": 9, "defnames": [ "C04SPF0.DEF" ] },
-		{ "id": 10, "defnames": [ "C05SPE0.DEF" ] },
-		{ "id": 11, "defnames": [ "C05SPF0.DEF" ] },
-		{ "id": 12, "defnames": [ "C06SPF0.DEF" ] },
-		{ "id": 13, "defnames": [ "C07SPA0.DEF" ] },
-		{ "id": 14, "defnames": [ "C07SPA1.DEF" ] },
+		{ "id": 0, "defnames": [ "C10SPW.DEF" ] },//merged
+		{ "id": 1, "defnames": [ "C03SPA0.DEF" ] },//merged
+		{ "id": 2, "defnames": [ "C01SPA0.DEF" ] },//merged
+		{ "id": 3, "defnames": [ "C02SPA0.DEF" ] },//merged
+		{ "id": 4, "defnames": [ "SP12_.DEF" ] },//merged (?)
+		{ "id": 5, "defnames": [ "C02SPE0.DEF" ] },//merged
+		{ "id": 6, "defnames": [ "C02SPF0.DEF" ] },//merged
+		{ "id": 7, "defnames": [ "C04SPA0.DEF" ] },//merged
+		{ "id": 8, "defnames": [ "C04SPE0.DEF" ] },//merged
+		{ "id": 9, "defnames": [ "C04SPF0.DEF" ] },//merged
+		{ "id": 10, "defnames": [ "C05SPE0.DEF" ] },//merged
+		{ "id": 11, "defnames": [ "C05SPF0.DEF" ] },//merged
+		{ "id": 12, "defnames": [ "C06SPF0.DEF" ] },//merged (?)
+		{ "id": 13, "defnames": [ "C07SPA0.DEF" ] },//merged (?)
+		{ "id": 14, "defnames": [ "C07SPA1.DEF" ] },//merged
 		{ "id": 15, "defnames": [ "C0FEAR.DEF" ] },
 		{ "id": 16, "defnames": [ "C08SPE0.DEF" ] },
-		{ "id": 17, "defnames": [ "C08SPF0.DEF" ] },
-		{ "id": 18, "defnames": [ "C09SPA0.DEF" ] },
-		{ "id": 19, "defnames": [ "C09SPE0.DEF" ] },
-		{ "id": 20, "defnames": [ "C09SPW0.DEF" ] },
-		{ "id": 21, "defnames": [ "C10SPA0.DEF" ] },
-		{ "id": 22, "defnames": [ "C11SPE0.DEF" ] },
-		{ "id": 23, "defnames": [ "C11SPF0.DEF" ] },
-		{ "id": 24, "defnames": [ "C11SPW0.DEF" ] },
-		{ "id": 25, "defnames": [ "C12SPA0.DEF" ] },
-		{ "id": 26, "defnames": [ "C13SPA0.DEF" ] },
-		{ "id": 27, "defnames": [ "C13SPE0.DEF" ] },
-		{ "id": 28, "defnames": [ "C13SPW0.DEF" ] },
-		{ "id": 29, "defnames": [ "C14SPA0.DEF" ] },
-		{ "id": 30, "defnames": [ "C14SPE0.DEF" ] },
-		{ "id": 31, "defnames": [ "C15SPA0.DEF" ] },
+		{ "id": 17, "defnames": [ "C08SPF0.DEF" ] },//merged
+		{ "id": 18, "defnames": [ "C09SPA0.DEF" ] },//merged
+		{ "id": 19, "defnames": [ "C09SPE0.DEF" ] },//merged
+		{ "id": 20, "defnames": [ "C09SPW0.DEF" ] },//merged
+		{ "id": 21, "defnames": [ "C10SPA0.DEF" ] },//merged
+		{ "id": 22, "defnames": [ "C11SPE0.DEF" ] },//merged
+		{ "id": 23, "defnames": [ "C11SPF0.DEF" ] },//merged
+		{ "id": 24, "defnames": [ "C11SPW0.DEF" ] },//merged
+		{ "id": 25, "defnames": [ "C12SPA0.DEF" ] },//merged
+		{ "id": 26, "defnames": [ "C13SPA0.DEF" ] },//merged
+		{ "id": 27, "defnames": [ "C13SPE0.DEF" ] },//merged
+		{ "id": 28, "defnames": [ "C13SPW0.DEF" ] },//merged
+		{ "id": 29, "defnames": [ "C14SPA0.DEF" ] },//merged
+		{ "id": 30, "defnames": [ "C14SPE0.DEF" ] },//merged
+		{ "id": 31, "defnames": [ "C15SPA0.DEF" ] },//merged
 		{ "id": 32, "defnames": [ "C15SPE0.DEF", "C15SPE1.DEF", "C15SPE2.DEF" ] },
 		{ "id": 33, "defnames": [ "C15SPE3.DEF", "C15SPE6.DEF", "C15SPE7.DEF", "C15SPE8.DEF", "C15SPE9.DEF", "C15SPE10.DEF", "C15SPE11.DEF" ] },
-		{ "id": 35, "defnames": [ "C01SPF.DEF", "C01SPF0.DEF" ] },
-		{ "id": 36, "defnames": [ "C01SPW.DEF", "C01SPW0.DEF" ] },
-		{ "id": 38, "defnames": [ "C11SPA1.DEF" ] },
-		{ "id": 39, "defnames": [ "C03SPW.DEF", "C03SPW0.DEF" ] },
-		{ "id": 40, "defnames": [ "C04SPW.DEF", "C04SPW0.DEF" ] },
-		{ "id": 41, "defnames": [ "C05SPW.DEF", "C05SPW0.DEF" ] },
-		{ "id": 42, "defnames": [ "C06SPW.DEF", "C06SPW0.DEF" ] },
+		{ "id": 35, "defnames": [ "C01SPF.DEF", "C01SPF0.DEF" ] },//merged
+		{ "id": 36, "defnames": [ "C01SPW.DEF", "C01SPW0.DEF" ] },//merged
+		{ "id": 38, "defnames": [ "C11SPA1.DEF" ] },//merged
+		{ "id": 39, "defnames": [ "C03SPW.DEF", "C03SPW0.DEF" ] },//merged
+		{ "id": 40, "defnames": [ "C04SPW.DEF", "C04SPW0.DEF" ] },//merged
+		{ "id": 41, "defnames": [ "C05SPW.DEF", "C05SPW0.DEF" ] },//merged
+		{ "id": 42, "defnames": [ "C06SPW.DEF", "C06SPW0.DEF" ] },//merged
 		{ "id": 43, "defnames": [ "C07SPF0.DEF", "C07SPF1.DEF", "C07SPF2.DEF", "C07SPF6.DEF", "C07SPF7.DEF", "C07SPF8.DEF" ] },
 		{ "id": 44, "defnames": [ "C07SPF0.DEF", "C07SPF4.DEF", "C07SPF5.DEF", "C07SPF9.DEF", "C07SPF10.DEF", "C07SPF11.DEF" ] },
-		{ "id": 45, "defnames": [ "C07SPW.DEF", "C07SPW0.DEF" ] },
-		{ "id": 46, "defnames": [ "C08SPW5.DEF" ] },
+		{ "id": 45, "defnames": [ "C07SPW.DEF", "C07SPW0.DEF" ] },//merged
+		{ "id": 46, "defnames": [ "C08SPW5.DEF" ] },//merged (?)
 		{ "id": 47, "defnames": [ "C09SPF0.DEF" ] },
-		{ "id": 48, "defnames": [ "C10SPF0.DEF" ] },
+		{ "id": 48, "defnames": [ "C10SPF0.DEF" ] },//merged
 		{ "id": 49, "defnames": [ "C11SPA1.DEF" ] },
 		{ "id": 50, "defnames": [ "C12SPE0.DEF" ] },
 		{ "id": 51, "defnames": [ "C12SPF0.DEF" ] },
 		{ "id": 52, "defnames": [ "SP06_.DEF" ] },
-		{ "id": 53, "defnames": [ "C13SPF.DEF", "C13SPF0.DEF" ] },
-		{ "id": 54, "defnames": [ "C16SPE.DEF", "C16SPE0.DEF" ] },
+		{ "id": 53, "defnames": [ "C13SPF.DEF", "C13SPF0.DEF" ] }, //merged
+		{ "id": 54, "defnames": [ "C16SPE.DEF", "C16SPE0.DEF" ] }, //merged
 		{ "id": 55, "defnames": [ "C17SPE0.DEF" ] },
-		{ "id": 56, "defnames": [ "C0ACID.DEF" ] },
+		{ "id": 56, "defnames": [ "C0ACID.DEF" ] },//merged
 		{ "id": 57, "defnames": [ "C09SPF1.DEF", "C09SPF2.DEF" ] },
 		{ "id": 58, "defnames": [ "C17SPE2.DEF" ] },
 		{ "id": 59, "defnames": [ "C09SPF0.DEF" ] },
 		{ "id": 62, "defnames": [ "C07SPF60.DEF", "C07SPF61.DEF", "C07SPF62.DEF" ] },
-		{ "id": 64, "defnames": [ "C20SPX.DEF" ] },
-		{ "id": 67, "defnames": [ "SP11_.DEF" ] },
-		{ "id": 68, "defnames": [ "SP02_.DEF" ] },
-		{ "id": 69, "defnames": [ "SP05_.DEF" ] },
-		{ "id": 71, "defnames": [ "SP01_.DEF" ] },
-		{ "id": 72, "defnames": [ "SP04_.DEF" ] },
+		{ "id": 64, "defnames": [ "C20SPX.DEF" ] }, //merged
+		{ "id": 67, "defnames": [ "SP11_.DEF" ] }, //merged
+		{ "id": 68, "defnames": [ "SP02_.DEF" ] }, //merged
+		{ "id": 69, "defnames": [ "SP05_.DEF" ] }, //merged
+		{ "id": 71, "defnames": [ "SP01_.DEF" ] }, //merged
+		{ "id": 72, "defnames": [ "SP04_.DEF" ] }, //merged
 		{ "id": 73, "defnames": [ "SP03_.DEF" ] },
 		{ "id": 74, "defnames": [ "SP12_.DEF" ] },
 		{ "id": 75, "defnames": [ "SP07_A.DEF" ] },
 		{ "id": 76, "defnames": [ "SP07_B.DEF" ] },
 		{ "id": 77, "defnames": [ "SP08_.DEF" ] },
 		{ "id": 78, "defnames": [ "SP09_.DEF" ] },
-		{ "id": 79, "defnames": [ "C01SPE0.DEF" ] },
-		{ "id": 80, "defnames": [ "C07SPE0.DEF" ] },
-		{ "id": 81, "defnames": [ "C17SPW0.DEF" ] },
+		{ "id": 79, "defnames": [ "C01SPE0.DEF" ] },//merged
+		{ "id": 80, "defnames": [ "C07SPE0.DEF" ] },//merged
+		{ "id": 81, "defnames": [ "C17SPW0.DEF" ] },//merged
 		{ "id": 82, "defnames": [ "C09SPF3.DEF" ] },
 		{ "id": 84, "defnames": [ "ZMGC02.DEF" ] }
 	]

+ 66 - 10
config/schemas/spell.json

@@ -8,6 +8,46 @@
 
 
 	"definitions" : {
+		"animationQueue":{
+			"type": "array",
+			"items":{
+				"anyOf":[
+					{
+						//assumed verticalPosition: top
+						"type": "string",
+						"format": "defFile"				
+					},
+					{
+						"type": "object",
+						"properties":{
+							"verticalPosition": {"type":"string", "enum":["top","bottom"]},
+							"defName": {"type":"string", "format": "defFile"}
+						},					
+						"additionalProperties" : false				
+					}
+				]					
+			}			
+		},
+		"animation":{
+			"type": "object",
+			"additionalProperties" : false,
+			"properties":{
+				"affect":{"$ref" : "#/definitions/animationQueue"},				
+				"hit":{"$ref" : "#/definitions/animationQueue"},				
+				"cast":{"$ref" : "#/definitions/animationQueue"},
+				"projectile":{
+					"type":"array",
+					"items":{
+						"type": "object",
+						"properties":{
+							"minimumAngle": {"type":"number", "minimum" : 0},
+							"defName": {"type":"string", "format": "defFile"}
+						},					
+						"additionalProperties" : false						
+					}
+				}				
+			}		
+		},
 		"flags" :{
 			"type" : "object",
 			"additionalProperties" : {
@@ -53,10 +93,27 @@
 						"smart":{
 							"type": "boolean",
 							"description": "true: friendly/hostile based on positiveness; false: all targets"
-						}
+						},
+						"clearTarget":
+						{
+							"type": "boolean",
+							"description": "LOCATION target only. Target hex/tile must be clear"
+						},
+						"clearAffected":
+						{
+							"type": "boolean",
+							"description": "LOCATION target only. All affected hexes/tile must be clear"
+						}						
 					}
 				}
 			}
+		},
+		
+		"texts":{
+			"type": "object",
+			
+			
+			"additionalProperties" : false
 		}
 	},
 
@@ -114,18 +171,14 @@
 				"type": "object",
 				"description": "Chance in % to gain for faction. NOTE: this field is merged with faction config",
 				"additionalProperties" : {
-		 "type": "number",
-		 "minimum" : 0
-		}
+					"type": "number",
+					"minimum" : 0
+				}
 		},
 		"targetType":{
 				  "type": "string",
-				  "enum": ["NO_TARGET","CREATURE","OBSTACLE"]
-		},
-		"anim":{
-				"type": "number",
-				"description": "Main effect animation (AC format), -1 - none, deprecated",
-				"minimum": -1
+				  "description": "NO_TARGET - instant cast no aiming, default; CREATURE - target is unit; OBSTACLE - target is OBSTACLE; LOCATION - target is location",
+				  "enum": ["NO_TARGET","CREATURE","OBSTACLE","LOCATION"]
 		},
 		"counters":{
 				 "$ref" : "#/definitions/flags",
@@ -182,6 +235,9 @@
 				 "$ref" : "#/definitions/flags",
 				 "description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
 		},
+		
+		"animation":{"$ref": "#/definitions/animation"},
+		
 		"graphics":{
 				 "type": "object",
 				 "additionalProperties" : false,

+ 59 - 12
config/spells/ability.json

@@ -1,7 +1,10 @@
 {
 	"stoneGaze" : {
 		"index" : 70,
-		"anim" : 70,
+		"targetType": "NO_TARGET",		
+		"animation":{
+			//need special animation
+		},		
 		"sounds": {
 			"cast": "PARALYZE"
 		},
@@ -33,7 +36,11 @@
 	},
 	"poison" : {
 		"index" : 71,
-		"anim" : 67,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["SP11_"]
+		},		
 		"sounds": {
 			"cast": "POISON"
 		},
@@ -67,7 +74,11 @@
 	},
 	"bind" : {
 		"index" : 72,
-		"anim" : 68,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["SP02_"]
+		},		
 		"sounds": {
 			"cast": "BIND"
 		},
@@ -90,7 +101,11 @@
 	},
 	"disease" : {
 		"index" : 73,
-		"anim" : 69,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["SP05_"]
+		},		
 		"sounds": {
 			"cast": "DISEASE"
 		},
@@ -124,7 +139,11 @@
 	},
 	"paralyze" : {
 		"index" : 74,
-		"anim" : 70,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			//missing
+		},		
 		"sounds": {
 			"cast": "PARALYZE"
 		},
@@ -156,7 +175,11 @@
 	},
 	"age" : {
 		"index" : 75,
-		"anim" : 71,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["SP01_"]
+		},		
 		"sounds": {
 			"cast": "AGE"
 		},
@@ -184,7 +207,11 @@
 	},
 	"deathCloud" : {
 		"index" : 76,
-		"anim" : 72,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"hit":["SP04_"]
+		},		
 		"sounds": {
 			"cast": "DEATHCLD"
 		},
@@ -203,7 +230,11 @@
 	},
 	"thunderbolt" : {
 		"index" : 77,
-		"anim" : 38,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
+		},		
 		"sounds": {
 			"cast": "LIGHTBLT"
 		},
@@ -220,7 +251,11 @@
 	},
 	"dispelHelpful" : {
 		"index" : 78,
-		"anim" : 41,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["C05SPW"]
+		},		
 		"sounds": {
 			"cast": "DISPELL"
 		},
@@ -236,7 +271,11 @@
 	},
 	"deathStare" : {
 		"index" : 79,
-		"anim" : 80,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["C07SPE0"]
+		},		
 		"sounds": {
 			"cast": "DEATHSTR"
 		},
@@ -255,7 +294,11 @@
 	},
 	"acidBreath" : {
 		"index" : 80,
-		"anim" : 81,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			//???
+		},		
 		"sounds": {
 			"cast": "ACID"
 		},
@@ -280,7 +323,11 @@
 	},
 	"acidBreathDamage" : {
 		"index" : 81,
-		"anim" : 81,
+		"targetType": "NO_TARGET",
+		
+		"animation":{
+			"affect":["C17SPW0"]
+		},		
 		"sounds": {
 			"cast": "ACID"
 		},

+ 20 - 10
config/spells/adventure.json

@@ -1,7 +1,8 @@
 {
 	"summonBoat" : {
 		"index" : 0,
-		"anim" : -1,
+		
+		"targetType": "NO_TARGET",
 		"sounds": {
 			"cast": "SUMMBOAT"
 		},
@@ -16,7 +17,8 @@
 	},
 	"scuttleBoat" : {
 		"index" : 1,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "SCUTBOAT"
 		},
@@ -31,7 +33,8 @@
 	},
 	"visions" : {
 		"index" : 2,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "VISIONS"
 		},
@@ -46,7 +49,8 @@
 	},
 	"viewEarth" : {
 		"index" : 3,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "VIEW"
 		},
@@ -61,7 +65,8 @@
 	},
 	"disguise" : {
 		"index" : 4,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "DISGUISE"
 		},
@@ -76,7 +81,8 @@
 	},
 	"viewAir" : {
 		"index" : 5,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "VIEW"
 		},
@@ -91,7 +97,8 @@
 	},
 	"fly" : {
 		"index" : 6,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "FLYSPELL"
 		},
@@ -106,7 +113,8 @@
 	},
 	"waterWalk" : {
 		"index" : 7,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "WATRWALK"
 		},
@@ -121,7 +129,8 @@
 	},
 	"dimensionDoor" : {
 		"index" : 8,
-		"anim" : -1,
+		"targetType": "LOCATION",
+		
 		"sounds": {
 			"cast": "TELPTOUT"
 		},
@@ -136,7 +145,8 @@
 	},
 	"townPortal" : {
 		"index" : 9,
-		"anim" : -1,
+		"targetType": "NO_TARGET",
+		
 		"sounds": {
 			"cast": "TELPTOUT"
 		},

+ 77 - 14
config/spells/offensive.json

@@ -1,7 +1,18 @@
 {
 	"magicArrow" : {
 		"index" : 15,
-		"anim" : 64,
+		"targetType": "CREATURE",
+		
+		"animation":{
+			"projectile": [
+				{"minimumAngle": 0 ,"defName":"C20SPX4"},
+				{"minimumAngle": 0.60 ,"defName":"C20SPX3"},
+				{"minimumAngle": 0.90 ,"defName":"C20SPX2"},
+				{"minimumAngle": 1.20 ,"defName":"C20SPX1"},
+				{"minimumAngle": 1.50 ,"defName":"C20SPX0"}
+			],
+			"hit":["C20SPX"]
+		},
 		"sounds": {
 			"cast": "MAGICBLT"
 		},
@@ -22,7 +33,18 @@
 	},
 	"iceBolt" : {
 		"index" : 16,
-		"anim" : 46,
+		"targetType": "CREATURE",
+		
+		"animation":{
+			"projectile": [
+				{"minimumAngle": 0 ,"defName":"C08SPW4"},
+				{"minimumAngle": 0.60 ,"defName":"C08SPW3"},
+				{"minimumAngle": 0.90 ,"defName":"C08SPW2"},
+				{"minimumAngle": 1.20 ,"defName":"C08SPW1"},
+				{"minimumAngle": 1.50 ,"defName":"C08SPW0"}
+			],
+			"hit":["C08SPW5"]
+		},		
 		"sounds": {
 			"cast": "ICERAY"
 		},
@@ -43,7 +65,11 @@
 	},
 	"lightningBolt" : {
 		"index" : 17,
-		"anim" : 38,
+		"targetType": "CREATURE",
+		
+		"animation":{
+			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
+		},		
 		"sounds": {
 			"cast": "LIGHTBLT"
 		},
@@ -64,7 +90,11 @@
 	},
 	"implosion" : {
 		"index" : 18,
-		"anim" : 10,
+		"targetType": "CREATURE",
+		
+		"animation":{
+			"affect":["C05SPE0"]
+		},
 		"sounds": {
 			"cast": "DECAY"
 		},
@@ -86,7 +116,11 @@
 	},
 	"chainLightning" : {
 		"index" : 19,
-		"anim" : 38,
+		"targetType": "CREATURE",
+		
+		"animation":{
+			"affect":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
+		},		
 		"sounds": {
 			"cast": "CHAINLTE"
 		},
@@ -104,7 +138,11 @@
 	},
 	"frostRing" : {
 		"index" : 20,
-		"anim" : 45,
+		"targetType": "LOCATION",
+		
+		"animation":{
+			"hit":["C07SPW"] //C07SPW0 ???
+		},			
 		"sounds": {
 			"cast": "FROSTING"
 		},
@@ -125,7 +163,11 @@
 	},
 	"fireball" : {
 		"index" : 21,
-		"anim" : 53,
+		"targetType": "LOCATION",
+		
+		"animation":{
+			"hit":["C13SPF"] //C13SPF0 ???
+		},		
 		"sounds": {
 			"cast": "FIREBALL"
 		},
@@ -146,7 +188,11 @@
 	},
 	"inferno" : {
 		"index" : 22,
-		"anim" : 9,
+		"targetType": "LOCATION",
+		
+		"animation":{
+			"hit":["C04SPF0"]
+		},		
 		"sounds": {
 			"cast": "FIREBLST"
 		},
@@ -167,7 +213,11 @@
 	},
 	"meteorShower" : {
 		"index" : 23,
-		"anim" : 16,
+		"targetType": "LOCATION",
+		
+		"animation":{
+			"hit":["C08SPE0"]
+		},		
 		"sounds": {
 			"cast": "METEOR"
 		},
@@ -189,7 +239,10 @@
 	"deathRipple" : {
 		"index" : 24,
 		"targetType" : "CREATURE",
-		"anim" : 8,
+		
+		"animation":{
+			"affect":["C04SPE0"]
+		},		
 		"sounds": {
 			"cast": "DEATHRIP"
 		},
@@ -215,7 +268,10 @@
 	"destroyUndead" : {
 		"index" : 25,
 		"targetType" : "CREATURE",
-		"anim" : 29,
+		
+		"animation":{
+			"affect":["C14SPA0"]
+		},		
 		"sounds": {
 			"cast": "COLDRING"
 		},
@@ -240,7 +296,10 @@
 	"armageddon" : {
 		"index" : 26,
 		"targetType" : "CREATURE",
-		"anim" : 12,
+		
+		"animation":{
+			"hit":["C06SPF0"]
+		},		
 		"sounds": {
 			"cast": "ARMGEDN"
 		},
@@ -261,7 +320,11 @@
 	},
 	"titanBolt" : {
 		"index" : 57,
-		"anim" : 38,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"hit":[{"defName":"C03SPA0", "verticalPosition":"bottom"}, "C11SPA1"]
+		},		
 		"sounds": {
 			"cast": "LIGHTBLT"
 		},
@@ -277,5 +340,5 @@
 			"negative": true,
 			"special": true
 		}
-	},
+	}
 }

+ 57 - 19
config/spells/other.json

@@ -1,7 +1,8 @@
 {
 	"quicksand" : {
 		"index" : 10,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",
+		
 		"sounds": {
 			"cast": "QUIKSAND"
 		},
@@ -16,7 +17,8 @@
 	},
 	"landMine" : {
 		"index" : 11,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",
+		
 		"sounds": {
 			"cast": ""
 		},
@@ -35,13 +37,17 @@
 	},
 	"forceField" : {
 		"index" : 12,
-		"anim" : -1,
+		"targetType" : "LOCATION",
+		
 		"sounds": {
 			"cast": "FORCEFLD"
 		},
 		"levels" : {
 			"base":{
-				"range" : "0"
+				"range" : "0",
+				"targetModifier":{
+					"clearAffected": true
+				}
 			}
 		},
 		"flags" : {
@@ -50,13 +56,17 @@
 	},
 	"fireWall" : {
 		"index" : 13,
-		"anim" : -1,
+		"targetType" : "LOCATION",		
+		
 		"sounds": {
 			"cast": "FIREWALL"
 		},
 		"levels" : {
 			"base":{
-				"range" : "0"
+				"range" : "0",
+				"targetModifier":{
+					"clearAffected": true
+				}				
 			}
 		},
 		"flags" : {
@@ -69,7 +79,8 @@
 	},
 	"earthquake" : {
 		"index" : 14,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",		
+		
 		"sounds": {
 			"cast": "ERTHQUAK"
 		},
@@ -85,7 +96,11 @@
 	
 	"dispel" : {
 		"index" : 35,
-		"anim" : 41,
+		"targetType" : "CREATURE",			
+		
+		"animation":{
+			"affect":["C05SPW"] //C05SPW0
+		},			
 		"sounds": {
 			"cast": "DISPELL"
 		},
@@ -103,7 +118,11 @@
 	},
 	"cure" : {
 		"index" : 37,
-		"anim" : 39,
+		"targetType" : "CREATURE",		
+		"animation":{
+			"affect":["C03SPW"]//C03SPW0
+		},
+		
 		"sounds": {
 			"cast": "CURE"
 		},
@@ -122,7 +141,11 @@
 	},
 	"resurrection" : {
 		"index" : 38,
-		"anim" : 79,
+		"targetType" : "CREATURE",			
+		
+		"animation":{
+			"affect":["C01SPE0"]
+		},
 		"sounds": {
 			"cast": "RESURECT"
 		},
@@ -143,7 +166,11 @@
 	},
 	"animateDead" : {
 		"index" : 39,
-		"anim" : 79,
+		"targetType" : "CREATURE",			
+		
+		"animation":{
+			"affect":["C01SPE0"]
+		},		
 		"sounds": {
 			"cast": "ANIMDEAD"
 		},
@@ -163,7 +190,11 @@
 	},
 	"sacrifice" : {
 		"index" : 40,
-		"anim" : 79,
+		"targetType" : "CREATURE",			
+		
+		"animation":{
+			"affect":["C01SPE0"]
+		},		
 		"sounds": {
 			"cast": "SACRIF1"
 		},
@@ -184,7 +215,8 @@
 	},
 	"teleport" : {
 		"index" : 63,
-		"anim" : -1,
+		"targetType" : "CREATURE",			
+		
 		"sounds": {
 			"cast": "TELPTOUT"
 		},
@@ -203,7 +235,8 @@
 	},
 	"removeObstacle" : {
 		"index" : 64,
-		"anim" : -1,
+		"targetType" : "OBSTACLE",		
+		
 		"sounds": {
 			"cast": "REMOVEOB"
 		},
@@ -218,7 +251,8 @@
 	},
 	"clone" : {
 		"index" : 65,
-		"anim" : -1,
+		"targetType" : "CREATURE",		
+		
 		"sounds": {
 			"cast": "CLONE"
 		},
@@ -237,7 +271,8 @@
 	},
 	"fireElemental" : {
 		"index" : 66,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",		
+		
 		"sounds": {
 			"cast": "SUMNELM"
 		},
@@ -252,7 +287,8 @@
 	},
 	"earthElemental" : {
 		"index" : 67,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",	
+		
 		"sounds": {
 			"cast": "SUMNELM"
 		},
@@ -267,7 +303,8 @@
 	},
 	"waterElemental" : {
 		"index" : 68,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",	
+		
 		"sounds": {
 			"cast": "SUMNELM"
 		},
@@ -282,7 +319,8 @@
 	},
 	"airElemental" : {
 		"index" : 69,
-		"anim" : -1,
+		"targetType" : "NO_TARGET",	
+		
 		"sounds": {
 			"cast": "SUMNELM"
 		},

+ 150 - 32
config/spells/timed.json

@@ -1,7 +1,11 @@
 {
 	"shield" : {
 		"index" : 27,
-		"anim" : 27,
+		"targetType" : "CREATURE",			
+		
+		"animation":{
+			"affect":["C13SPE0"]
+		},		
 		"sounds": {
 			"cast": "SHIELD"
 		},
@@ -27,7 +31,11 @@
 	},
 	"airShield" : {
 		"index" : 28,
-		"anim" : 2,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C01SPA0"]
+		},		
 		"sounds": {
 			"cast": "AIRSHELD"
 		},
@@ -53,7 +61,11 @@
 	},
 	"fireShield" : {
 		"index" : 29,
-		"anim" : 11,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C05SPF0"]
+		},		
 		"sounds": {
 			"cast": "FIRESHIE"
 		},
@@ -81,7 +93,11 @@
 	},
 	"protectAir" : {
 		"index" : 30,
-		"anim" : 22,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C11SPE0"]
+		},		
 		"sounds": {
 			"cast": "PROTECTA"
 		},
@@ -107,7 +123,11 @@
 	},
 	"protectFire" : {
 		"index" : 31,
-		"anim" : 24,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C11SPW0"]
+		},		
 		"sounds": {
 			"cast": "PROTECTF"
 		},
@@ -133,7 +153,11 @@
 	},
 	"protectWater" : {
 		"index" : 32,
-		"anim" : 23,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C11SPF0"]
+		},		
 		"sounds": {
 			"cast": "PROTECTW"
 		},
@@ -159,7 +183,11 @@
 	},
 	"protectEarth" : {
 		"index" : 33,
-		"anim" : 26,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C13SPA0"]
+		},		
 		"sounds": {
 			"cast": "PROTECTE"
 		},
@@ -185,7 +213,11 @@
 	},
 	"antiMagic" : {
 		"index" : 34,
-		"anim" : 5,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C02SPE0"]
+		},		
 		"sounds": {
 			"cast": "ANTIMAGK"
 		},
@@ -224,7 +256,11 @@
 
 	"magicMirror" : {
 		"index" : 36,
-		"anim" : 3,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C02SPA0"]
+		},		
 		"sounds": {
 			"cast": "BACKLASH"
 		},
@@ -245,10 +281,13 @@
 			"positive": true
 		}
 	},
-
 	"bless" : {
 		"index" : 41,
-		"anim" : 36,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C01SPW"] //C01SPW0
+		},		
 		"sounds": {
 			"cast": "BLESS"
 		},
@@ -282,7 +321,11 @@
 	},
 	"curse" : {
 		"index" : 42,
-		"anim" : 40,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C04SPW"]//C04SPW0
+		},		
 		"sounds": {
 			"cast": "CURSE"
 		},
@@ -317,7 +360,11 @@
 	},
 	"bloodlust" : {
 		"index" : 43,
-		"anim" : 4,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["SP12_"] //???
+		},		
 		"sounds": {
 			"cast": "BLOODLUS"
 		},
@@ -360,7 +407,11 @@
 	},
 	"precision" : {
 		"index" : 44,
-		"anim" : 25,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C12SPA0"]
+		},		
 		"sounds": {
 			"cast": "PRECISON"
 		},
@@ -403,7 +454,11 @@
 	},
 	"weakness" : {
 		"index" : 45,
-		"anim" : 56,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C0ACID"]
+		},		
 		"sounds": {
 			"cast": "WEAKNESS"
 		},
@@ -446,7 +501,11 @@
 	},
 	"stoneSkin" : {
 		"index" : 46,
-		"anim" : 54,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C16SPE"] //C16SPE0
+		},		
 		"sounds": {
 			"cast": "TUFFSKIN"
 		},
@@ -485,8 +544,12 @@
 	},
 	"disruptingRay" : {
 		"index" : 47,
-		"targetType" : "CREATURE", //fix, dont remove
-		"anim" : 14,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C07SPA1"],
+			"projectile":[{"defName":"C07SPA0"}]//???
+		},		
 		"sounds": {
 			"cast": "DISRUPTR"
 		},
@@ -525,7 +588,11 @@
 	},
 	"prayer" : {
 		"index" : 48,
-		"anim" : 0,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":[{"defName":"C10SPW", "verticalPosition":"bottom"}]
+		},		
 		"sounds": {
 			"cast": "PRAYER"
 		},
@@ -588,7 +655,11 @@
 	},
 	"mirth" : {
 		"index" : 49,
-		"anim" : 20,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C09SPW0"]
+		},		
 		"sounds": {
 			"cast": "MIRTH"
 		},
@@ -636,7 +707,11 @@
 	},
 	"sorrow" : {
 		"index" : 50,
-		"anim" : 30,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C14SPE0"]
+		},		
 		"sounds": {
 			"cast": "SORROW"
 		},
@@ -684,7 +759,11 @@
 	},
 	"fortune" : {
 		"index" : 51,
-		"anim" : 18,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C09SPA0"]
+		},		
 		"sounds": {
 			"cast": "FORTUNE"
 		},
@@ -725,7 +804,11 @@
 	},
 	"misfortune" : {
 		"index" : 52,
-		"anim" : 48,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C10SPF0"]
+		},		
 		"sounds": {
 			"cast": "MISFORT"
 		},
@@ -766,7 +849,11 @@
 	},
 	"haste" : {
 		"index" : 53,
-		"anim" : 31,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C15SPA0"]
+		},		
 		"sounds": {
 			"cast": "HASTE"
 		},
@@ -811,7 +898,11 @@
 	},
 	"slow" : {
 		"index" : 54,
-		"anim" : 19,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":[{"defName":"C09SPE0", "verticalPosition":"bottom"}]
+		},		
 		"sounds": {
 			"cast": "MUCKMIRE"
 		},
@@ -858,7 +949,11 @@
 	},
 	"slayer" : {
 		"index" : 55,
-		"anim" : 28,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C13SPW0"]
+		},		
 		"sounds": {
 			"cast": "SLAYER"
 		},
@@ -908,7 +1003,11 @@
 	},
 	"frenzy" : {
 		"index" : 56,
-		"anim" : 17,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C08SPF0"]
+		},		
 		"sounds": {
 			"cast": "FRENZY"
 		},
@@ -946,7 +1045,11 @@
 
 	"counterstrike" : {
 		"index" : 58,
-		"anim" : 7,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C04SPA0"]
+		},		
 		"sounds": {
 			"cast": "CNTRSTRK"
 		},
@@ -984,7 +1087,11 @@
 	},
 	"berserk" : {
 		"index" : 59,
-		"anim" : 35,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C01SPF"] //C01SPF0
+		},		
 		"sounds": {
 			"cast": "BERSERK"
 		},
@@ -1042,7 +1149,11 @@
 	},
 	"hypnotize" : {
 		"index" : 60,
-		"anim" : 21,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C10SPA0"]
+		},		
 		"sounds": {
 			"cast": "HYPNOTIZ"
 		},
@@ -1099,7 +1210,10 @@
 	"forgetfulness" : {
 		"index" : 61,
 		"targetType" : "CREATURE", 
-		"anim" : 42,
+		
+		"animation":{
+			"affect":["C06SPW"]//C06SPW0
+		},		
 		"sounds": {
 			"cast": "FORGET"
 		},
@@ -1158,7 +1272,11 @@
 	},
 	"blind" : {
 		"index" : 62,
-		"anim" : 6,
+		"targetType" : "CREATURE",
+		
+		"animation":{
+			"affect":["C02SPF0"]
+		},		
 		"sounds": {
 			"cast": "BLIND"
 		},

+ 0 - 48
lib/BattleState.cpp

@@ -118,24 +118,6 @@ void BattleInfo::calculateCasualties( std::map<ui32,si32> *casualties ) const
 	}
 }
 
-int BattleInfo::calculateSpellDuration( const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower)
-{
-	if(!caster)
-	{
-		if (!usedSpellPower)
-			return 3; //default duration of all creature spells
-		else
-			return usedSpellPower; //use creature spell power
-	}
-	switch(spell->id)
-	{
-	case SpellID::FRENZY:
-		return 1;
-	default: //other spells
-		return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
-	}
-}
-
 CStack * BattleInfo::generateNewStack(const CStackInstance &base, bool attackerOwned, SlotID slot, BattleHex position) const
 {
 	int stackID = getIdForNewStack();
@@ -159,36 +141,6 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at
 	return ret;
 }
 
-//All spells casted by hero 9resurrection, cure, sacrifice)
-ui32 CBattleInfoCallback::calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack) const
-{
-	bool resurrect = spell->isRisingSpell();
-	int healedHealth;
-	if (spell->id == SpellID::SACRIFICE && sacrificedStack)
-		healedHealth = (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + sacrificedStack->MaxHealth() + spell->getPower(caster->getSpellSchoolLevel(spell))) * sacrificedStack->count;
-	else
-		healedHealth = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) * spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)); //???
-	healedHealth = calculateSpellBonus(healedHealth, spell, caster, stack);
-	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
-}
-//Archangel
-ui32 CBattleInfoCallback::calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const
-{
-	bool resurrect = spell->isRisingSpell();
-	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
-}
-//Casted by stack, no hero bonus applied
-ui32 CBattleInfoCallback::calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const
-{
-	bool resurrect = spell->isRisingSpell();
-	int healedHealth = usedSpellPower * spell->power + spell->getPower(spellSchoolLevel);
-	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (resurrect ? stack->baseAmount * stack->MaxHealth() : 0));
-}
-bool BattleInfo::resurrects(SpellID spellid) const
-{
-	return spellid.toSpell()->isRisingSpell();
-}
-
 const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
 {
 	CStack * stack = nullptr;

+ 1 - 2
lib/BattleState.h

@@ -136,12 +136,11 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	//void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee
 	//std::set<CStack*> getAttackedCreatures(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
 	//std::set<BattleHex> getAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID); //calculates range of multi-hex attacks
-	static int calculateSpellDuration(const CSpell * spell, const CGHeroInstance * caster, int usedSpellPower);
+
 	CStack * generateNewStack(const CStackInstance &base, bool attackerOwned, SlotID slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
 	CStack * generateNewStack(const CStackBasicDescriptor &base, bool attackerOwned, SlotID slot, BattleHex position) const; //helper for CGameHandler::setupBattle and spells addign new stacks to the battlefield
 	int getIdForNewStack() const; //suggest a currently unused ID that'd suitable for generating a new stack
 	//std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const; //if attackerOwned is indetermnate, returened stack is of any owner; hex is the number of hex we should be looking from; returns (nerarest creature, predecessorHex)
-	bool resurrects(SpellID spellid) const; //TODO: move it to spellHandler?
 
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 

+ 111 - 407
lib/CBattleCallback.cpp

@@ -1565,145 +1565,6 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
 	return attackableBattleHexes;
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-
-	// Get all stacks at destination hex -> subject of our spell. only alive if not rising spell
-	TStacks stacks = battleGetStacksIf([=](const CStack * s){
-		return s->coversPos(dest) && (spell->isRisingSpell() || s->alive());
-	});
-	
-	if(!stacks.empty())
-	{
-		bool allImmune = true;
-		
-		ESpellCastProblem::ESpellCastProblem problem;		
-		
-		for(auto s : stacks)
-		{
-			ESpellCastProblem::ESpellCastProblem res = battleStackIsImmune(caster,spell,mode,s);
-			
-			if(res == ESpellCastProblem::OK)
-			{
-				allImmune = false;
-			}
-			else
-			{
-				problem = res;
-			}
-		}
-		
-		if(allImmune)
-			return problem;
-	}
-	else //no target stack on this tile
-	{
-		if(spell->getTargetType() == CSpell::CREATURE)
-		{
-			if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
-			{
-				const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
-				
-				if(!ti.massive)
-					return ESpellCastProblem::WRONG_SPELL_TARGET;					
-			}
-			else
-			{
- 				return ESpellCastProblem::WRONG_SPELL_TARGET;
-			}
-			
-		}
-	}
-
-	return ESpellCastProblem::OK;
-}
-
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const
-{
-	if (spell->isPositive() && subject->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
-		return ESpellCastProblem::OK;
-
-	if (spell->isImmuneBy(subject)) //TODO: move all logic to spellhandler
-		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-
-	switch (spell->id) //TODO: more general logic for new spells?
-	{
-	case SpellID::CLONE:
-		{
-			//can't clone already cloned creature
-			if (vstd::contains(subject->state, EBattleStackState::CLONED))
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			//TODO: how about stacks casting Clone?
-			//currently Clone casted by stack is assumed Expert level
-			ui8 schoolLevel;
-			if (caster)
-			{
-				schoolLevel = caster->getSpellSchoolLevel(spell);
-			}
-			else
-			{
-				schoolLevel = 3;
-			}
-
-			if (schoolLevel < 3)
-			{
-				int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
-				int creLevel = subject->getCreature()->level;
-				if (maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
-					return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-			}
-		}
-		break;
-	case SpellID::DISPEL_HELPFUL_SPELLS:
-		{
-			TBonusListPtr spellBon = subject->getSpellBonuses();
-			bool hasPositiveSpell = false;
-			for(const Bonus * b : *spellBon)
-			{
-				if(SpellID(b->sid).toSpell()->isPositive())
-				{
-					hasPositiveSpell = true;
-					break;
-				}
-			}
-			if(!hasPositiveSpell)
-			{
-				return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
-			}
-		}
-		break;
-	}
-
-    if (spell->isRisingSpell() && spell->id != SpellID::SACRIFICE)
-	{
-        // following does apply to resurrect and animate dead(?) only
-        // for sacrifice health calculation and health limit check don't matter
-
-		if(subject->count >= subject->baseAmount)
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		
-		if (caster) //FIXME: Archangels can cast immune stack
-		{
-			auto maxHealth = calculateHealedHP (caster, spell, subject);
-			if (maxHealth < subject->MaxHealth()) //must be able to rise at least one full creature
-				return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-		}
-	}
-	else if(spell->id == SpellID::HYPNOTIZE && caster) //do not resist hypnotize casted after attack, for example
-	{
-		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
-		ui64 subjectHealth = (subject->count - 1) * subject->MaxHealth() + subject->firstHPleft;
-		//apply 'damage' bonus for hypnotize, including hero specialty
-		ui64 maxHealth = calculateSpellBonus (caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
-			* spell->power + spell->getPower(caster->getSpellSchoolLevel(spell)), spell, caster, subject);
-		if (subjectHealth > maxHealth)
-			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
-	}	
-		
-	return ESpellCastProblem::OK;
-}
-
 ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
@@ -1746,7 +1607,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
 		for(auto stack : stacks)
 		{
-			if( ESpellCastProblem::OK == battleStackIsImmune(castingHero, spell, mode, stack))
+			if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
 			{
 				allStacksImmune = false;
 				break;
@@ -1790,7 +1651,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 
             for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
 			{
-				bool immune =  ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
+				bool immune =  ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
                 if(spell->id == SpellID::SACRIFICE)
@@ -1847,8 +1708,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:
@@ -1858,7 +1717,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 			
 			for(const CStack * stack : battleAliveStacks())
 			{
-				bool immune = ESpellCastProblem::OK != battleStackIsImmune(caster, spell, mode, stack);
+				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
 				bool casterStack = stack->owner == caster->getOwner();
 				
 				if(!immune)
@@ -1985,199 +1844,11 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
 
-
+	const CGHeroInstance * caster = nullptr;
 	if (mode == ECastingMode::HERO_CASTING)
-		return battleIsImmune(battleGetFightingHero(playerToSide(player)), spell, mode, dest);
-	else
-		return battleIsImmune(nullptr, spell, mode, dest);
-}
-
-ui32 CBattleInfoCallback::calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const
-{
-	ui32 ret = baseDamage;
-	//applying sorcery secondary skill
-	if(caster)
-	{
-		ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
-		ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, sp->id.toEnum())) / 100.0;
-
-		if(sp->air)
-			ret *= (100.0 + caster->valOfBonuses(Bonus::AIR_SPELL_DMG_PREMY)) / 100.0;
-		else if(sp->fire) //only one type of bonus for Magic Arrow
-			ret *= (100.0 + caster->valOfBonuses(Bonus::FIRE_SPELL_DMG_PREMY)) / 100.0;
-		else if(sp->water)
-			ret *= (100.0 + caster->valOfBonuses(Bonus::WATER_SPELL_DMG_PREMY)) / 100.0;
-		else if(sp->earth)
-			ret *= (100.0 + caster->valOfBonuses(Bonus::EARTH_SPELL_DMG_PREMY)) / 100.0;
-
-		if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
-			ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, sp->id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
-	}
-	return ret;
-}
-
-ui32 CBattleInfoCallback::calculateSpellDmg( const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower ) const
-{
-	ui32 ret = 0; //value to return
-
-	//check if spell really does damage - if not, return 0
-	if(!sp->isDamageSpell())
-		return 0;
-
-	ret = usedSpellPower * sp->power;
-	ret += sp->getPower(spellSchoolLevel);
-
-	//affected creature-specific part
-	if(affectedCreature)
-	{
-		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
-		if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 0)) //air spell & protection from air
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 0);
-			ret /= 100;
-		}
-		else if(sp->fire && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 1)) //fire spell & protection from fire
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 1);
-			ret /= 100;
-		}
-		else if(sp->water && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 2)) //water spell & protection from water
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 2);
-			ret /= 100;
-		}
-		else if (sp->earth && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, 3)) //earth spell & protection from earth
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, 3);
-			ret /= 100;
-		}
-		//general spell dmg reduction
-		//FIXME?
-		if(sp->air && affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
-			ret /= 100;
-		}
-		//dmg increasing
-		if( affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id) )
-		{
-			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, sp->id.toEnum());
-			ret /= 100;
-		}
-	}
-	ret = calculateSpellBonus(ret, sp, caster, affectedCreature);
-	return ret;
-}
-
-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
+		caster = battleGetFightingHero(playerToSide(player));
 	
-			//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;
+	return spell->isImmuneAt(this, caster, mode, dest);
 }
 
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
@@ -2220,93 +1891,126 @@ std::set<const CStack*> CBattleInfoCallback:: batteAdjacentCreatures(const CStac
 SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) const
 {
 	RETURN_IF_NOT_BATTLE(SpellID::NONE);
-	std::vector<SpellID> possibleSpells;
-
-	for(const CSpell *spell : VLC->spellh->objects)
+	//This is complete list. No spells from mods.
+	//todo: this should be Spellbook of caster Stack
+	static const std::set<SpellID> allPossibleSpells = 
+	{
+		SpellID::AIR_SHIELD,
+		SpellID::ANTI_MAGIC,
+		SpellID::BLESS,		
+		SpellID::BLOODLUST,
+		SpellID::COUNTERSTRIKE,
+		SpellID::CURE,
+		SpellID::FIRE_SHIELD,		
+		SpellID::FORTUNE,
+		SpellID::HASTE,
+		SpellID::MAGIC_MIRROR,
+		SpellID::MIRTH,				
+		SpellID::PRAYER,
+		SpellID::PRECISION,
+		SpellID::PROTECTION_FROM_AIR,
+		SpellID::PROTECTION_FROM_EARTH,
+		SpellID::PROTECTION_FROM_FIRE,
+		SpellID::PROTECTION_FROM_WATER,
+		SpellID::SHIELD,
+		SpellID::SLAYER,
+		SpellID::STONE_SKIN
+	};
+	std::vector<SpellID> beneficialSpells;
+	
+	auto getAliveEnemy = [=](const std::function<bool(const CStack * )> & pred)
 	{
-		if (spell->isPositive() && !spell->isRisingSpell()) //only positive and not rising
+		return getStackIf([=](const CStack * stack)
 		{
-			if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spell->id)
-				|| battleCanCastThisSpellHere(subject->owner, spell, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
-				continue;
+			return pred(stack) && stack->owner != subject->owner && stack->alive();
+		});
+	};
 
-			switch (spell->id)
-			{
-			case SpellID::SHIELD:
-			case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
-				{
-					auto walker = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
-					{
-						return stack->owner != subject->owner && !stack->shots;
-					});
+	for(const SpellID spellID : allPossibleSpells)
+	{
+		if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spellID)
+			//TODO: this ability has special limitations
+			|| battleCanCastThisSpellHere(subject->owner, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+			continue;
 
-					if (!walker)
-						continue;
-				}
-				break;
-			case SpellID::AIR_SHIELD: //only against active shooters
+		switch (spellID)
+		{
+		case SpellID::SHIELD:
+		case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
+			{				
+				auto walker = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
 				{
+					return !stack->shots;
+				});
 
-					auto shooter = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
-					{
-						return stack->owner != subject->owner && stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
-					});
-					if (!shooter)
-						continue;
-				}
-				break;
-			case SpellID::ANTI_MAGIC:
-			case SpellID::MAGIC_MIRROR:
-				{
-					if (!battleHasHero(subject->attackerOwned)) //only if there is enemy hero
-						continue;
-				}
-				break;
-			case SpellID::CURE: //only damaged units - what about affected by curse?
-				{
-					if (subject->firstHPleft >= subject->MaxHealth())
-						continue;
-				}
-				break;
-			case SpellID::BLOODLUST:
-				{
-					if (subject->shots) //if can shoot - only if enemy uits are adjacent
-						continue;
-				}
-				break;
-			case SpellID::PRECISION:
+				if (!walker)
+					continue;
+			}
+			break;
+		case SpellID::AIR_SHIELD: //only against active shooters
+			{
+				auto shooter = getAliveEnemy([&](const CStack * stack) //look for enemy, non-shooting stack
 				{
-					if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
-						continue;
-				}
-				break;
-			case SpellID::SLAYER://only if monsters are present
+					return stack->hasBonusOfType(Bonus::SHOOTER) && stack->shots;
+				});
+				if (!shooter)
+					continue;
+			}
+			break;
+		case SpellID::ANTI_MAGIC:
+		case SpellID::MAGIC_MIRROR:
+		case SpellID::PROTECTION_FROM_AIR:
+		case SpellID::PROTECTION_FROM_EARTH:
+		case SpellID::PROTECTION_FROM_FIRE:
+		case SpellID::PROTECTION_FROM_WATER:				
+			{
+				const ui8 enemySide = (ui8)subject->attackerOwned;
+				//todo: only if enemy has spellbook
+				if (!battleHasHero(enemySide)) //only if there is enemy hero
+					continue;
+			}
+			break;
+		case SpellID::CURE: //only damaged units
+			{
+				//do not cast on affected by debuffs
+				if (subject->firstHPleft >= subject->MaxHealth())
+					continue;
+			}
+			break;
+		case SpellID::BLOODLUST:
+			{
+				if (subject->shots) //if can shoot - only if enemy uits are adjacent
+					continue;
+			}
+			break;
+		case SpellID::PRECISION:
+			{
+				if (!(subject->hasBonusOfType(Bonus::SHOOTER) && subject->shots))
+					continue;
+			}
+			break;
+		case SpellID::SLAYER://only if monsters are present
+			{
+				auto kingMonster = getAliveEnemy([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
 				{
-					auto kingMonster = getStackIf([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
-					{
-						const auto isKing = Selector::type(Bonus::KING1)
-							.Or(Selector::type(Bonus::KING2))
-							.Or(Selector::type(Bonus::KING3));
+					const auto isKing = Selector::type(Bonus::KING1)
+						.Or(Selector::type(Bonus::KING2))
+						.Or(Selector::type(Bonus::KING3));
 
-						return stack->owner != subject->owner  &&  stack->hasBonus(isKing);
-					});
+					return stack->hasBonus(isKing);
+				});
 
-					if (!kingMonster)
-						continue;
-				}
-				break;
-			case SpellID::TELEPORT: //issue 1928
-			case SpellID::CLONE: //not allowed
-				continue;
-				break;
+				if (!kingMonster)
+					continue;
 			}
-			possibleSpells.push_back(spell->id);
+			break;
 		}
+		beneficialSpells.push_back(spellID);		
 	}
 
-	if(!possibleSpells.empty())
+	if(!beneficialSpells.empty())
 	{
-		return *RandomGeneratorUtil::nextItem(possibleSpells, gs->getRandomGenerator());
+		return *RandomGeneratorUtil::nextItem(beneficialSpells, gs->getRandomGenerator());
 	}
 	else
 	{

+ 0 - 17
lib/CBattleCallback.h

@@ -281,21 +281,11 @@ public:
 	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
 	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
 	std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
-	ui32 calculateSpellBonus(ui32 baseDamage, const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature) const;
-	ui32 calculateSpellDmg(const CSpell * sp, const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; //calculates damage inflicted by spell
-	ui32 calculateHealedHP(const CGHeroInstance * caster, const CSpell * spell, const CStack * stack, const CStack * sacrificedStack = nullptr) const; //Sacrifice
-	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;
 	SpellID getRandomCastedSpell(const CStack * caster) const; //called at the beginning of turn for Faerie Dragon
 
-	//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 battleStackIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, const CStack * subject) const; 
-
-
 	const CStack * getStackIf(std::function<bool(const CStack*)> pred) const;
 
 	si8 battleHasShootingPenalty(const CStack * stack, BattleHex destHex)
@@ -321,17 +311,10 @@ public:
 	AccessibilityInfo getAccesibility(const std::vector<BattleHex> &accessibleHexes) const; //given hexes will be marked as accessible
 	std::pair<const CStack *, BattleHex> getNearestStack(const CStack * closest, boost::logic::tribool attackerOwned) const;
 protected:
-	
-	//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 battleIsImmune(const CGHeroInstance * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const;
-	
-	
 	ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters &params) const;
 	ReachabilityInfo makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters &params) const;
 	ReachabilityInfo makeBFS(const CStack *stack) const; //uses default parameters -> stack position and owner's perspective
 	std::set<BattleHex> getStoppers(BattlePerspective::BattlePerspective whichSidePerspective) const; //get hexes with stopping obstacles (quicksands)
-
-
 };
 
 class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback

+ 6 - 13
lib/CGameInfoCallback.cpp

@@ -17,6 +17,7 @@
 #include "BattleState.h" // for BattleInfo
 #include "NetPacks.h" // for InfoWindow
 #include "CModHandler.h"
+#include "CSpellHandler.h"
 
 //TODO make clean
 #define ERROR_VERBOSE_OR_NOT_RET_VAL_IF(cond, verbose, txt, retVal) do {if(cond){if(verbose)logGlobal->errorStream() << BOOST_CURRENT_FUNCTION << ": " << txt; return retVal;}} while(0)
@@ -170,19 +171,11 @@ int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstan
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 
 	ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
-	if(!gs->curB) //no battle
-	{
-		if (hero) //but we see hero's spellbook
-			return gs->curB->calculateSpellDmg(
-				sp, hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
-		else
-			return 0; //mage guild
-	}
-	//gs->getHero(gs->currentPlayer)
-	//const CGHeroInstance * ourHero = gs->curB->heroes[0]->tempOwner == player ? gs->curB->heroes[0] : gs->curB->heroes[1];
-	const CGHeroInstance * ourHero = hero;
-	return gs->curB->calculateSpellDmg(
-		sp, ourHero, nullptr, ourHero->getSpellSchoolLevel(sp), ourHero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
+
+	if (hero) //we see hero's spellbook
+		return sp->calculateDamage(hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
+	else
+		return 0; //mage guild
 }
 
 void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj)

+ 1 - 0
lib/CMakeLists.txt

@@ -83,6 +83,7 @@ set(lib_SRCS
 		VCMI_Lib.cpp
 		VCMIDirs.cpp
 		IHandlerBase.cpp
+                SpellMechanics.cpp
 
 		IGameCallback.cpp
 		CGameInfoCallback.cpp

+ 8 - 0
lib/CModHandler.cpp

@@ -99,6 +99,14 @@ void CIdentifierStorage::requestIdentifier(std::string scope, std::string type,
 	requestIdentifier(ObjectCallback(scope, pair.first, type, pair.second, callback, false));
 }
 
+void CIdentifierStorage::requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)>& callback)
+{
+	auto scopeAndFullName = splitString(fullName, ':');	
+	auto typeAndName = splitString(scopeAndFullName.second, '.');	
+	
+	requestIdentifier(ObjectCallback(scope, scopeAndFullName.first, typeAndName.first, typeAndName.second, callback, false));
+}
+
 void CIdentifierStorage::requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback)
 {
 	auto pair = splitString(name.String(), ':'); // remoteScope:name

+ 2 - 0
lib/CModHandler.h

@@ -72,6 +72,8 @@ public:
 	/// request identifier for specific object name.
 	/// Function callback will be called during ID resolution phase of loading
 	void requestIdentifier(std::string scope, std::string type, std::string name, const std::function<void(si32)> & callback);
+	///fullName = [remoteScope:]type.name
+	void requestIdentifier(std::string scope, std::string fullName, const std::function<void(si32)> & callback);	
 	void requestIdentifier(std::string type, const JsonNode & name, const std::function<void(si32)> & callback);
 	void requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback);
 

+ 453 - 277
lib/CSpellHandler.cpp

@@ -10,6 +10,14 @@
 #include "CModHandler.h"
 #include "StringConstants.h"
 
+#include "mapObjects/CGHeroInstance.h"
+#include "BattleState.h"
+#include "CBattleCallback.h"
+
+#include "SpellMechanics.h"
+
+
+
 /*
  * CSpellHandler.cpp, part of VCMI engine
  *
@@ -23,113 +31,55 @@
 namespace SpellConfig
 {
 	static const std::string LEVEL_NAMES[] = {"none", "basic", "advanced", "expert"};
-
-}
-
-namespace SRSLPraserHelpers
-{
-	static int XYToHex(int x, int y)
-	{
-		return x + 17 * y;
-	}
-
-	static int XYToHex(std::pair<int, int> xy)
-	{
-		return XYToHex(xy.first, xy.second);
-	}
-
-	static int hexToY(int battleFieldPosition)
-	{
-		return battleFieldPosition/17;
-	}
-
-	static int hexToX(int battleFieldPosition)
-	{
-		int pos = battleFieldPosition - hexToY(battleFieldPosition) * 17;
-		return pos;
-	}
-
-	static std::pair<int, int> hexToPair(int battleFieldPosition)
-	{
-		return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
-	}
-
-	//moves hex by one hex in given direction
-	//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
-	static std::pair<int, int> gotoDir(int x, int y, int direction)
+	
+	static const SpellSchoolInfo SCHOOL[4] = 
 	{
-		switch(direction)
 		{
-		case 0: //top left
-			return std::make_pair((y%2) ? x-1 : x, y-1);
-		case 1: //top right
-			return std::make_pair((y%2) ? x : x+1, y-1);
-		case 2:  //right
-			return std::make_pair(x+1, y);
-		case 3: //right bottom
-			return std::make_pair((y%2) ? x : x+1, y+1);
-		case 4: //left bottom
-			return std::make_pair((y%2) ? x-1 : x, y+1);
-		case 5: //left
-			return std::make_pair(x-1, y);
-		default:
-			throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
-		}
-	}
-
-	static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
-	{
-		return gotoDir(xy.first, xy.second, direction);
-	}
-
-	static bool isGoodHex(std::pair<int, int> xy)
-	{
-		return xy.first >=0 && xy.first < 17 && xy.second >= 0 && xy.second < 11;
-	}
-
-	//helper function for std::set<ui16> CSpell::rangeInHexes(unsigned int centralHex, ui8 schoolLvl ) const
-	static std::set<ui16> getInRange(unsigned int center, int low, int high)
-	{
-		std::set<ui16> ret;
-		if(low == 0)
+			ESpellSchool::AIR,
+			Bonus::AIR_SPELL_DMG_PREMY,
+			Bonus::AIR_IMMUNITY,
+			"air",
+			SecondarySkill::AIR_MAGIC,
+			Bonus::AIR_SPELLS
+		},
 		{
-			ret.insert(center);
-		}
-
-		std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
-		for(auto & elem : mainPointForLayer)
-			elem = hexToPair(center);
-
-		for(int it=1; it<=high; ++it) //it - distance to the center
+			ESpellSchool::FIRE,
+			Bonus::FIRE_SPELL_DMG_PREMY,
+			Bonus::FIRE_IMMUNITY,
+			"fire",
+			SecondarySkill::FIRE_MAGIC,
+			Bonus::FIRE_SPELLS
+		},
 		{
-			for(int b=0; b<6; ++b)
-				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
-
-			if(it>=low)
-			{
-				std::pair<int, int> curHex;
-
-				//adding lines (A-b, B-c, C-d, etc)
-				for(int v=0; v<6; ++v)
-				{
-					curHex = mainPointForLayer[v];
-					for(int h=0; h<it; ++h)
-					{
-						if(isGoodHex(curHex))
-							ret.insert(XYToHex(curHex));
-						curHex = gotoDir(curHex, (v+2)%6);
-					}
-				}
-
-			} //if(it>=low)
+			ESpellSchool::WATER,
+			Bonus::WATER_SPELL_DMG_PREMY,
+			Bonus::WATER_IMMUNITY,
+			"water",
+			SecondarySkill::WATER_MAGIC,
+			Bonus::WATER_SPELLS
+		},
+		{
+			ESpellSchool::EARTH,
+			Bonus::EARTH_SPELL_DMG_PREMY,
+			Bonus::EARTH_IMMUNITY,
+			"earth",
+			SecondarySkill::EARTH_MAGIC,
+			Bonus::EARTH_SPELLS
 		}
+	};	
+}
 
-		return ret;
-	}
+BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
+	: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),caster(nullptr), secHero(nullptr),
+	usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
+{
+	
 }
 
+
+///CSpell::LevelInfo
 CSpell::LevelInfo::LevelInfo()
-	:description(""),cost(0),power(0),AIValue(0),smartTarget(true),range("0")
+	:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
 {
 
 }
@@ -139,22 +89,68 @@ CSpell::LevelInfo::~LevelInfo()
 
 }
 
-
+///CSpell
 CSpell::CSpell():
 	id(SpellID::NONE), level(0),
 	earth(false), water(false), fire(false), air(false),
 	combatSpell(false), creatureAbility(false),
 	positiveness(ESpellPositiveness::NEUTRAL),
-	mainEffectAnim(-1),
 	defaultProbability(0),
 	isRising(false), isDamage(false), isOffensive(false),
-	targetType(ETargetType::NO_TARGET)
+	targetType(ETargetType::NO_TARGET),
+	mechanics(nullptr)
 {
 	levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
 }
 
 CSpell::~CSpell()
 {
+	delete mechanics;
+}
+
+void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	mechanics->afterCast(battle, packet);
+}
+
+
+void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
+{
+	assert(env);
+	
+	mechanics->battleCast(env, parameters);
+}
+
+bool CSpell::isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const
+{
+	if(!hasSpellBook)
+		return false;
+	
+	const bool inSpellBook = vstd::contains(spellBook, id);
+	const bool isBonus = caster->hasBonusOfType(Bonus::SPELL, id);
+	
+	bool inTome = false;
+	
+	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+	{
+		if(caster->hasBonusOfType(cnf.knoledgeBonus))
+		{
+			inTome = stop = true;
+		}				
+	});	
+
+    if (isSpecialSpell())
+    {
+        if (inSpellBook)
+        {//hero has this spell in spellbook
+            logGlobal->errorStream() << "Special spell in spellbook "<<name;
+        }
+        return isBonus;
+    }
+    else
+    {
+       return inSpellBook || inTome || isBonus || caster->hasBonusOfType(Bonus::SPELLS_OF_LEVEL, level);
+    }	
 }
 
 const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
@@ -168,128 +164,145 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
 	return levels.at(level);
 }
 
+ui32 CSpell::calculateBonus(ui32 baseDamage, const CGHeroInstance* caster, const CStack* affectedCreature) const
+{
+	ui32 ret = baseDamage;
+	//applying sorcery secondary skill
+	if(caster)
+	{
+		ret *= (100.0 + caster->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::SORCERY)) / 100.0;
+		ret *= (100.0 + caster->valOfBonuses(Bonus::SPELL_DAMAGE) + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id.toEnum())) / 100.0;
+		
+		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+		{
+			ret *= (100.0 + caster->valOfBonuses(cnf.damagePremyBonus)) / 100.0;
+			stop = true; //only bonus from one school is used
+		});		
 
-std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
+		if (affectedCreature && affectedCreature->getCreature()->level) //Hero specials like Solmyr, Deemer
+			ret *= (100. + ((caster->valOfBonuses(Bonus::SPECIAL_SPELL_LEV, id.toEnum()) * caster->level) / affectedCreature->getCreature()->level)) / 100.0;
+	}
+	return ret;	
+}
+
+ui32 CSpell::calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
 {
-	using namespace SRSLPraserHelpers;
+	ui32 ret = 0; //value to return
+
+	//check if spell really does damage - if not, return 0
+	if(!isDamageSpell())
+		return 0;
 
-	std::vector<BattleHex> ret;
+	ret = usedSpellPower * power;
+	ret += getPower(spellSchoolLevel);
 
-	if(id == SpellID::FIRE_WALL  ||  id == SpellID::FORCE_FIELD)
+	//affected creature-specific part
+	if(nullptr != affectedCreature)
 	{
-		//Special case - shape of obstacle depends on caster's side
-		//TODO make it possible through spell_info config
+		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
+		
+		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+		{
+			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
+			{
+				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
+				ret /= 100;
+				stop = true;//only bonus from one school is used	
+			}				
+		});
 
-		BattleHex::EDir firstStep, secondStep;
-		if(side)
+		//general spell dmg reduction
+		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
 		{
-			firstStep = BattleHex::TOP_LEFT;
-			secondStep = BattleHex::TOP_RIGHT;
+			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
+			ret /= 100;
 		}
-		else
+		//dmg increasing
+		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
 		{
-			firstStep = BattleHex::TOP_RIGHT;
-			secondStep = BattleHex::TOP_LEFT;
+			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
+			ret /= 100;
 		}
+	}
+	ret = calculateBonus(ret, caster, affectedCreature);
+	return ret;	
+}
 
-		//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
-		auto addIfValid = [&](BattleHex hex)
-		{
-			if(hex.isValid())
-				ret.push_back(hex);
-			else if(outDroppedHexes)
-				*outDroppedHexes = true;
-		};
 
-		ret.push_back(centralHex);
-		addIfValid(centralHex.moveInDir(firstStep, false));
-		if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
-			addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
+ui32 CSpell::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
+{
+//todo: use Mechanics class
+	int healedHealth;
+	
+	if(!isHealingSpell())
+	{
+		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< name;
+		return 0;
+	}		
+	
+	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
+	const int levelPower = getPower(caster->getSpellSchoolLevel(this));
+	
+	if (id == SpellID::SACRIFICE && sacrificedStack)
+		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
+	else
+		healedHealth = spellPowerSkill * power + levelPower; //???
+	healedHealth = calculateBonus(healedHealth, caster, stack);
+	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));	
+}
 
-		return ret;
-	}
 
+std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
+{
+	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
+}
 
-	std::string rng = getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
+std::set<const CStack* > CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
+{
+	ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
 
-	if(rng.size() >= 1 && rng[0] != 'X') //there is at lest one hex in range
+	std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
+	
+	//now handle immunities		
+	auto predicate = [&, this](const CStack * s)->bool
 	{
-		std::string number1, number2;
-		int beg, end;
-		bool readingFirst = true;
-		for(auto & elem : rng)
-		{
-			if(std::isdigit(elem) ) //reading number
-			{
-				if(readingFirst)
-					number1 += elem;
-				else
-					number2 += elem;
-			}
-			else if(elem == ',') //comma
-			{
-				//calculating variables
-				if(readingFirst)
-				{
-					beg = atoi(number1.c_str());
-					number1 = "";
-				}
-				else
-				{
-					end = atoi(number2.c_str());
-					number2 = "";
-				}
-				//obtaining new hexes
-				std::set<ui16> curLayer;
-				if(readingFirst)
-				{
-					curLayer = getInRange(centralHex, beg, beg);
-				}
-				else
-				{
-					curLayer = getInRange(centralHex, beg, end);
-					readingFirst = true;
-				}
-				//adding abtained hexes
-				for(auto & curLayer_it : curLayer)
-				{
-					ret.push_back(curLayer_it);
-				}
-
-			}
-			else if(elem == '-') //dash
-			{
-				beg = atoi(number1.c_str());
-				number1 = "";
-				readingFirst = false;
-			}
-		}
-	}
-
-	//remove duplicates (TODO check if actually needed)
-	range::unique(ret);
-	return ret;
+		bool hitDirectly = ctx.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;
-
-	auto & levelInfo = getLevelInfo(level);
-
-	info.type = getTargetType();
-	info.smart = levelInfo.smartTarget;
-	info.massive = levelInfo.range == "X";
-	info.onlyAlive = !isRisingSpell();
-
+	TargetInfo info(this, level);
 	return info;
 }
 
+void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
+{
+	bool stop = false;
+	for(const SpellSchoolInfo & cnf : SpellConfig::SCHOOL)
+	{
+		if(school.at(cnf.id))
+		{
+			cb(cnf, stop);
+			
+			if(stop)
+				break;
+		}				
+	}	
+}
+
 
 bool CSpell::isCombatSpell() const
 {
@@ -321,6 +334,10 @@ bool CSpell::isNeutral() const
 	return positiveness == NEUTRAL;
 }
 
+bool CSpell::isHealingSpell() const
+{
+	return isRisingSpell() || (id == SpellID::CURE);
+}
 
 bool CSpell::isRisingSpell() const
 {
@@ -408,33 +425,89 @@ void CSpell::getEffects(std::vector<Bonus>& lst, const int level) const
 	}
 }
 
-bool CSpell::isImmuneBy(const IBonusBearer* obj) const
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 {
+	// Get all stacks at destination hex. only alive if not rising spell
+	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
+		return s->coversPos(destination) && (isRisingSpell() || s->alive());
+	});
+	
+	if(!stacks.empty())
+	{
+		bool allImmune = true;
+		
+		ESpellCastProblem::ESpellCastProblem problem;		
+		
+		for(auto s : stacks)
+		{
+			ESpellCastProblem::ESpellCastProblem res = isImmuneByStack(caster,s);
+			
+			if(res == ESpellCastProblem::OK)
+			{
+				allImmune = false;
+			}
+			else
+			{
+				problem = res;
+			}
+		}
+		
+		if(allImmune)
+			return problem;
+	}
+	else //no target stack on this tile
+	{
+		if(getTargetType() == CSpell::CREATURE)
+		{
+			if(caster && mode == ECastingMode::HERO_CASTING) //TODO why???
+			{
+				const CSpell::TargetInfo ti(this, caster->getSpellSchoolLevel(this), mode);
+				
+				if(!ti.massive)
+					return ESpellCastProblem::WRONG_SPELL_TARGET;					
+			}
+			else
+			{
+ 				return ESpellCastProblem::WRONG_SPELL_TARGET;
+			}			
+		}
+	}
+
+	return ESpellCastProblem::OK;	
+}
+
+
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
+{	
 	//todo: use new bonus API
 	//1. Check absolute limiters
 	for(auto b : absoluteLimiters)
 	{
 		if (!obj->hasBonusOfType(b))
-			return true;
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 
 	//2. Check absolute immunities
 	for(auto b : absoluteImmunities)
 	{
 		if (obj->hasBonusOfType(b))
-			return true;
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
+	
+	//check receptivity
+	if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
+		return ESpellCastProblem::OK;	
 
 	//3. Check negation
 	//FIXME: Orb of vulnerability mechanics is not such trivial
 	if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
-		return false;
+		return ESpellCastProblem::NOT_DECIDED;
 		
 	//4. Check negatable limit
 	for(auto b : limiters)
 	{
 		if (!obj->hasBonusOfType(b))
-			return true;
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 
 
@@ -442,55 +515,56 @@ bool CSpell::isImmuneBy(const IBonusBearer* obj) const
 	for(auto b : immunities)
 	{
 		if (obj->hasBonusOfType(b))
-			return true;
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 
-	auto battleTestElementalImmunity = [&,this](Bonus::BonusType element) -> bool
+	//6. Check elemental immunities
+	
+	ESpellCastProblem::ESpellCastProblem tmp = ESpellCastProblem::NOT_DECIDED;
+	
+	forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
 	{
+		auto element = cnf.immunityBonus;
+		
 		if(obj->hasBonusOfType(element, 0)) //always resist if immune to all spells altogether
-				return true;
+		{
+			tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+			stop = true;
+		}				
 		else if(!isPositive()) //negative or indifferent
 		{
 			if((isDamageSpell() && obj->hasBonusOfType(element, 2)) || obj->hasBonusOfType(element, 1))
-				return true;
-		}
-		return false;
-	};
-
-	//6. Check elemental immunities
-	if(fire)
-	{
-		if(battleTestElementalImmunity(Bonus::FIRE_IMMUNITY))
-			return true;
-	}
-	if(water)
-	{
-		if(battleTestElementalImmunity(Bonus::WATER_IMMUNITY))
-			return true;
-	}
-
-	if(earth)
-	{
-		if(battleTestElementalImmunity(Bonus::EARTH_IMMUNITY))
-			return true;
-	}
-	if(air)
-	{
-		if(battleTestElementalImmunity(Bonus::AIR_IMMUNITY))
-			return true;
-	}
-
+			{
+				tmp = ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+				stop = true;
+			}			
+		}	
+	});
+	
+	if(tmp != ESpellCastProblem::NOT_DECIDED)
+		return tmp;
+	
 	TBonusListPtr levelImmunities = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY));
 
 	if(obj->hasBonusOfType(Bonus::SPELL_IMMUNITY, id)
 		|| ( levelImmunities->size() > 0  &&  levelImmunities->totalValue() >= level  &&  level))
 	{
-		return true;
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 
-	return false;
+	return ESpellCastProblem::NOT_DECIDED;
+}
+
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const
+{
+	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
+	
+	if (ESpellCastProblem::NOT_DECIDED != immuneResult) 
+		return immuneResult;
+	return ESpellCastProblem::OK;	
 }
 
+
 void CSpell::setIsOffensive(const bool val)
 {
 	isOffensive = val;
@@ -512,6 +586,89 @@ void CSpell::setIsRising(const bool val)
 	}
 }
 
+void CSpell::setup()
+{
+	setupMechanics();
+	
+	air = school[ESpellSchool::AIR];
+	fire = school[ESpellSchool::FIRE];
+	water = school[ESpellSchool::WATER];
+	earth = school[ESpellSchool::EARTH];	
+}
+
+
+void CSpell::setupMechanics()
+{
+	if(nullptr != mechanics)
+	{
+		logGlobal->errorStream() << "Spell " << this->name << " mechanics already set";
+		delete mechanics;
+	}
+	
+	mechanics = ISpellMechanics::createMechanics(this);	
+}
+
+///CSpell::AnimationInfo
+CSpell::AnimationInfo::AnimationInfo()
+{
+	
+}
+
+CSpell::AnimationInfo::~AnimationInfo()
+{
+	
+}
+
+std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
+{	
+	std::string res;	
+	double maximum = 0.0;
+	
+	for(const auto & info : projectile)
+	{
+		if(info.minimumAngle < angle && info.minimumAngle > maximum)
+		{
+			maximum = info.minimumAngle;
+			res = info.resourceName;
+		}
+	}
+	
+	return std::move(res);	
+}
+
+
+///CSpell::TargetInfo
+CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level)
+{
+	init(spell, level);
+}
+
+CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode)
+{
+	init(spell, level);
+	if(mode == ECastingMode::ENCHANTER_CASTING)
+	{
+		smart = true; //FIXME: not sure about that, this makes all spells smart in this mode
+		massive = true;
+	}
+	else if(mode == ECastingMode::SPELL_LIKE_ATTACK)
+	{
+		alwaysHitDirectly = true;
+	}	
+}
+
+void CSpell::TargetInfo::init(const CSpell * spell, const int level)
+{
+	auto & levelInfo = spell->getLevelInfo(level);
+
+	type = spell->getTargetType();
+	smart = levelInfo.smartTarget;
+	massive = levelInfo.range == "X";
+	onlyAlive = !spell->isRisingSpell();
+	alwaysHitDirectly = false;
+	clearAffected = levelInfo.clearAffected;
+	clearTarget = levelInfo.clearTarget;
+}
 
 
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
@@ -523,6 +680,7 @@ bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos)
 		return false;
 }
 
+///CSpellHandler
 CSpellHandler::CSpellHandler()
 {
 
@@ -590,17 +748,7 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
 			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS ; i++)
 				descriptions.push_back(parser.readString());
 
-			std::string attributes = parser.readString();
-
-			std::string targetType = "NO_TARGET";
-
-			if(attributes.find("CREATURE_TARGET_1") != std::string::npos
-				|| attributes.find("CREATURE_TARGET_2") != std::string::npos)
-				targetType = "CREATURE_EXPERT_MASSIVE";
-			else if(attributes.find("CREATURE_TARGET") != std::string::npos)
-				targetType = "CREATURE";
-			else if(attributes.find("OBSTACLE_TARGET") != std::string::npos)
-				targetType = "OBSTACLE";
+			parser.readString(); //ignore attributes. All data present in JSON
 
 			//save parsed level specific data
 			for(size_t i = 0; i < GameConstants::SPELL_SCHOOL_LEVELS; i++)
@@ -612,16 +760,6 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData(size_t dataSize)
 				level["aiValue"].Float() = AIVals[i];
 			}
 
-			if(targetType == "CREATURE_EXPERT_MASSIVE")
-			{
-				lineNode["targetType"].String() = "CREATURE";
-				getLevel(3)["range"].String() = "X";
-			}
-			else
-			{
-				lineNode["targetType"].String() = targetType;
-			}
-
 			legacyData.push_back(lineNode);
 
 
@@ -682,11 +820,11 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
 	logGlobal->traceStream() << __FUNCTION__ << ": loading spell " << spell->name;
 
 	const auto schoolNames = json["school"];
-
-	spell->air   = schoolNames["air"].Bool();
-	spell->earth = schoolNames["earth"].Bool();
-	spell->fire  = schoolNames["fire"].Bool();
-	spell->water = schoolNames["water"].Bool();
+	
+	for(const SpellSchoolInfo & info : SpellConfig::SCHOOL)
+	{
+		spell->school[info.id] = schoolNames[info.jsonName].Bool();
+	}
 
 	spell->level = json["level"].Float();
 	spell->power = json["power"].Float();
@@ -711,18 +849,16 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
 		spell->targetType = CSpell::CREATURE;
 	else if(targetType == "OBSTACLE")
 		spell->targetType = CSpell::OBSTACLE;
-
-
-	spell->mainEffectAnim = json["anim"].Float();
+	else if(targetType == "LOCATION")
+		spell->targetType = CSpell::LOCATION;
+	else 
+		logGlobal->warnStream() << "Spell " << spell->name << ". Target type " << (targetType.empty() ? "empty" : "unknown ("+targetType+")") << ". Assumed NO_TARGET.";
 
 	for(const auto & counteredSpell: json["counters"].Struct())
 		if (counteredSpell.second.Bool())
 		{
-			JsonNode tmp(JsonNode::DATA_STRING);
-			tmp.meta = json.meta;
-			tmp.String() = counteredSpell.first;
-
-			VLC->modh->identifiers.requestIdentifier(tmp,[=](si32 id){
+			VLC->modh->identifiers.requestIdentifier(json.meta, counteredSpell.first, [=](si32 id)
+			{
 				spell->counteredSpells.push_back(SpellID(id));
 			});
 		}
@@ -805,10 +941,46 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
 	spell->iconScenarioBonus = graphicsNode["iconScenarioBonus"].String();
 	spell->iconScroll = graphicsNode["iconScroll"].String();
 
-
+	const JsonNode & animationNode = json["animation"];
+	
+	auto loadAnimationQueue = [&](const std::string & jsonName, CSpell::TAnimationQueue & q)	
+	{
+		auto queueNode = animationNode[jsonName].Vector();		
+		for(const JsonNode & item : queueNode)
+		{	
+			CSpell::TAnimation newItem;
+			newItem.verticalPosition = VerticalPosition::TOP;
+			
+			if(item.getType() == JsonNode::DATA_STRING)
+				newItem.resourceName = item.String();
+			else if(item.getType() == JsonNode::DATA_STRUCT)
+			{
+				newItem.resourceName = item["defName"].String();
+				
+				auto vPosStr = item["verticalPosition"].String();
+				if("bottom" == vPosStr)
+					newItem.verticalPosition = VerticalPosition::BOTTOM;
+			}	
+			q.push_back(newItem);		
+		}
+	};
+	
+	loadAnimationQueue("affect", spell->animationInfo.affect);
+	loadAnimationQueue("cast", spell->animationInfo.cast);
+	loadAnimationQueue("hit", spell->animationInfo.hit);	
+	
+	const JsonVector & projectile = animationNode["projectile"].Vector();
+	
+	for(const JsonNode & item : projectile)
+	{
+		CSpell::ProjectileInfo info;
+		info.resourceName = item["defName"].String();
+		info.minimumAngle = item["minimumAngle"].Float();
+		
+		spell->animationInfo.projectile.push_back(info);
+	}
 
 	const JsonNode & soundsNode = json["sounds"];
-
 	spell->castSound = soundsNode["cast"].String();
 
 
@@ -822,14 +994,15 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode& json)
 		
 		CSpell::LevelInfo & levelObject = spell->levels[levelIndex];
 
-		const si32 levelPower   = levelNode["power"].Float();		
-		
-		levelObject.description = levelNode["description"].String();
-		levelObject.cost        = levelNode["cost"].Float();
-		levelObject.power       = levelPower;
-		levelObject.AIValue     = levelNode["aiValue"].Float();
-		levelObject.smartTarget = levelNode["targetModifier"]["smart"].Bool();
-		levelObject.range       = levelNode["range"].String();
+		const si32 levelPower     = levelObject.power = levelNode["power"].Float();		
+
+		levelObject.description   = levelNode["description"].String();
+		levelObject.cost          = levelNode["cost"].Float();
+		levelObject.AIValue       = levelNode["aiValue"].Float();
+		levelObject.smartTarget   = levelNode["targetModifier"]["smart"].Bool();
+		levelObject.clearTarget   = levelNode["targetModifier"]["clearTarget"].Bool();
+		levelObject.clearAffected = levelNode["targetModifier"]["clearAffected"].Bool();			
+		levelObject.range         = levelNode["range"].String();
 		
 		for(const auto & elem : levelNode["effects"].Struct())
 		{
@@ -857,9 +1030,12 @@ void CSpellHandler::afterLoadFinalization()
 {
 	//FIXME: it is a bad place for this code, should refactor loadFromJson to know object id during loading
 	for(auto spell: objects)
+	{
 		for(auto & level: spell->levels)
 			for(auto & bonus: level.effects)
 				bonus.sid = spell->id;
+		spell->setup();
+	}
 }
 
 void CSpellHandler::beforeValidate(JsonNode & object)

+ 200 - 24
lib/CSpellHandler.h

@@ -4,6 +4,7 @@
 #include "../lib/ConstTransitivePtr.h"
 #include "int3.h"
 #include "GameConstants.h"
+#include "BattleHex.h"
 #include "HeroBonus.h"
 
 
@@ -17,20 +18,132 @@
  *
  */
 
+class CSpell;
+class ISpellMechanics;
+
 class CLegacyConfigParser;
-struct BattleHex;
+
+class CGHeroInstance;
+class CStack;
+
+class CBattleInfoCallback;
+class BattleInfo;
+
+struct CPackForClient;
+struct BattleSpellCast;
+
+class CRandomGenerator;
+
+struct SpellSchoolInfo
+{
+	ESpellSchool id; //backlink
+	Bonus::BonusType damagePremyBonus;
+	Bonus::BonusType immunityBonus;	
+	std::string jsonName;
+	SecondarySkill::ESecondarySkill skill;
+	Bonus::BonusType knoledgeBonus;			
+};
+
+///callback to be provided by server
+class DLL_LINKAGE SpellCastEnvironment
+{
+public:
+	virtual ~SpellCastEnvironment(){};
+	virtual void sendAndApply(CPackForClient * info) const = 0;
+	
+	virtual CRandomGenerator & getRandomGenerator() const = 0;
+	virtual void complain(const std::string & problem) const = 0;
+};
+
+///helper struct
+struct DLL_LINKAGE BattleSpellCastParameters
+{
+public:
+	BattleSpellCastParameters(const BattleInfo * cb);
+	int spellLvl;
+	BattleHex destination;
+	ui8 casterSide;
+	PlayerColor casterColor;
+	const CGHeroInstance * caster;
+	const CGHeroInstance * secHero;
+	int usedSpellPower;
+	ECastingMode::ECastingMode mode;
+	const CStack * casterStack;
+	const CStack * selectedStack;	
+	const BattleInfo * cb;		
+};
+
+enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
 
 class DLL_LINKAGE CSpell
 {
+public:
+	
+	struct ProjectileInfo
+	{
+		///in radians. Only positive value. Negative angle is handled by vertical flip
+		double minimumAngle; 
+		
+		///resource name
+		std::string resourceName;
+		 
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & minimumAngle & resourceName; 
+		}		
+	};
+	
+	struct AnimationItem
+	{
+		std::string resourceName;
+		VerticalPosition verticalPosition;
+		
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & resourceName & verticalPosition; 
+		}		
+	};
+	
+	typedef AnimationItem TAnimation;
+	typedef std::vector<TAnimation> TAnimationQueue; 
+	
+	struct DLL_LINKAGE AnimationInfo
+	{
+		AnimationInfo();
+		~AnimationInfo();
+
+		///displayed on all affected targets. 
+		TAnimationQueue affect;
+
+		///displayed on caster.
+		TAnimationQueue cast;
+
+		///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
+		TAnimationQueue hit;
+
+		///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
+		///use selectProjectile to access
+		std::vector<ProjectileInfo> projectile;
+
+		template <typename Handler> void serialize(Handler &h, const int version)
+		{
+			h & projectile & hit & cast;
+		}
+
+		std::string selectProjectile(const double angle) const;
+	} animationInfo;
+	
 public:
 	struct LevelInfo
 	{
 		std::string description; //descriptions of spell for skill level
-		si32 cost; //per skill level: 0 - none, 1 - basic, etc
-		si32 power; //per skill level: 0 - none, 1 - basic, etc
-		si32 AIValue; //AI values: per skill level: 0 - none, 1 - basic, etc
+		si32 cost;
+		si32 power;
+		si32 AIValue;
 
 		bool smartTarget;
+		bool clearTarget;
+		bool clearAffected;
 		std::string range;
 
 		std::vector<Bonus> effects;
@@ -41,6 +154,7 @@ public:
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & description & cost & power & AIValue & smartTarget & range & effects;
+			h & clearTarget & clearAffected;
 		}
 	};
 
@@ -52,7 +166,7 @@ public:
 	 */
 	const CSpell::LevelInfo& getLevelInfo(const int level) const;
 public:
-	enum ETargetType {NO_TARGET, CREATURE, OBSTACLE};
+	enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
 	enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
 
 	struct TargetInfo
@@ -61,6 +175,17 @@ public:
 		bool smart;
 		bool massive;
 		bool onlyAlive;
+		///no immunity on primary target (mostly spell-like attack)
+		bool alwaysHitDirectly;
+		
+		bool clearTarget;
+		bool clearAffected;
+		
+		TargetInfo(const CSpell * spell, const int level);
+		TargetInfo(const CSpell * spell, const int level, ECastingMode::ECastingMode mode);
+		
+	private:
+		void init(const CSpell * spell, const int level);
 	};
 
 	SpellID id;
@@ -68,10 +193,13 @@ public:
 	std::string name;
 
 	si32 level;
-	bool earth;
-	bool water;
-	bool fire;
-	bool air;
+	bool earth; //deprecated
+	bool water; //deprecated
+	bool fire;  //deprecated
+	bool air;   //deprecated
+	
+	std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
+	
 	si32 power; //spell's power
 
 	std::map<TFaction, si32> probabilities; //% chance to gain for castles
@@ -84,12 +212,15 @@ public:
 
 	CSpell();
 	~CSpell();
+	
+	bool isCastableBy(const IBonusBearer * caster, bool hasSpellBook, const std::set<SpellID> & spellBook) const;
+	
 
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
-	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;
+
 
 	bool isCombatSpell() const;
 	bool isAdventureSpell() const;
@@ -99,16 +230,36 @@ public:
 	bool isNegative() const;
 	bool isNeutral() const;
 
-	bool isRisingSpell() const;
 	bool isDamageSpell() const;
+	bool isHealingSpell() const;
+	bool isRisingSpell() const;	
 	bool isOffensiveSpell() const;
 
 	bool isSpecialSpell() const;
 
 	bool hasEffects() const;
 	void getEffects(std::vector<Bonus> &lst, const int level) const;
-
-	bool isImmuneBy(const IBonusBearer *obj) const;
+	
+	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
+	
+	//internal, for use only by Mechanics classes
+	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
+	
+	//checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	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;
+	
+	///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
+	ui32 calculateDamage(const CGHeroInstance * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
+	
+	///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;
 
@@ -125,6 +276,13 @@ public:
 
 	si32 getProbability(const TFaction factionId) const;
 
+	/**
+	 * Calls cb for each school this spell belongs to
+	 *
+	 * Set stop to true to abort looping
+	 */	
+	void forEachSchool(const std::function<void (const SpellSchoolInfo &, bool &)> & cb) const;
+
 	/**
 	 * Returns resource name of icon for SPELL_IMMUNITY bonus
 	 */
@@ -134,28 +292,45 @@ public:
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & identifier & id & name & level & earth & water & fire & air & power
-		  & probabilities  & attributes & combatSpell & creatureAbility & positiveness & counteredSpells & mainEffectAnim;
+		h & identifier & id & name & level & power
+		  & probabilities  & attributes & combatSpell & creatureAbility & positiveness & counteredSpells;
 		h & isRising & isDamage & isOffensive;
 		h & targetType;
 		h & immunities & limiters & absoluteImmunities & absoluteLimiters;
 		h & iconImmune;
 		h & defaultProbability;
-
 		h & isSpecial;
-
 		h & castSound & iconBook & iconEffect & iconScenarioBonus & iconScroll;
+		h & levels;		
+		h & school;		
+		h & animationInfo;
 
-		h & levels;
-
+		if(!h.saving)
+			setup();
 	}
 	friend class CSpellHandler;
 	friend class Graphics;
-
+public:
+	///Server logic. Has write access to GameState via packets.
+	///May be executed on client side by (future) non-cheat-proof scripts.
+	
+	//void adventureCast() const; 
+	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const; 	
+		
+public:	
+	///Client-server logic. Has direct write access to GameState.
+	///Shall be called (only) when applying packets on BOTH SIDES
+	
+	///implementation of BattleSpellCast applying
+	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const;
+		
 private:
 	void setIsOffensive(const bool val);
 	void setIsRising(const bool val);
-
+	
+	//call this after load or deserialization. cant be done in constructor.
+	void setup();
+	void setupMechanics();
 private:
 	si32 defaultProbability;
 
@@ -186,10 +361,10 @@ private:
 	std::string castSound;
 
 	std::vector<LevelInfo> levels;
+	
+	ISpellMechanics * mechanics;//(!) do not serialize
 };
 
-
-
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door
 
 class DLL_LINKAGE CSpellHandler: public CHandlerBase<SpellID, CSpell>
@@ -216,6 +391,7 @@ public:
 	{
 		h & objects ;
 	}
+		
 protected:
 	CSpell * loadFromJson(const JsonNode & json) override;
 };

+ 5 - 0
lib/GameConstants.cpp

@@ -87,6 +87,11 @@ CCreature * CreatureID::toCreature() const
 
 CSpell * SpellID::toSpell() const
 {
+	if(num < 0 || num >= VLC->spellh->objects.size())
+	{	
+		logGlobal->errorStream() << "Unable to get spell of invalid ID " << int(num);
+		return nullptr;
+	}		
 	return VLC->spellh->objects[*this];
 }
 

+ 15 - 2
lib/GameConstants.h

@@ -395,14 +395,19 @@ namespace ESpellCastProblem
 		SECOND_HEROS_SPELL_IMMUNITY, SPELL_LEVEL_LIMIT_EXCEEDED, NO_SPELLS_TO_DISPEL,
 		NO_APPROPRIATE_TARGET, STACK_IMMUNE_TO_SPELL, WRONG_SPELL_TARGET, ONGOING_TACTIC_PHASE,
 		MAGIC_IS_BLOCKED, //For Orb of Inhibition and similar - no casting at all
+		NOT_DECIDED,
 		INVALID
 	};
 }
 
 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
@@ -890,6 +895,14 @@ public:
 	ESpellID num;
 };
 
+enum class ESpellSchool: ui8
+{
+	AIR 	= 0,
+	FIRE 	= 1,
+	WATER 	= 2,
+	EARTH 	= 3
+};
+
 ID_LIKE_OPERATORS_DECLS(SpellID, SpellID::ESpellID)
 
 // Typedef declarations

+ 30 - 13
lib/NetPacks.h

@@ -274,17 +274,18 @@ struct ChangeSpells : public CPackForClient //109
 
 struct SetMana : public CPackForClient //110
 {
-	SetMana(){type = 110;};
+	SetMana(){type = 110;absolute=true;};
 	void applyCl(CClient *cl);
 	DLL_LINKAGE void applyGs(CGameState *gs);
 
 
 	ObjectInstanceID hid;
 	si32 val;
+	bool absolute;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & val & hid;
+		h & val & hid & absolute;
 	}
 };
 
@@ -1319,16 +1320,18 @@ struct StacksHealedOrResurrected : public CPackForClient //3013
 
 struct BattleStackAttacked : public CPackForClient//3005
 {
-	BattleStackAttacked(){flags = 0; type = 3005;};
+	BattleStackAttacked():
+		flags(0), spellID(SpellID::NONE){type=3005;};
 	void applyFirstCl(CClient * cl);
 	//void applyCl(CClient *cl);
 	DLL_LINKAGE void applyGs(CGameState *gs);
 
 	ui32 stackAttacked, attackerID;
 	ui32 newAmount, newHP, killedAmount, damageAmount;
-	enum EFlags {KILLED = 1, EFFECT = 2, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16};
-	ui8 flags; //uses EFlags (above)
+	enum EFlags {KILLED = 1, EFFECT = 2/*deprecated */, SECONDARY = 4, REBIRTH = 8, CLONE_KILLED = 16, SPELL_EFFECT = 32 /*, BONUS_EFFECT = 64 */};
+	ui32 flags; //uses EFlags (above)
 	ui32 effect; //set only if flag EFFECT is set
+	SpellID spellID; //only if flag SPELL_EFFECT is set
 	std::vector<StacksHealedOrResurrected> healedStacks; //used when life drain
 
 
@@ -1348,6 +1351,11 @@ struct BattleStackAttacked : public CPackForClient//3005
 	{
 		return flags & SECONDARY;
 	}
+	///Attacked with spell (SPELL_LIKE_ATTACK)
+	bool isSpell() const
+	{
+		return flags & SPELL_EFFECT;
+	}
 	bool willRebirth() const//resurrection, e.g. Phoenix
 	{
 		return flags & REBIRTH;
@@ -1360,6 +1368,7 @@ struct BattleStackAttacked : public CPackForClient//3005
 	{
 		h & stackAttacked & attackerID & newAmount & newHP & flags & killedAmount & damageAmount & effect
 			& healedStacks;
+		h & spellID;
 	}
 	bool operator<(const BattleStackAttacked &b) const
 	{
@@ -1369,15 +1378,17 @@ struct BattleStackAttacked : public CPackForClient//3005
 
 struct BattleAttack : public CPackForClient//3006
 {
-	BattleAttack(){flags = 0; type = 3006;};
+	BattleAttack(): flags(0), spellID(SpellID::NONE){type = 3006;};
 	void applyFirstCl(CClient *cl);
 	DLL_LINKAGE void applyGs(CGameState *gs);
 	void applyCl(CClient *cl);
 
 	std::vector<BattleStackAttacked> bsa;
 	ui32 stackAttacking;
-	ui8 flags; //uses Eflags (below)
-	enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32};
+	ui32 flags; //uses Eflags (below)
+	enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64};
+	
+	SpellID spellID; //for SPELL_LIKE 
 
 	bool shot() const//distance attack - decrease number of shots
 	{
@@ -1403,13 +1414,17 @@ struct BattleAttack : public CPackForClient//3006
 	{
 		return flags & DEATH_BLOW;
 	}
+	bool spellLike() const
+	{
+		return flags & SPELL_LIKE;
+	}
 	//bool killed() //if target stack was killed
 	//{
 	//	return bsa.killed();
 	//}
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & bsa & stackAttacking & flags;
+		h & bsa & stackAttacking & flags & spellID;
 	}
 };
 
@@ -1439,7 +1454,7 @@ struct EndAction : public CPackForClient//3008
 
 struct BattleSpellCast : public CPackForClient//3009
 {
-	BattleSpellCast(){type = 3009;};
+	BattleSpellCast(){type = 3009; casterStack = -1;};
 	DLL_LINKAGE void applyGs(CGameState *gs);
 	void applyCl(CClient *cl);
 
@@ -1447,16 +1462,15 @@ struct BattleSpellCast : public CPackForClient//3009
 	ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
 	ui32 id; //id of spell
 	ui8 skill; //caster's skill level
-	ui8 spellCost;
 	ui8 manaGained; //mana channeling ability
 	BattleHex tile; //destination tile (may not be set in some global/mass spells
 	std::vector<ui32> resisted; //ids of creatures that resisted this spell
 	std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
-	CreatureID attackerType;//id of caster to generate console message; -1 if not set (eg. spell casted by artifact)
+	si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
 	bool castedByHero; //if true - spell has been casted by hero, otherwise by a creature
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & dmgToDisplay & side & id & skill & spellCost & manaGained & tile & resisted & affectedCres & attackerType & castedByHero;
+		h & dmgToDisplay & side & id & skill & manaGained & tile & resisted & affectedCres & casterStack & castedByHero;
 	}
 };
 
@@ -1578,6 +1592,9 @@ struct BattleStackAdded : public CPackForClient //3017
 	int amount;
 	int pos;
 	int summoned; //if true, remove it afterwards
+	
+	///Actual stack ID, set on apply, do not serialize
+	int newStackID; 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 16 - 40
lib/NetPacksLib.cpp

@@ -184,9 +184,16 @@ DLL_LINKAGE void ChangeSpells::applyGs( CGameState *gs )
 
 DLL_LINKAGE void SetMana::applyGs( CGameState *gs )
 {
-	CGHeroInstance *hero = gs->getHero(hid);
-	vstd::amax(val, 0); //not less than 0
-	hero->mana = val;
+	CGHeroInstance * hero = gs->getHero(hid);
+	
+	assert(hero);
+	
+	if(absolute)
+		hero->mana = val;		
+	else
+		hero->mana += val;
+
+	vstd::amax(hero->mana, 0); //not less than 0
 }
 
 DLL_LINKAGE void SetMovePoints::applyGs( CGameState *gs )
@@ -1329,44 +1336,10 @@ DLL_LINKAGE void StartAction::applyGs( CGameState *gs )
 DLL_LINKAGE void BattleSpellCast::applyGs( CGameState *gs )
 {
 	assert(gs->curB);
-	if (castedByHero)
-	{
-		CGHeroInstance * h = gs->curB->battleGetFightingHero(side);
-		CGHeroInstance * enemy = gs->curB->battleGetFightingHero(!side);
-
-		h->mana -= spellCost;
-			vstd::amax(h->mana, 0);
-		if (enemy && manaGained)
-			enemy->mana += manaGained;
-		if (side < 2)
-		{
-			gs->curB->sides[side].castSpellsCount++;
-		}
-	}
-
-	//Handle spells removing effects from stacks
-	const CSpell *spell = SpellID(id).toSpell();
-	const bool removeAllSpells = id == SpellID::DISPEL;
-	const bool removeHelpful = id == SpellID::DISPEL_HELPFUL_SPELLS;
-
-	for(auto stackID : affectedCres)
-	{
-		if(vstd::contains(resisted, stackID))
-			continue;
-
-		CStack *s = gs->curB->getStack(stackID);
-		s->popBonuses([&](const Bonus *b) -> bool
-		{
-			//check for each bonus if it should be removed
-			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
-			const bool isPositiveSpell = Selector::positiveSpellEffects(b);
-			const int spellID = isSpellEffect ? b->sid : -1;
 
-			return (removeHelpful && isPositiveSpell)
-				|| (removeAllSpells && isSpellEffect)
-				|| vstd::contains(spell->counteredSpells, spellID);
-		});
-	}
+	const CSpell * spell = SpellID(id).toSpell();
+	
+	spell->afterCast(gs->curB, this);
 }
 
 void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
@@ -1591,6 +1564,7 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs )
 
 DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
 {
+	newStackID = 0;
 	if (!BattleHex(pos).isValid())
 	{
         logNetwork->warnStream() << "No place found for new stack!";
@@ -1604,6 +1578,8 @@ DLL_LINKAGE void BattleStackAdded::applyGs(CGameState *gs)
 
 	gs->curB->localInitStack(addedStack);
 	gs->curB->stacks.push_back(addedStack); //the stack is not "SUMMONED", it is permanent
+	
+	newStackID = addedStack->ID;
 }
 
 DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)

+ 1335 - 0
lib/SpellMechanics.cpp

@@ -0,0 +1,1335 @@
+/*
+ * SpellMechanics.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 "SpellMechanics.h"
+
+#include "CObstacleInstance.h"
+#include "mapObjects/CGHeroInstance.h"
+#include "BattleState.h"
+#include "CRandomGenerator.h"
+
+#include "NetPacks.h"
+
+namespace SRSLPraserHelpers
+{
+	static int XYToHex(int x, int y)
+	{
+		return x + GameConstants::BFIELD_WIDTH * y;
+	}
+
+	static int XYToHex(std::pair<int, int> xy)
+	{
+		return XYToHex(xy.first, xy.second);
+	}
+
+	static int hexToY(int battleFieldPosition)
+	{
+		return battleFieldPosition/GameConstants::BFIELD_WIDTH;
+	}
+
+	static int hexToX(int battleFieldPosition)
+	{
+		int pos = battleFieldPosition - hexToY(battleFieldPosition) * GameConstants::BFIELD_WIDTH;
+		return pos;
+	}
+
+	static std::pair<int, int> hexToPair(int battleFieldPosition)
+	{
+		return std::make_pair(hexToX(battleFieldPosition), hexToY(battleFieldPosition));
+	}
+
+	//moves hex by one hex in given direction
+	//0 - left top, 1 - right top, 2 - right, 3 - right bottom, 4 - left bottom, 5 - left
+	static std::pair<int, int> gotoDir(int x, int y, int direction)
+	{
+		switch(direction)
+		{
+		case 0: //top left
+			return std::make_pair((y%2) ? x-1 : x, y-1);
+		case 1: //top right
+			return std::make_pair((y%2) ? x : x+1, y-1);
+		case 2:  //right
+			return std::make_pair(x+1, y);
+		case 3: //right bottom
+			return std::make_pair((y%2) ? x : x+1, y+1);
+		case 4: //left bottom
+			return std::make_pair((y%2) ? x-1 : x, y+1);
+		case 5: //left
+			return std::make_pair(x-1, y);
+		default:
+			throw std::runtime_error("Disaster: wrong direction in SRSLPraserHelpers::gotoDir!\n");
+		}
+	}
+
+	static std::pair<int, int> gotoDir(std::pair<int, int> xy, int direction)
+	{
+		return gotoDir(xy.first, xy.second, direction);
+	}
+
+	static bool isGoodHex(std::pair<int, int> xy)
+	{
+		return xy.first >=0 && xy.first < GameConstants::BFIELD_WIDTH && xy.second >= 0 && xy.second < GameConstants::BFIELD_HEIGHT;
+	}
+
+	//helper function for rangeInHexes
+	static std::set<ui16> getInRange(unsigned int center, int low, int high)
+	{
+		std::set<ui16> ret;
+		if(low == 0)
+		{
+			ret.insert(center);
+		}
+
+		std::pair<int, int> mainPointForLayer[6]; //A, B, C, D, E, F points
+		for(auto & elem : mainPointForLayer)
+			elem = hexToPair(center);
+
+		for(int it=1; it<=high; ++it) //it - distance to the center
+		{
+			for(int b=0; b<6; ++b)
+				mainPointForLayer[b] = gotoDir(mainPointForLayer[b], b);
+
+			if(it>=low)
+			{
+				std::pair<int, int> curHex;
+
+				//adding lines (A-b, B-c, C-d, etc)
+				for(int v=0; v<6; ++v)
+				{
+					curHex = mainPointForLayer[v];
+					for(int h=0; h<it; ++h)
+					{
+						if(isGoodHex(curHex))
+							ret.insert(XYToHex(curHex));
+						curHex = gotoDir(curHex, (v+2)%6);
+					}
+				}
+
+			} //if(it>=low)
+		}
+
+		return ret;
+	}
+}
+
+struct SpellCastContext
+{
+	SpellCastContext(std::vector<const CStack*> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
+		attackedCres(attackedCres), sc(sc), si(si){}; 
+	std::vector<const CStack*> & attackedCres;
+	BattleSpellCast & sc;
+	StacksInjured & si;
+};
+
+class DefaultSpellMechanics: public ISpellMechanics
+{
+public:
+	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
+	
+	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
+	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	
+	//bool adventureCast(const SpellCastContext & context) const override; 
+	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
+	
+	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;
+protected:
+	
+	virtual void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
+	
+	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
+	
+};
+
+class AcidBreathDamageMechnics: public DefaultSpellMechanics
+{
+public:
+	AcidBreathDamageMechnics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+class ChainLightningMechanics: public DefaultSpellMechanics
+{
+public:
+	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+};
+
+class CloneMechanics: public DefaultSpellMechanics
+{
+public:
+	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
+};
+
+class CureMechanics: public DefaultSpellMechanics
+{
+public:
+	CureMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+	
+	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;	
+};
+
+class DeathStareMechnics: public DefaultSpellMechanics
+{
+public:
+	DeathStareMechnics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+class DispellHelpfulMechanics: public DefaultSpellMechanics
+{
+public:
+	DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	
+	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;
+	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
+};
+
+class DispellMechanics: public DefaultSpellMechanics
+{
+public:
+	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	
+	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;	
+};
+
+class HypnotizeMechanics: public DefaultSpellMechanics
+{
+public:
+	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
+}; 
+
+class ObstacleMechanics: public DefaultSpellMechanics
+{
+public:
+	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};		
+
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
+};
+
+class WallMechanics: public ObstacleMechanics
+{
+public:
+	WallMechanics(CSpell * s): ObstacleMechanics(s){};	
+	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;		
+};
+
+class RemoveObstacleMechanics: public DefaultSpellMechanics
+{
+public:
+	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+///all rising spells
+class RisingSpellMechanics: public DefaultSpellMechanics
+{
+public:
+	RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};		
+	
+};
+
+class SacrificeMechanics: public RisingSpellMechanics
+{
+public:
+	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};	
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+///all rising spells but SACRIFICE
+class SpecialRisingSpellMechanics: public RisingSpellMechanics
+{
+public:
+	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;						
+};
+
+class SummonMechanics: public DefaultSpellMechanics
+{
+public:
+	SummonMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+class TeleportMechanics: public DefaultSpellMechanics
+{
+public:
+	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+///ISpellMechanics
+ISpellMechanics::ISpellMechanics(CSpell * s):
+	owner(s)
+{
+	
+}
+
+ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
+{
+	switch (s->id)
+	{
+	case SpellID::ACID_BREATH_DAMAGE:
+		return new AcidBreathDamageMechnics(s);
+	case SpellID::CHAIN_LIGHTNING:
+		return new ChainLightningMechanics(s);		
+	case SpellID::CLONE:
+		return new CloneMechanics(s);
+	case SpellID::CURE:
+		return new CureMechanics(s);
+	case SpellID::DEATH_STARE:
+		return new DeathStareMechnics(s);			
+	case SpellID::DISPEL:
+		return new DispellMechanics(s);	
+	case SpellID::DISPEL_HELPFUL_SPELLS:
+		return new DispellHelpfulMechanics(s);
+	case SpellID::FIRE_WALL:
+	case SpellID::FORCE_FIELD:
+		return new WallMechanics(s);		
+	case SpellID::HYPNOTIZE:
+		return new HypnotizeMechanics(s);
+	case SpellID::LAND_MINE:
+	case SpellID::QUICKSAND:
+		return new ObstacleMechanics(s);
+	case SpellID::REMOVE_OBSTACLE:
+		return new RemoveObstacleMechanics(s);
+	case SpellID::SACRIFICE:
+		return new SacrificeMechanics(s);
+	case SpellID::SUMMON_FIRE_ELEMENTAL:
+	case SpellID::SUMMON_EARTH_ELEMENTAL:
+	case SpellID::SUMMON_WATER_ELEMENTAL:
+	case SpellID::SUMMON_AIR_ELEMENTAL:
+		return new SummonMechanics(s);
+	case SpellID::TELEPORT:
+		return new TeleportMechanics(s);
+	default:		
+		if(s->isRisingSpell())
+			return new SpecialRisingSpellMechanics(s);
+		else	
+			return new DefaultSpellMechanics(s);		
+	}	
+}
+
+
+///DefaultSpellMechanics
+void DefaultSpellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	if (packet->castedByHero)
+	{
+		if (packet->side < 2)
+		{
+			battle->sides[packet->side].castSpellsCount++;
+		}
+	}
+	
+	//handle countering spells
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack * s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus * b) -> bool
+		{
+			//check for each bonus if it should be removed
+			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
+			const int spellID = isSpellEffect ? b->sid : -1;
+
+			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
+		});
+	}	
+}
+
+
+void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
+{
+	BattleSpellCast sc;
+	sc.side = parameters.casterSide;
+	sc.id = owner->id;
+	sc.skill = parameters.spellLvl;
+	sc.tile = parameters.destination;
+	sc.dmgToDisplay = 0;
+	sc.castedByHero = nullptr != parameters.caster;
+	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
+	sc.manaGained = 0;
+	
+	int spellCost = 0;	
+	
+	//calculate spell cost
+	if(parameters.caster) 
+	{
+		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.caster);
+
+		if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
+		{
+			int manaChannel = 0;
+			for(const CStack * stack : parameters.cb->battleGetAllStacks(true)) //TODO: shouldn't bonus system handle it somehow?
+			{
+				if(stack->owner == parameters.secHero->tempOwner)
+				{
+					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
+				}
+			}
+			sc.manaGained = (manaChannel * spellCost) / 100;
+		}
+	}	
+	
+	
+	//calculating affected creatures for all spells
+	//must be vector, as in Chain Lightning order matters
+	std::vector<const CStack*> attackedCres; //CStack vector is somewhat more suitable than ID vector
+
+	auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.destination, parameters.caster);
+	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
+	
+	for (auto cre : attackedCres)
+	{
+		sc.affectedCres.insert(cre->ID);
+	}
+	
+	//checking if creatures resist
+	//resistance is applied only to negative spells
+	if(owner->isNegative())
+	{
+		for(auto s : attackedCres)
+		{
+			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
+			
+			if(env->getRandomGenerator().nextInt(99) < prob)
+			{
+				sc.resisted.push_back(s->ID);
+			}
+		}
+	}
+	
+	StacksInjured si;	
+	SpellCastContext ctx(attackedCres, sc, si);
+	
+	applyBattleEffects(env, parameters, ctx);
+	
+	env->sendAndApply(&sc);
+	
+
+	//spend mana
+	if(parameters.caster) 
+	{
+		SetMana sm;
+		sm.absolute = false;
+		
+		sm.hid = parameters.caster->id;
+		sm.val = -spellCost;
+		
+		env->sendAndApply(&sm);
+		
+		if(sc.manaGained > 0)
+		{
+			assert(parameters.secHero);
+			
+			sm.hid = parameters.secHero->id;
+			sm.val = sc.manaGained;
+			env->sendAndApply(&sm);
+		}		
+	}
+	
+	if(!si.stacks.empty()) //after spellcast info shows
+		env->sendAndApply(&si);
+	
+	//reduce number of casts remaining
+	//TODO: this should be part of BattleSpellCast apply
+	if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING) 
+	{
+		assert(parameters.casterStack);
+		
+		BattleSetStackProperty ssp;
+		ssp.stackID = parameters.casterStack->ID;
+		ssp.which = BattleSetStackProperty::CASTS;
+		ssp.val = -1;
+		ssp.absolute = false;
+		env->sendAndApply(&ssp);
+	}
+
+	//Magic Mirror effect
+	if(owner->isNegative() && parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
+	{
+		for(auto & attackedCre : attackedCres)
+		{
+			int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
+			if(mirrorChance > env->getRandomGenerator().nextInt(99))
+			{
+				std::vector<const CStack *> mirrorTargets;
+				auto battleStacks = parameters.cb->battleGetAllStacks(true);
+				for(auto & battleStack : battleStacks)
+				{
+					if(battleStack->owner == parameters.casterColor) //get enemy stacks which can be affected by this spell
+					{
+						if (ESpellCastProblem::OK == owner->isImmuneByStack(nullptr, battleStack))
+							mirrorTargets.push_back(battleStack);
+					}
+				}
+				if(!mirrorTargets.empty())
+				{
+					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
+					
+					BattleSpellCastParameters mirrorParameters = parameters;
+					mirrorParameters.spellLvl = 0;
+					mirrorParameters.casterSide = 1-parameters.casterSide;
+					mirrorParameters.casterColor = (attackedCre)->owner;
+					mirrorParameters.caster = nullptr;
+					mirrorParameters.destination = targetHex;
+					mirrorParameters.secHero = parameters.caster;
+					mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
+					mirrorParameters.casterStack = (attackedCre);
+					mirrorParameters.selectedStack = nullptr;
+					
+					battleCast(env, mirrorParameters);					
+				}
+			}
+		}
+	}	
+}
+
+int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
+{
+	if(!caster)
+	{
+		if (!usedSpellPower)
+			return 3; //default duration of all creature spells
+		else
+			return usedSpellPower; //use creature spell power
+	}
+	switch(owner->id)
+	{
+	case SpellID::FRENZY:
+		return 1;
+	default: //other spells
+		return caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER) + caster->valOfBonuses(Bonus::SPELL_DURATION);
+	}	
+}
+
+void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//applying effects
+	if(owner->isOffensiveSpell())
+	{
+		int spellDamage = 0;
+		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
+		{
+			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
+			if(unitSpellPower)
+				ctx.sc.dmgToDisplay = spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities
+			else //Faerie Dragon
+			{
+				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
+				ctx.sc.dmgToDisplay = 0;
+			}
+		}
+		int chainLightningModifier = 0;
+		for(auto & attackedCre : ctx.attackedCres)
+		{
+			if(vstd::contains(ctx.sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
+				continue;
+
+			BattleStackAttacked bsa;
+			if(spellDamage)
+				bsa.damageAmount = spellDamage >> chainLightningModifier;
+			else
+				bsa.damageAmount =  owner->calculateDamage(parameters.caster, attackedCre, parameters.spellLvl, parameters.usedSpellPower) >> chainLightningModifier;
+
+			ctx.sc.dmgToDisplay += bsa.damageAmount;
+
+			bsa.stackAttacked = (attackedCre)->ID;
+			if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
+				bsa.attackerID = parameters.casterStack->ID;
+			else
+				bsa.attackerID = -1;
+			(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+			ctx.si.stacks.push_back(bsa);
+
+			if(owner->id == SpellID::CHAIN_LIGHTNING)
+				++chainLightningModifier;
+		}
+	}
+	
+	if(owner->hasEffects())
+	{
+		int stackSpellPower = 0;
+		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
+		{
+			stackSpellPower =  parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
+		}
+		SetStackEffect sse;
+		Bonus pseudoBonus;
+		pseudoBonus.sid = owner->id;
+		pseudoBonus.val = parameters.spellLvl;
+		pseudoBonus.turnsRemain = calculateDuration(parameters.caster, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);
+		CStack::stackEffectToFeature(sse.effect, pseudoBonus);
+		if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
+		{
+			sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
+		}
+		if(owner->id == SpellID::BIND &&  parameters.casterStack)//bind
+		{
+			sse.effect.back().additionalInfo =  parameters.casterStack->ID; //we need to know who casted Bind
+		}
+		const Bonus * bonus = nullptr;
+		if(parameters.caster)
+			bonus = parameters.caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
+		//TODO does hero specialty should affects his stack casting spells?
+
+		si32 power = 0;
+		for(const CStack * affected : ctx.attackedCres)
+		{
+			if(vstd::contains(ctx.sc.resisted, affected->ID)) //this creature resisted the spell
+				continue;
+			sse.stacks.push_back(affected->ID);
+
+			//Apply hero specials - peculiar enchants
+			const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
+			if(bonus)
+			{
+				switch(bonus->additionalInfo)
+				{
+					case 0: //normal
+					{
+						switch(tier)
+						{
+							case 1: case 2:
+								power = 3;
+							break;
+							case 3: case 4:
+								power = 2;
+							break;
+							case 5: case 6:
+								power = 1;
+							break;
+						}
+						Bonus specialBonus(sse.effect.back());
+						specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
+						sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
+					}
+					break;
+					case 1: //only Coronius as yet
+					{
+						power = std::max(5 - tier, 0);
+						Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
+						specialBonus.sid = owner->id;
+						sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
+					}
+					break;
+				}
+			}
+			if (parameters.caster && parameters.caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
+			{
+				int damagePercent = parameters.caster->level * parameters.caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
+				Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
+				specialBonus.valType = Bonus::PERCENT_TO_ALL;
+				specialBonus.sid = owner->id;
+				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
+			}
+		}
+
+		if(!sse.stacks.empty())
+			env->sendAndApply(&sse);
+
+	}
+	
+	if(owner->isHealingSpell())
+	{
+		int hpGained = 0;
+		if(parameters.casterStack)
+		{
+			int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum());
+			if(unitSpellPower)
+				hpGained = parameters.casterStack->count * unitSpellPower; //Archangel
+			else //Faerie Dragon-like effect - unused so far
+				parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100;
+		}
+		StacksHealedOrResurrected shr;
+		shr.lifeDrain = false;
+		shr.tentHealing = false;
+		for(auto & attackedCre : ctx.attackedCres)
+		{
+			StacksHealedOrResurrected::HealInfo hi;
+			hi.stackID = (attackedCre)->ID;
+			if (parameters.casterStack) //casted by creature
+			{
+				const bool resurrect = owner->isRisingSpell();
+				if (hpGained)
+				{
+					//archangel
+					hi.healedHP = std::min<ui32>(hpGained, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
+				}
+				else
+				{
+					//any typical spell (commander's cure or animate dead)
+					int healedHealth = parameters.usedSpellPower * owner->power + owner->getPower(parameters.spellLvl);
+					hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
+				}					
+			}
+			else
+				hi.healedHP = owner->calculateHealedHP(parameters.caster, attackedCre, parameters.selectedStack); //Casted by hero
+			hi.lowLevelResurrection = parameters.spellLvl <= 1;
+			shr.healedStacks.push_back(hi);
+		}
+		if(!shr.healedStacks.empty())
+			env->sendAndApply(&shr);
+	}		
+}
+
+
+std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
+{
+	using namespace SRSLPraserHelpers;
+	
+	std::vector<BattleHex> ret;
+	std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
+
+	if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
+	{
+		std::string number1, number2;
+		int beg, end;
+		bool readingFirst = true;
+		for(auto & elem : rng)
+		{
+			if(std::isdigit(elem) ) //reading number
+			{
+				if(readingFirst)
+					number1 += elem;
+				else
+					number2 += elem;
+			}
+			else if(elem == ',') //comma
+			{
+				//calculating variables
+				if(readingFirst)
+				{
+					beg = atoi(number1.c_str());
+					number1 = "";
+				}
+				else
+				{
+					end = atoi(number2.c_str());
+					number2 = "";
+				}
+				//obtaining new hexes
+				std::set<ui16> curLayer;
+				if(readingFirst)
+				{
+					curLayer = getInRange(centralHex, beg, beg);
+				}
+				else
+				{
+					curLayer = getInRange(centralHex, beg, end);
+					readingFirst = true;
+				}
+				//adding abtained hexes
+				for(auto & curLayer_it : curLayer)
+				{
+					ret.push_back(curLayer_it);
+				}
+
+			}
+			else if(elem == '-') //dash
+			{
+				beg = atoi(number1.c_str());
+				number1 = "";
+				readingFirst = false;
+			}
+		}
+	}
+
+	//remove duplicates (TODO check if actually needed)
+	range::unique(ret);
+	return ret;		
+}
+
+
+std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+{
+	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
+	
+	const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
+	const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
+
+	const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
+	
+	//TODO: more generic solution for mass spells
+	if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
+	{
+		for(BattleHex hex : attackedHexes)
+		{
+			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
+			{
+				attackedCres.insert(st);
+			}
+		}
+	}
+	else if(ti.type == CSpell::CREATURE)
+	{
+		auto predicate = [=](const CStack * s){
+			const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
+			const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.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(ctx.destination);
+			//handle smart targeting
+			const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
+			
+			return rangeCovers && positivenessFlag && validTarget;		
+		};
+		
+		TStacks stacks = ctx.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 = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
+				attackedCres.insert(st);
+		}
+	}	
+	
+	return attackedCres;
+}
+
+
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	//by default use general algorithm
+	return owner->isImmuneBy(obj);
+}
+
+///AcidBreathDamageMechnics
+void AcidBreathDamageMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	
+	for(auto & attackedCre : ctx.attackedCres) //no immunities
+	{
+		BattleStackAttacked bsa;
+		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
+		bsa.spellID = owner->id;
+		bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers
+		bsa.stackAttacked = (attackedCre)->ID;
+		bsa.attackerID = -1;
+		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+		ctx.si.stacks.push_back(bsa);
+	}	
+}
+
+///ChainLightningMechanics
+std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+{
+	std::set<const CStack* > attackedCres;
+	
+	std::set<BattleHex> possibleHexes;
+	for(auto stack : ctx.cb->battleGetAllStacks())
+	{
+		if(stack->isValidTarget())
+		{
+			for(auto hex : stack->getHexes())
+			{
+				possibleHexes.insert (hex);
+			}
+		}
+	}
+	int targetsOnLevel[4] = {4, 4, 5, 5};
+
+	BattleHex lightningHex = ctx.destination;
+	for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
+	{
+		auto stack = ctx.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, ctx.destination, possibleHexes);
+	}	
+		
+	return attackedCres;
+}
+
+///CloneMechanics
+void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	const CStack * clonedStack = nullptr;
+	if(ctx.attackedCres.size())
+		clonedStack = *ctx.attackedCres.begin();
+	if(!clonedStack)
+	{
+		env->complain ("No target stack to clone!");
+		return;
+	}
+	const int attacker = !(bool)parameters.casterSide; 
+
+	BattleStackAdded bsa;
+	bsa.creID = clonedStack->type->idNumber;
+	bsa.attacker = attacker;
+	bsa.summoned = true;
+	bsa.pos = parameters.cb->getAvaliableHex(bsa.creID, attacker); //TODO: unify it
+	bsa.amount = clonedStack->count;
+	env->sendAndApply(&bsa);
+
+	BattleSetStackProperty ssp;
+	ssp.stackID = bsa.newStackID;//we know stack ID after apply
+	ssp.which = BattleSetStackProperty::CLONED;
+	ssp.val = 0;
+	ssp.absolute = 1;
+	env->sendAndApply(&ssp);	
+}
+
+ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	//can't clone already cloned creature
+	if(vstd::contains(obj->state, EBattleStackState::CLONED))
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	//TODO: how about stacks casting Clone?
+	//currently Clone casted by stack is assumed Expert level
+	ui8 schoolLevel;
+	if(caster)
+	{
+		schoolLevel = caster->getSpellSchoolLevel(owner);
+	}
+	else
+	{
+		schoolLevel = 3;
+	}
+
+	if(schoolLevel < 3)
+	{
+		int maxLevel = (std::max(schoolLevel, (ui8)1) + 4);
+		int creLevel = obj->getCreature()->level;
+		if(maxLevel < creLevel) //tier 1-5 for basic, 1-6 for advanced, any level for expert
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+	//use default algorithm only if there is no mechanics-related problem		
+	return DefaultSpellMechanics::isImmuneByStack(caster, obj);	
+}
+
+///CureMechanics
+void CureMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::afterCast(battle, packet);
+	
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+		{
+			logGlobal->errorStream() << "Resistance to positive spell CURE";
+			continue;
+		}			
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			if(b->source == Bonus::SPELL_EFFECT)
+			{
+				CSpell * sp = SpellID(b->sid).toSpell();
+				return sp->isNegative();
+			}
+			return false; //not a spell effect
+		});
+	}		
+}
+
+
+///DeathStareMechnics
+void DeathStareMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
+	
+	for(auto & attackedCre : ctx.attackedCres)
+	{
+		BattleStackAttacked bsa;
+		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
+		bsa.spellID = owner->id;
+		bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
+		bsa.stackAttacked = (attackedCre)->ID;
+		bsa.attackerID = -1;
+		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
+		ctx.si.stacks.push_back(bsa);
+	}	
+}
+
+
+///DispellHelpfulMechanics
+void DispellHelpfulMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::afterCast(battle, packet);
+	
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			return Selector::positiveSpellEffects(b);
+		});
+	}	
+}
+
+
+ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster,  const CStack * obj) const
+{
+	TBonusListPtr spellBon = obj->getSpellBonuses();
+	bool hasPositiveSpell = false;
+	for(const Bonus * b : *spellBon)
+	{
+		if(SpellID(b->sid).toSpell()->isPositive())
+		{
+			hasPositiveSpell = true;
+			break;
+		}
+	}
+	if(!hasPositiveSpell)
+	{
+		return ESpellCastProblem::NO_SPELLS_TO_DISPEL;
+	}
+	
+	//use default algorithm only if there is no mechanics-related problem		
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
+}
+
+///DispellMechanics
+void DispellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::afterCast(battle, packet);
+	
+	for(auto stackID : packet->affectedCres)
+	{
+		if(vstd::contains(packet->resisted, stackID))
+			continue;
+
+		CStack *s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus *b) -> bool
+		{
+			return Selector::sourceType(Bonus::SPELL_EFFECT)(b);
+		});
+	}	
+}
+
+
+///HypnotizeMechanics
+ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+{
+	if(nullptr != caster) //do not resist hypnotize casted after attack, for example
+	{
+		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
+		ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
+		//apply 'damage' bonus for hypnotize, including hero specialty
+		ui64 maxHealth = owner->calculateBonus(caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
+			* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), caster, obj);
+		if (subjectHealth > maxHealth)
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}			
+	return DefaultSpellMechanics::isImmuneByStack(caster, obj);
+}
+
+///ObstacleMechanics
+void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	auto placeObstacle = [&, this](BattleHex pos)
+	{
+		static int obstacleIdToGive =  parameters.cb->obstacles.size()
+									? (parameters.cb->obstacles.back()->uniqueID+1)
+									: 0;
+
+		auto obstacle = make_shared<SpellCreatedObstacle>();
+		switch(owner->id) // :/
+		{
+		case SpellID::QUICKSAND:
+			obstacle->obstacleType = CObstacleInstance::QUICKSAND;
+			obstacle->turnsRemaining = -1;
+			obstacle->visibleForAnotherSide = false;
+			break;
+		case SpellID::LAND_MINE:
+			obstacle->obstacleType = CObstacleInstance::LAND_MINE;
+			obstacle->turnsRemaining = -1;
+			obstacle->visibleForAnotherSide = false;
+			break;
+		case SpellID::FIRE_WALL:
+			obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
+			obstacle->turnsRemaining = 2;
+			obstacle->visibleForAnotherSide = true;
+			break;
+		case SpellID::FORCE_FIELD:
+			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
+			obstacle->turnsRemaining = 2;
+			obstacle->visibleForAnotherSide = true;
+			break;
+		default:
+			//this function cannot be used with spells that do not create obstacles
+			assert(0);
+		}
+
+		obstacle->pos = pos;
+		obstacle->casterSide = parameters.casterSide;
+		obstacle->ID = owner->id;
+		obstacle->spellLevel = parameters.spellLvl;
+		obstacle->casterSpellPower = parameters.usedSpellPower;
+		obstacle->uniqueID = obstacleIdToGive++;
+
+		BattleObstaclePlaced bop;
+		bop.obstacle = obstacle;
+		env->sendAndApply(&bop);
+	};	
+	
+	switch(owner->id)
+	{
+	case SpellID::QUICKSAND:
+	case SpellID::LAND_MINE:
+		{
+			std::vector<BattleHex> availableTiles;
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
+			{
+				BattleHex hex = i;
+				if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
+					availableTiles.push_back(hex);
+			}
+			boost::range::random_shuffle(availableTiles);
+
+			const int patchesForSkill[] = {4, 4, 6, 8};
+			const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
+
+			//land mines or quicksand patches are handled as spell created obstacles
+			for (int i = 0; i < patchesToPut; i++)
+				placeObstacle(availableTiles.at(i));
+		}
+
+		break;
+	case SpellID::FORCE_FIELD:
+		placeObstacle(parameters.destination);
+		break;
+	case SpellID::FIRE_WALL:
+		{
+			//fire wall is build from multiple obstacles - one fire piece for each affected hex
+			auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
+			for(BattleHex hex : affectedHexes)
+				placeObstacle(hex);
+		}
+		break;
+	default:		
+		assert(0);
+	}			
+}
+
+
+///WallMechanics
+std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
+{
+	using namespace SRSLPraserHelpers;
+
+	std::vector<BattleHex> ret;	
+	
+	//Special case - shape of obstacle depends on caster's side
+	//TODO make it possible through spell_info config
+
+	BattleHex::EDir firstStep, secondStep;
+	if(side)
+	{
+		firstStep = BattleHex::TOP_LEFT;
+		secondStep = BattleHex::TOP_RIGHT;
+	}
+	else
+	{
+		firstStep = BattleHex::TOP_RIGHT;
+		secondStep = BattleHex::TOP_LEFT;
+	}
+
+	//Adds hex to the ret if it's valid. Otherwise sets output arg flag if given.
+	auto addIfValid = [&](BattleHex hex)
+	{
+		if(hex.isValid())
+			ret.push_back(hex);
+		else if(outDroppedHexes)
+			*outDroppedHexes = true;
+	};
+
+	ret.push_back(centralHex);
+	addIfValid(centralHex.moveInDir(firstStep, false));
+	if(schoolLvl >= 2) //advanced versions of fire wall / force field cotnains of 3 hexes
+		addIfValid(centralHex.moveInDir(secondStep, false)); //moveInDir function modifies subject hex
+
+	return ret;	
+}
+
+///RemoveObstacleMechanics
+void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
+	{
+		ObstaclesRemoved obr;
+		obr.obstacles.insert(obstacleToRemove->uniqueID);
+		env->sendAndApply(&obr);
+	}
+	else
+		env->complain("There's no obstacle to remove!");	
+}
+
+///SpecialRisingSpellMechanics
+void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
+
+	if(parameters.selectedStack == parameters.cb->battleActiveStack())
+	//set another active stack than the one removed, or bad things will happen
+	//TODO: make that part of BattleStacksRemoved? what about client update?
+	{
+		//makeStackDoNothing(gs->curB->getStack (selectedStack));
+
+		BattleSetActiveStack sas;
+
+		//std::vector<const CStack *> hlp;
+		//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
+
+		//if(hlp.size())
+		//{
+		//	sas.stack = hlp[0]->ID;
+		//}
+		//else
+		//	complain ("No new stack to activate!");
+		sas.stack = parameters.cb->getNextStack()->ID; //why the hell next stack has same ID as current?
+		env->sendAndApply(&sas);
+
+	}
+	BattleStacksRemoved bsr;
+	bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
+	env->sendAndApply(&bsr);
+		
+}
+
+
+///SpecialRisingSpellMechanics
+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
+
+	if(obj->count >= obj->baseAmount)
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	
+	if(caster) //FIXME: Archangels can cast immune stack
+	{
+		auto maxHealth = owner->calculateHealedHP(caster, obj);
+		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}	
+	
+	return DefaultSpellMechanics::isImmuneByStack(caster,obj);	
+}
+
+///SummonMechanics
+void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//todo: make configurable
+	CreatureID creID = CreatureID::NONE;
+	switch(owner->id)
+	{
+		case SpellID::SUMMON_FIRE_ELEMENTAL:
+			creID = CreatureID::FIRE_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_EARTH_ELEMENTAL:
+			creID = CreatureID::EARTH_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_WATER_ELEMENTAL:
+			creID = CreatureID::WATER_ELEMENTAL;
+			break;
+		case SpellID::SUMMON_AIR_ELEMENTAL:
+			creID = CreatureID::AIR_ELEMENTAL;
+			break;
+		default:
+			env->complain("Unable to determine summoned creature");
+			return;
+	}
+
+	BattleStackAdded bsa;
+	bsa.creID = creID;
+	bsa.attacker = !(bool)parameters.casterSide;
+	bsa.summoned = true;
+	bsa.pos = parameters.cb->getAvaliableHex(creID, !(bool)parameters.casterSide); //TODO: unify it
+
+	//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
+	int percentBonus = parameters.caster ? parameters.caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
+
+	bsa.amount = parameters.usedSpellPower
+		* owner->getPower(parameters.spellLvl)
+		* (100 + percentBonus) / 100.0; //new feature - percentage bonus
+	if(bsa.amount)
+		env->sendAndApply(&bsa);
+	else
+		env->complain("Summoning didn't summon any!");	
+}
+
+
+///TeleportMechanics
+void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	BattleStackMoved bsm;
+	bsm.distance = -1;
+	bsm.stack = parameters.selectedStack->ID;
+	std::vector<BattleHex> tiles;
+	tiles.push_back(parameters.destination);
+	bsm.tilesToMove = tiles;
+	bsm.teleporting = true;
+	env->sendAndApply(&bsm);	
+}
+	
+

+ 54 - 0
lib/SpellMechanics.h

@@ -0,0 +1,54 @@
+/*
+ * SpellMechanics.h, 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
+ *
+ */
+ 
+#pragma once
+
+#include "CSpellHandler.h"
+#include "BattleHex.h"
+
+class DLL_LINKAGE ISpellMechanics
+{
+public:
+	
+	struct DLL_LINKAGE SpellTargetingContext
+	{
+		const CBattleInfoCallback * cb;		
+		CSpell::TargetInfo ti;
+		ECastingMode::ECastingMode mode;
+		BattleHex destination;
+		PlayerColor casterColor;
+		int schoolLvl;
+		
+		SpellTargetingContext(const CSpell * s, const CBattleInfoCallback * c, ECastingMode::ECastingMode m, PlayerColor cc, int lvl, BattleHex dest)
+			: cb(c), ti(s,lvl, m), mode(m), destination(dest), casterColor(cc), schoolLvl(lvl)
+		{};
+		
+	};
+	
+public:
+	ISpellMechanics(CSpell * s);
+	virtual ~ISpellMechanics(){};	
+	
+	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const = 0;
+	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
+	
+	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
+                   
+	//virtual bool adventureCast(const SpellCastContext & context) const = 0; 
+	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0; 	
+	
+	static ISpellMechanics * createMechanics(CSpell * s);
+	
+	virtual void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
+	
+protected:
+	CSpell * owner;	
+};
+

+ 2 - 0
lib/VCMI_lib.cbp

@@ -185,6 +185,8 @@
 		<Unit filename="ResourceSet.cpp" />
 		<Unit filename="ResourceSet.h" />
 		<Unit filename="ScopeGuard.h" />
+		<Unit filename="SpellMechanics.cpp" />
+		<Unit filename="SpellMechanics.h" />
 		<Unit filename="StartInfo.h" />
 		<Unit filename="StdInc.h">
 			<Option weight="0" />

+ 1 - 0
lib/VCMI_lib.vcxproj

@@ -185,6 +185,7 @@
     <ClCompile Include="CObstacleInstance.cpp" />
     <ClCompile Include="Connection.cpp" />
     <ClCompile Include="CSpellHandler.cpp" />
+    <ClCompile Include="SpellMechanics.cpp" />
     <ClCompile Include="CThreadHelper.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="CRandomGenerator.cpp" />

+ 13 - 51
lib/mapObjects/CGHeroInstance.cpp

@@ -863,28 +863,18 @@ TExpType CGHeroInstance::calculateXp(TExpType exp) const
 ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const
 {
 	si16 skill = -1; //skill level
-
-#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId)	\
-	if(spell-> schoolName)									\
-	{															\
-		int thisSchool = std::max<int>(getSecSkillLevel( \
-			SecondarySkill(14 + (schoolMechanicsId))), \
-			valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \
-		if(thisSchool > skill)									\
-		{														\
-			skill = thisSchool;									\
-			if(outSelectedSchool)								\
-				*outSelectedSchool = schoolOutId;				\
-		}														\
-	}
-	TRY_SCHOOL(fire, 0, 1)
-	TRY_SCHOOL(air, 1, 0)
-	TRY_SCHOOL(water, 2, 2)
-	TRY_SCHOOL(earth, 3, 3)
-#undef TRY_SCHOOL
-
-
-
+	
+	spell->forEachSchool([&, this](const SpellSchoolInfo & cnf, bool & stop)
+	{
+		int thisSchool = std::max<int>(getSecSkillLevel(cnf.skill),	valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << ((ui8)cnf.id))); 
+		if(thisSchool > skill)									
+		{														
+			skill = thisSchool;									
+			if(outSelectedSchool)								
+				*outSelectedSchool = (ui8)cnf.id;				
+		}																
+	});
+	
 	vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
 	vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect
 	if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia)
@@ -895,35 +885,7 @@ ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSc
 
 bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
 {
-	if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook
-		return false;
-
-    if (spell->isSpecialSpell())
-    {
-        if (vstd::contains(spells, spell->id))
-        {//hero has this spell in spellbook
-            logGlobal->errorStream() << "Special spell in spellbook "<<spell->name;
-        }
-
-        if (hasBonusOfType(Bonus::SPELL, spell->id))
-            return true;
-
-        return false;
-    }
-    else
-    {
-        if(vstd::contains(spells, spell->id) //hero has this spell in spellbook
-            || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells
-            || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells
-            || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells
-            || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells
-            || hasBonusOfType(Bonus::SPELL, spell->id)
-            || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level)
-            )
-            return true;
-
-        return false;
-    }
+	return spell->isCastableBy(this, nullptr !=getArt(ArtifactPosition::SPELLBOOK), spells);
 }
 
 /**

+ 199 - 609
server/CGameHandler.cpp

@@ -59,6 +59,18 @@ extern bool end2;
 		bnr.round = gs->curB->round + 1;\
 		sendAndApply(&bnr);
 
+class ServerSpellCastEnvironment: public SpellCastEnvironment
+{
+public:
+	ServerSpellCastEnvironment(CGameHandler * gh);
+	~ServerSpellCastEnvironment(){};
+	void sendAndApply(CPackForClient * info) const override;	
+	CRandomGenerator & getRandomGenerator() const override;
+	void complain(const std::string & problem) const override;
+private:
+	CGameHandler * gh;	
+};
+
 CondSh<bool> battleMadeAction;
 CondSh<BattleResult *> battleResult(nullptr);
 template <typename T> class CApplyOnGH;
@@ -782,12 +794,16 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 	}
 
 	const Bonus * bonus = att->getBonusLocalFirst(Selector::type(Bonus::SPELL_LIKE_ATTACK));
-	if (bonus && (bat.shot())) //TODO: make it work in meele?
-	{
-		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);
+	if (bonus && (bat.shot())) //TODO: make it work in melee?
+	{	
+		//this is need for displaying hit animation
+		bat.flags |= BattleAttack::SPELL_LIKE;
+		bat.spellID = SpellID(bonus->subtype);
+		
+		//TODO: should spell override creature`s projectile?
+		
+		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)
@@ -797,6 +813,18 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 				applyBattleEffects(bat, att, stack, distance, true);
 			}
 		}
+		
+		//now add effect info for all attacked stacks
+		for(BattleStackAttacked & bsa : bat.bsa)
+		{
+			if(bsa.attackerID == att->ID) //this is our attack and not f.e. fire shield
+			{
+				//this is need for displaying affect animation
+				bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
+				bsa.spellID = SpellID(bonus->subtype);
+			}
+		}
+		
 	}
 }
 void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary) //helper function for prepareAttack
@@ -1051,10 +1079,13 @@ CGameHandler::CGameHandler(void)
 	registerTypesServerPacks(*applier);
 	visitObjectAfterVictory = false;
 	queries.gh = this;
+	
+	spellEnv = new ServerSpellCastEnvironment(this);
 }
 
 CGameHandler::~CGameHandler(void)
 {
+	delete spellEnv;
 	delete applier;
 	applier = nullptr;
 	delete gs;
@@ -2029,6 +2060,7 @@ void CGameHandler::setManaPoints( ObjectInstanceID hid, int val )
 	SetMana sm;
 	sm.hid = hid;
 	sm.val = val;
+	sm.absolute = true;
 	sendAndApply(&sm);
 }
 
@@ -3757,17 +3789,27 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				complain("That stack can't cast spells!");
 			else
 			{
-				int spellLvl = 0;
+				BattleSpellCastParameters p(gs->curB);				
+				
+				p.spellLvl = 0;
 				if (spellcaster)
-					vstd::amax(spellLvl, spellcaster->val);
+					vstd::amax(p.spellLvl, spellcaster->val);
 				if (randSpellcaster)
-					vstd::amax(spellLvl, randSpellcaster->val);
-				vstd::amin (spellLvl, 3);
-
-				int casterSide = gs->curB->whatSide(stack->owner);
-				const CGHeroInstance * secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
-
-				handleSpellCasting(spellID, spellLvl, destination, casterSide, stack->owner, nullptr, secHero, 0, ECastingMode::CREATURE_ACTIVE_CASTING, stack);
+					vstd::amax(p.spellLvl, randSpellcaster->val);
+				vstd::amin (p.spellLvl, 3);
+
+				p.casterSide = gs->curB->whatSide(stack->owner);
+				p.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
+				p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
+				p.destination = destination;
+				p.casterColor = stack->owner;	
+				p.caster = nullptr;
+				p.usedSpellPower = 0;	
+				p.casterStack = stack;	
+				p.selectedStack = nullptr;				
+
+				const CSpell * spell = SpellID(spellID).toSpell();
+				spell->battleCast(spellEnv, p);
 			}
 			sendAndApply(&end_action);
 			break;
@@ -3804,6 +3846,7 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 
 		//give mana
 		sm.val = 999;
+		sm.absolute = true;
 
 		if(!h->hasSpellbook()) //hero doesn't have spellbook
 			giveHeroNewArtifact(h, VLC->arth->artifacts.at(0), ArtifactPosition::SPELLBOOK); //give spellbook
@@ -3931,542 +3974,6 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 	}
 }
 
-void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
-	int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack)
-{
-	const CSpell * spell = SpellID(spellID).toSpell();
-
-
-	//Helper local function that creates obstacle on given position. Obstacle type is inferred from spell type.
-	//It creates, sends and applies needed package.
-	auto placeObstacle = [&](BattleHex pos)
-	{
-		static int obstacleIdToGive = gs->curB->obstacles.size()
-									? (gs->curB->obstacles.back()->uniqueID+1)
-									: 0;
-
-		auto obstacle = make_shared<SpellCreatedObstacle>();
-		switch(spellID.toEnum()) // :/
-		{
-		case SpellID::QUICKSAND:
-			obstacle->obstacleType = CObstacleInstance::QUICKSAND;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::LAND_MINE:
-			obstacle->obstacleType = CObstacleInstance::LAND_MINE;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::FIRE_WALL:
-			obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		case SpellID::FORCE_FIELD:
-			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		default:
-			//this function cannot be used with spells that do not create obstacles
-			assert(0);
-		}
-
-		obstacle->pos = pos;
-		obstacle->casterSide = casterSide;
-		obstacle->ID = spellID;
-		obstacle->spellLevel = spellLvl;
-		obstacle->casterSpellPower = usedSpellPower;
-		obstacle->uniqueID = obstacleIdToGive++;
-
-		BattleObstaclePlaced bop;
-		bop.obstacle = obstacle;
-		sendAndApply(&bop);
-	};
-
-	BattleSpellCast sc;
-	sc.side = casterSide;
-	sc.id = spellID;
-	sc.skill = spellLvl;
-	sc.tile = destination;
-	sc.dmgToDisplay = 0;
-	sc.castedByHero = (bool)caster;
-	sc.attackerType = (stack ? stack->type->idNumber : CreatureID(CreatureID::NONE));
-	sc.manaGained = 0;
-	sc.spellCost = 0;
-
-	if (caster) //calculate spell cost
-	{
-		sc.spellCost = gs->curB->battleGetSpellCost(spell, caster);
-
-		if (secHero && mode == ECastingMode::HERO_CASTING) //handle mana channel
-		{
-			int manaChannel = 0;
-			for(CStack * stack : gs->curB->stacks) //TODO: shouldn't bonus system handle it somehow?
-			{
-				if (stack->owner == secHero->tempOwner)
-				{
-					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
-				}
-			}
-			sc.manaGained = (manaChannel * sc.spellCost) / 100;
-		}
-	}
-
-	//calculating affected creatures for all spells
-	//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 != gs->curB->battleStackIsImmune(caster,spell,mode,s);		
-	});
-
-	for (auto cre : attackedCres)
-	{
-		sc.affectedCres.insert (cre->ID);
-	}
-	
-	//checking if creatures resist
-	//resistance is applied only to negative spells
-	if(spell->isNegative())
-	{
-		for(auto s : attackedCres)
-		{
-			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
-			
-			if(gs->getRandomGenerator().nextInt(99) < prob)
-			{
-				sc.resisted.push_back(s->ID);
-			}
-		}
-	}
-
-	//calculating dmg to display
-	if (spellID == SpellID::DEATH_STARE || spellID == SpellID::ACID_BREATH_DAMAGE)
-	{
-		sc.dmgToDisplay = usedSpellPower;
-		if (spellID == SpellID::DEATH_STARE)
-			vstd::amin(sc.dmgToDisplay, (*attackedCres.begin())->count); //stack is already reduced after attack
-	}
-	StacksInjured si;
-
-	//applying effects
-
-	if (spell->isOffensiveSpell())
-	{
-		int spellDamage = 0;
-		if (stack && mode != ECastingMode::MAGIC_MIRROR)
-		{
-			int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
-			if (unitSpellPower)
-				sc.dmgToDisplay = spellDamage = stack->count * unitSpellPower; //TODO: handle immunities
-			else //Faerie Dragon
-			{
-				usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
-				sc.dmgToDisplay = 0;
-			}
-		}
-		int chainLightningModifier = 0;
-		for(auto & attackedCre : attackedCres)
-		{
-			if(vstd::contains(sc.resisted, (attackedCre)->ID)) //this creature resisted the spell
-				continue;
-
-			BattleStackAttacked bsa;
-			if ((destination > -1 && (attackedCre)->coversPos(destination)) || (spell->getLevelInfo(spellLvl).range == "X" || mode == ECastingMode::ENCHANTER_CASTING))
-				//display effect only upon primary target of area spell
-				//FIXME: if no stack is attacked, there is no animation and interface freezes
-			{
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim;
-			}
-			if (spellDamage)
-				bsa.damageAmount = spellDamage >> chainLightningModifier;
-			else
-				bsa.damageAmount = gs->curB->calculateSpellDmg(spell, caster, attackedCre, spellLvl, usedSpellPower) >> chainLightningModifier;
-
-			sc.dmgToDisplay += bsa.damageAmount;
-
-			bsa.stackAttacked = (attackedCre)->ID;
-			if (mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
-				bsa.attackerID = stack->ID;
-			else
-				bsa.attackerID = -1;
-			(attackedCre)->prepareAttacked(bsa, gs->getRandomGenerator());
-			si.stacks.push_back(bsa);
-
-			if (spellID == SpellID::CHAIN_LIGHTNING)
-				++chainLightningModifier;
-		}
-	}
-	
-	if (spell->hasEffects())
-	{
-			int stackSpellPower = 0;
-			if (stack && mode != ECastingMode::MAGIC_MIRROR)
-			{
-				stackSpellPower = stack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
-			}
-			SetStackEffect sse;
-			Bonus pseudoBonus;
-			pseudoBonus.sid = spellID;
-			pseudoBonus.val = spellLvl;
-			pseudoBonus.turnsRemain = gs->curB->calculateSpellDuration(spell, caster, stackSpellPower ? stackSpellPower : usedSpellPower);
-			CStack::stackEffectToFeature(sse.effect, pseudoBonus);
-			if (spellID == SpellID::SHIELD || spellID == SpellID::AIR_SHIELD)
-			{
-				sse.effect.back().val = (100 - sse.effect.back().val); //fix to original config: shield should display damage reduction
-			}
-			if (spellID == SpellID::BIND && stack)//bind
-			{
-				sse.effect.back().additionalInfo = stack->ID; //we need to know who casted Bind
-			}
-			const Bonus * bonus = nullptr;
-			if (caster)
-				bonus = caster->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, spellID));
-			//TODO does hero specialty should affects his stack casting spells?
-
-			si32 power = 0;
-			for(const CStack *affected : attackedCres)
-			{
-				if(vstd::contains(sc.resisted, affected->ID)) //this creature resisted the spell
-					continue;
-				sse.stacks.push_back(affected->ID);
-
-				//Apply hero specials - peculiar enchants
-				const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
-				if (bonus)
- 				{
- 	 				switch(bonus->additionalInfo)
- 	 				{
- 	 					case 0: //normal
-						{
- 	 						switch(tier)
- 	 						{
- 	 							case 1: case 2:
- 	 								power = 3;
- 	 							break;
- 	 							case 3: case 4:
- 	 								power = 2;
- 	 							break;
- 	 							case 5: case 6:
- 	 								power = 1;
- 	 							break;
- 	 						}
-							Bonus specialBonus(sse.effect.back());
-							specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
-							sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
-						}
- 	 					break;
- 	 					case 1: //only Coronius as yet
-						{
- 	 						power = std::max(5 - tier, 0);
-							Bonus specialBonus = CStack::featureGenerator(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK, power, pseudoBonus.turnsRemain);
-							specialBonus.sid = spellID;
-				 	 		sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
-						}
- 	 					break;
- 	 				}
- 				}
-				if (caster && caster->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, spellID)) //TODO: better handling of bonus percentages
- 	 			{
- 	 				int damagePercent = caster->level * caster->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, spellID.toEnum()) / tier;
-					Bonus specialBonus = CStack::featureGenerator(Bonus::CREATURE_DAMAGE, 0, damagePercent, pseudoBonus.turnsRemain);
-					specialBonus.valType = Bonus::PERCENT_TO_ALL;
-					specialBonus.sid = spellID;
- 	 				sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
- 	 			}
-			}
-
-			if(!sse.stacks.empty())
-				sendAndApply(&sse);
-
-	}
-	
-	if (spell->isRisingSpell() || spell->id == SpellID::CURE)
-	{
-			int hpGained = 0;
-			if (stack)
-			{
-				int unitSpellPower = stack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spellID.toEnum());
-				if (unitSpellPower)
-					hpGained = stack->count * unitSpellPower; //Archangel
-				else //Faerie Dragon-like effect - unused so far
-					usedSpellPower = stack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * stack->count / 100;
-			}
-			StacksHealedOrResurrected shr;
-			shr.lifeDrain = false;
-			shr.tentHealing = false;
-			for(auto & attackedCre : attackedCres)
-			{
-				StacksHealedOrResurrected::HealInfo hi;
-				hi.stackID = (attackedCre)->ID;
-				if (stack) //casted by creature
-				{
-					if (hpGained)
-					{
-						hi.healedHP = gs->curB->calculateHealedHP(hpGained, spell, attackedCre); //archangel
-					}
-					else
-						hi.healedHP = gs->curB->calculateHealedHP(spell, usedSpellPower, spellLvl, attackedCre); //any typical spell (commander's cure or animate dead)
-				}
-				else
-					hi.healedHP = gs->curB->calculateHealedHP(caster, spell, attackedCre, gs->curB->battleGetStackByID(selectedStack)); //Casted by hero
-				hi.lowLevelResurrection = spellLvl <= 1;
-				shr.healedStacks.push_back(hi);
-			}
-			if(!shr.healedStacks.empty())
-				sendAndApply(&shr);
-			if (spellID == SpellID::SACRIFICE) //remove victim
-			{
-				if (selectedStack == gs->curB->activeStack)
-				//set another active stack than the one removed, or bad things will happen
-				//TODO: make that part of BattleStacksRemoved? what about client update?
-				{
-					//makeStackDoNothing(gs->curB->getStack (selectedStack));
-
-					BattleSetActiveStack sas;
-
-					//std::vector<const CStack *> hlp;
-					//battleGetStackQueue(hlp, 1, selectedStack); //next after this one
-
-					//if(hlp.size())
-					//{
-					//	sas.stack = hlp[0]->ID;
-					//}
-					//else
-					//	complain ("No new stack to activate!");
-					sas.stack = gs->curB->getNextStack()->ID; //why the hell next stack has same ID as current?
-					sendAndApply(&sas);
-
-				}
-				BattleStacksRemoved bsr;
-				bsr.stackIDs.insert (selectedStack); //somehow it works for teleport?
-				sendAndApply(&bsr);
-			}
-	}
-
-	switch (spellID)
-	{
-	case SpellID::QUICKSAND:
-	case SpellID::LAND_MINE:
-		{
-			std::vector<BattleHex> availableTiles;
-			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
-			{
-				BattleHex hex = i;
-				if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) && !battleGetObstacleOnPos(hex, false))
-					availableTiles.push_back(hex);
-			}
-			boost::range::random_shuffle(availableTiles);
-
-			const int patchesForSkill[] = {4, 4, 6, 8};
-			const int patchesToPut = std::min<int>(patchesForSkill[spellLvl], availableTiles.size());
-
-			//land mines or quicksand patches are handled as spell created obstacles
-			for (int i = 0; i < patchesToPut; i++)
-				placeObstacle(availableTiles.at(i));
-		}
-
-		break;
-	case SpellID::FORCE_FIELD:
-		placeObstacle(destination);
-		break;
-	case SpellID::FIRE_WALL:
-		{
-			//fire wall is build from multiple obstacles - one fire piece for each affected hex
-			auto affectedHexes = spell->rangeInHexes(destination, spellLvl, casterSide);
-			for(BattleHex hex : affectedHexes)
-				placeObstacle(hex);
-		}
-		break;
-	case SpellID::TELEPORT:
-		{
-			BattleStackMoved bsm;
-			bsm.distance = -1;
-			bsm.stack = selectedStack;
-			std::vector<BattleHex> tiles;
-			tiles.push_back(destination);
-			bsm.tilesToMove = tiles;
-			bsm.teleporting = true;
-			sendAndApply(&bsm);
-			break;
-		}
-	case SpellID::SUMMON_FIRE_ELEMENTAL:
-	case SpellID::SUMMON_EARTH_ELEMENTAL:
-	case SpellID::SUMMON_WATER_ELEMENTAL:
-	case SpellID::SUMMON_AIR_ELEMENTAL:
-		{ //elemental summoning
-			CreatureID creID;
-			switch(spellID)
-			{
-				case SpellID::SUMMON_FIRE_ELEMENTAL:
-					creID = CreatureID::FIRE_ELEMENTAL;
-					break;
-				case SpellID::SUMMON_EARTH_ELEMENTAL:
-					creID = CreatureID::EARTH_ELEMENTAL;
-					break;
-				case SpellID::SUMMON_WATER_ELEMENTAL:
-					creID = CreatureID::WATER_ELEMENTAL;
-					break;
-				case SpellID::SUMMON_AIR_ELEMENTAL:
-					creID = CreatureID::AIR_ELEMENTAL;
-					break;
-			}
-
-			BattleStackAdded bsa;
-			bsa.creID = creID;
-			bsa.attacker = !(bool)casterSide;
-			bsa.summoned = true;
-			bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it
-
-			//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
-			int percentBonus = caster ? caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID.toEnum()) : 0;
-
-			bsa.amount = usedSpellPower
-				* SpellID(spellID).toSpell()->getPower(spellLvl)
-				* (100 + percentBonus) / 100.0; //new feature - percentage bonus
-			if(bsa.amount)
-				sendAndApply(&bsa);
-			else
-				complain("Summoning elementals didn't summon any!");
-		}
-		break;
-	case SpellID::CLONE:
-		{
-			const CStack * clonedStack = nullptr;
-			if (attackedCres.size())
-				clonedStack = *attackedCres.begin();
-			if (!clonedStack)
-			{
-				complain ("No target stack to clone!");
-				break;
-			}
-
-			BattleStackAdded bsa;
-			bsa.creID = clonedStack->type->idNumber;
-			bsa.attacker = !(bool)casterSide;
-			bsa.summoned = true;
-			bsa.pos = gs->curB->getAvaliableHex(bsa.creID, !(bool)casterSide); //TODO: unify it
-			bsa.amount = clonedStack->count;
-			sendAndApply (&bsa);
-
-			BattleSetStackProperty ssp;
-			ssp.stackID = gs->curB->stacks.back()->ID; //how to get recent stack?
-			ssp.which = BattleSetStackProperty::CLONED; //using enum values
-			ssp.val = 0;
-			ssp.absolute = 1;
-			sendAndApply(&ssp);
-		}
-		break;
-	case SpellID::REMOVE_OBSTACLE:
-		{
-			if(auto obstacleToRemove = battleGetObstacleOnPos(destination, false))
-			{
-				ObstaclesRemoved obr;
-				obr.obstacles.insert(obstacleToRemove->uniqueID);
-				sendAndApply(&obr);
-			}
-			else
-				complain("There's no obstacle to remove!");
-		}
-		break;
-	case SpellID::DEATH_STARE: //handled in a bit different way
-		{
-			for(auto & attackedCre : attackedCres)
-			{
-				BattleStackAttacked bsa;
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim; //from config\spell-Info.txt
-				bsa.damageAmount = usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
-				bsa.stackAttacked = (attackedCre)->ID;
-				bsa.attackerID = -1;
-				(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
-				si.stacks.push_back(bsa);
-			}
-		}
-		break;
-	case SpellID::ACID_BREATH_DAMAGE: //new effect, separate from acid breath defense reduction
-		{
-			for(auto & attackedCre : attackedCres) //no immunities
-			{
-				BattleStackAttacked bsa;
-				bsa.flags |= BattleStackAttacked::EFFECT;
-				bsa.effect = spell->mainEffectAnim;
-				bsa.damageAmount = usedSpellPower; //damage times the number of attackers
-				bsa.stackAttacked = (attackedCre)->ID;
-				bsa.attackerID = -1;
-				(attackedCre)->prepareAttacked(bsa, gameState()->getRandomGenerator());
-				si.stacks.push_back(bsa);
-			}
-		}
-		break;
-	}
-
-	sendAndApply(&sc);
-	if(!si.stacks.empty()) //after spellcast info shows
-		sendAndApply(&si);
-
-	if (mode == ECastingMode::CREATURE_ACTIVE_CASTING || mode == ECastingMode::ENCHANTER_CASTING) //reduce number of casts remaining
-	{
-		BattleSetStackProperty ssp;
-		ssp.stackID = stack->ID;
-		ssp.which = BattleSetStackProperty::CASTS;
-		ssp.val = -1;
-		ssp.absolute = false;
-		sendAndApply(&ssp);
-	}
-
-	//Magic Mirror effect
-	if (spell->isNegative() && mode != ECastingMode::MAGIC_MIRROR && spell->level && spell->getLevelInfo(0).range == "0") //it is actual spell and can be reflected to single target, no recurrence
-	{
-		for(auto & attackedCre : attackedCres)
-		{
-			int mirrorChance = (attackedCre)->valOfBonuses(Bonus::MAGIC_MIRROR);
-			if(mirrorChance > gs->getRandomGenerator().nextInt(99))
-			{
-				std::vector<CStack *> mirrorTargets;
-				std::vector<CStack *> & battleStacks = gs->curB->stacks;
-				for (auto & battleStack : battleStacks)
-				{
-					if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell
-					{
-						if (ESpellCastProblem::OK == gs->curB->battleStackIsImmune(nullptr, spell, ECastingMode::MAGIC_MIRROR, battleStack))
-							mirrorTargets.push_back(battleStack);
-					}
-				}
-				if (!mirrorTargets.empty())
-				{
-					int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, gs->getRandomGenerator()))->position;
-					handleSpellCasting(spellID, 0, targetHex, 1 - casterSide, (attackedCre)->owner, nullptr, (caster ? caster : nullptr), usedSpellPower, ECastingMode::MAGIC_MIRROR, (attackedCre));
-				}
-			}
-		}
-	}
-}
-
 bool CGameHandler::makeCustomAction( BattleAction &ba )
 {
 	switch(ba.actionType)
@@ -4489,52 +3996,49 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 				return false;
 			}
 
-			const CSpell *s = SpellID(ba.additionalInfo).toSpell();
-			if (s->mainEffectAnim > -1
-				|| s->id == SpellID::CLONE
-				|| s->id >= SpellID::SUMMON_FIRE_ELEMENTAL
-				|| s->id <= SpellID::SUMMON_AIR_ELEMENTAL
-				|| s->id <= SpellID::SUMMON_EARTH_ELEMENTAL
-				|| s->id <= SpellID::SUMMON_WATER_ELEMENTAL)
-				//TODO: special effects, like Clone
-			{
-				ui8 skill = h->getSpellSchoolLevel(s); //skill level
-
-				ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
-				if(escp != ESpellCastProblem::OK)
-				{
-                    logGlobal->warnStream() << "Spell cannot be cast!";
-                    logGlobal->warnStream() << "Problem : " << escp;
-					return false;
-				}
-
-				StartAction start_action(ba);
-				sendAndApply(&start_action); //start spell casting
-
-				handleSpellCasting (SpellID(ba.additionalInfo), skill, ba.destinationTile, ba.side, h->tempOwner,
-									h, secondHero, h->getPrimSkillLevel(PrimarySkill::SPELL_POWER),
-									ECastingMode::HERO_CASTING, nullptr, ba.selectedStack);
+			const CSpell * s = SpellID(ba.additionalInfo).toSpell();
+			
+			BattleSpellCastParameters parameters(gs->curB);
+			parameters.spellLvl =  h->getSpellSchoolLevel(s);
+			parameters.destination = ba.destinationTile;
+			parameters.casterSide = ba.side;
+			parameters.casterColor =  h->tempOwner;	
+			parameters.caster = h;
+			parameters.secHero = secondHero;
+			
+			parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);	
+			parameters.mode = ECastingMode::HERO_CASTING;
+			parameters.casterStack = nullptr;	
+			parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);			
 
-				sendAndApply(&end_action);
-				if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
-				{
-					battleMadeAction.setn(true);
-				}
-				checkForBattleEnd();
-				if(battleResult.get())
-				{
-					battleMadeAction.setn(true);
-					//battle will be ended by startBattle function
-					//endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
-				}
+			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
+			if(escp != ESpellCastProblem::OK)
+			{
+				logGlobal->warnStream() << "Spell cannot be cast!";
+				logGlobal->warnStream() << "Problem : " << escp;
+				return false;
+			}
 
-				return true;
+			StartAction start_action(ba);
+			sendAndApply(&start_action); //start spell casting
+			
+			s->battleCast(spellEnv, parameters);
+			
+			sendAndApply(&end_action);
+			if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
+			{
+				battleMadeAction.setn(true);
 			}
-			else
+			checkForBattleEnd();
+			if(battleResult.get())
 			{
-                logGlobal->warnStream() << "Spell " << s->name << " is not yet supported!";
-				return false;
+				battleMadeAction.setn(true);
+				//battle will be ended by startBattle function
+				//endBattle(gs->curB->tile, gs->curB->heroes[0], gs->curB->heroes[1]);
 			}
+
+			return true;
+
 		}
 	}
 	return false;
@@ -4645,11 +4149,22 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		{
 			auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
 			auto spellID = SpellID(bonus->subtype);
-			if (gs->curB->battleCanCastThisSpell(st->owner, SpellID(spellID).toSpell(), ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) //TODO: select another available?
-			{
-				int spellLeveL = bonus->val; //spell level
-				const CGHeroInstance * enemyHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
-				handleSpellCasting(spellID, spellLeveL, -1, side, st->owner, nullptr, enemyHero, 0, ECastingMode::ENCHANTER_CASTING, st);
+			const CSpell * spell = SpellID(spellID).toSpell();
+			if (gs->curB->battleCanCastThisSpell(st->owner, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) //TODO: select another available?
+			{
+				BattleSpellCastParameters parameters(gs->curB);
+				parameters.spellLvl = bonus->val;
+				parameters.destination = BattleHex::INVALID;
+				parameters.casterSide = side;
+				parameters.casterColor = st->owner;	
+				parameters.caster = nullptr;
+				parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
+				parameters.usedSpellPower = 0;	
+				parameters.mode = ECastingMode::ENCHANTER_CASTING;
+				parameters.casterStack = st;	
+				parameters.selectedStack = nullptr;
+				
+				spell->battleCast(spellEnv, parameters);				
 
 				BattleSetStackProperty ssp;
 				ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
@@ -4710,14 +4225,14 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
 
 		oneTimeObstacle = true;
 		effect = 82; //makes
-		damage = gs->curB->calculateSpellDmg(SpellID(SpellID::LAND_MINE).toSpell(), hero, curStack,
+		damage = SpellID(SpellID::LAND_MINE).toSpell()->calculateDamage(hero, curStack,
 											 spellObstacle->spellLevel, spellObstacle->casterSpellPower);
 		//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if casted by hero,
 		//if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk
 	}
 	else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL)
 	{
-		damage = gs->curB->calculateSpellDmg(SpellID(SpellID::FIRE_WALL).toSpell(), hero, curStack,
+		damage = SpellID(SpellID::FIRE_WALL).toSpell()->calculateDamage(hero, curStack,
 											 spellObstacle->spellLevel, spellObstacle->casterSpellPower);
 	}
 	else
@@ -5331,9 +4846,26 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 			if(gs->getRandomGenerator().nextInt(99) >= chance)
 				continue;
 
-			//casting //TODO: check if spell can be blocked or target is immune
+			//casting
 			if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
-				handleSpellCasting(spellID, spellLevel, destination, !attacker->attackerOwned, attacker->owner, nullptr, nullptr, 0, ECastingMode::AFTER_ATTACK_CASTING, attacker);
+			{
+				const CSpell * spell = SpellID(spellID).toSpell();
+
+				BattleSpellCastParameters parameters(gs->curB);
+				parameters.spellLvl = spellLevel;
+				parameters.destination = destination;
+				parameters.casterSide = !attacker->attackerOwned;
+				parameters.casterColor = attacker->owner;	
+				parameters.caster = nullptr;
+				parameters.secHero = nullptr;
+
+				parameters.usedSpellPower = 0;	
+				parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
+				parameters.casterStack = attacker;	
+				parameters.selectedStack = nullptr;
+
+				spell->battleCast(spellEnv, parameters);			
+			}
 		}
 	}
 }
@@ -5349,6 +4881,27 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	const CStack * attacker = gs->curB->battleGetStackByID(bat.stackAttacking);
 	if (!attacker) //could be already dead
 		return;
+	
+	auto cast = [=](SpellID spellID, int power)
+	{
+		const CSpell * spell = SpellID(spellID).toSpell();
+
+		BattleSpellCastParameters parameters(gs->curB);
+		parameters.spellLvl = 0;
+		parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
+		parameters.casterSide = !attacker->attackerOwned;
+		parameters.casterColor = attacker->owner;	
+		parameters.caster = nullptr;
+		parameters.secHero = nullptr;
+
+		parameters.usedSpellPower = power;	
+		parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
+		parameters.casterStack = attacker;	
+		parameters.selectedStack = nullptr;
+
+		spell->battleCast(this->spellEnv, parameters);		
+	};	
+	
 	attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
 
 	if(bat.bsa.at(0).newAmount <= 0)
@@ -5379,8 +4932,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 		if (staredCreatures)
 		{
 			if (bat.bsa.at(0).newAmount > 0) //TODO: death stare was not originally available for multiple-hex attacks, but...
-			handleSpellCasting(SpellID::DEATH_STARE, 0, gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position,
-				!attacker->attackerOwned, attacker->owner, nullptr, nullptr, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
+				cast(SpellID::DEATH_STARE, staredCreatures);
 		}
 	}
 
@@ -5393,9 +4945,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	}
 	if (acidDamage)
 	{
-		handleSpellCasting(SpellID::ACID_BREATH_DAMAGE, 0, gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position,
-				!attacker->attackerOwned, attacker->owner, nullptr, nullptr,
-				acidDamage * attacker->count, ECastingMode::AFTER_ATTACK_CASTING, attacker);
+		cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->count);
 	}
 }
 
@@ -5613,7 +5163,8 @@ bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int
 
 	SetMana sm;
 	sm.hid = h->id;
-	sm.val = h->mana - cost;
+	sm.absolute = false;
+	sm.val = -cost;
 	sendAndApply(&sm);
 
 	return true;
@@ -5853,10 +5404,28 @@ void CGameHandler::runBattle()
 		auto h = gs->curB->battleGetFightingHero(i);
 		if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
 		{
-			TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
+			TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));		
+			
+			BattleSpellCastParameters parameters(gs->curB);
+			parameters.spellLvl = 3;
+			parameters.destination = BattleHex::INVALID;
+			parameters.casterSide = 0;
+			parameters.casterColor = h->tempOwner;	
+			parameters.caster = nullptr;
+			parameters.secHero = gs->curB->battleGetFightingHero(1-i);
+			
+			
+			parameters.mode = ECastingMode::HERO_CASTING;
+			parameters.casterStack = nullptr;	
+			parameters.selectedStack = nullptr;	
+					
 			for (Bonus *b : *bl)
 			{
-				handleSpellCasting(SpellID(b->subtype), 3, -1, 0, h->tempOwner, nullptr, gs->curB->battleGetFightingHero(1-i), b->val, ECastingMode::HERO_CASTING, nullptr);
+				parameters.usedSpellPower = b->val;	
+				
+				const CSpell * spell = SpellID(b->subtype).toSpell();
+				
+				spell->battleCast(spellEnv, parameters);
 			}
 		}
 	}
@@ -6422,3 +5991,24 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 {
 	winnerHero = loserHero = nullptr;
 }
+
+
+ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
+{
+	
+}
+
+void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
+{
+	gh->sendAndApply(info);
+}
+
+CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const
+{
+	return gh->gameState()->getRandomGenerator();
+}
+
+void ServerSpellCastEnvironment::complain(const std::string& problem) const
+{
+	gh->complain(problem);
+}

+ 4 - 2
server/CGameHandler.h

@@ -36,6 +36,8 @@ struct NewStructures;
 class CGHeroInstance;
 class IMarket;
 
+class ServerSpellCastEnvironment;
+
 extern std::map<ui32, CFunctionList<void(ui32)> > callbacks; //question id => callback functions - for selection dialogs
 extern boost::mutex gsm;
 
@@ -201,8 +203,6 @@ public:
 	void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj);
 	bool makeBattleAction(BattleAction &ba);
 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
-	void handleSpellCasting(SpellID spellID, int spellLvl, BattleHex destination, ui8 casterSide, PlayerColor casterColor, const CGHeroInstance * caster, const CGHeroInstance * secHero,
-		int usedSpellPower, ECastingMode::ECastingMode mode, const CStack * stack, si32 selectedStack = -1);
 	bool makeCustomAction(BattleAction &ba);
 	void stackTurnTrigger(const CStack * stack);
 	void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
@@ -288,6 +288,8 @@ public:
 	friend class CVCMIServer;
 
 private:
+	ServerSpellCastEnvironment * spellEnv;
+
 	std::list<PlayerColor> generatePlayerTurnOrder() const;
 	void makeStackDoNothing(const CStack * next);
 	void getVictoryLossMessage(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult, InfoWindow & out) const;