ソースを参照

Split mechanics implementation

AlexVinS 10 年 前
コミット
1333f8e410

+ 12 - 4
lib/VCMI_lib.cbp

@@ -150,8 +150,6 @@
 		<Unit filename="CRandomGenerator.h" />
 		<Unit filename="CScriptingModule.h" />
 		<Unit filename="CSoundBase.h" />
-		<Unit filename="CSpellHandler.cpp" />
-		<Unit filename="CSpellHandler.h" />
 		<Unit filename="CStopWatch.h" />
 		<Unit filename="CThreadHelper.cpp" />
 		<Unit filename="CThreadHelper.h" />
@@ -185,8 +183,6 @@
 		<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" />
@@ -294,6 +290,18 @@
 		<Unit filename="rmg/CZoneGraphGenerator.h" />
 		<Unit filename="rmg/CZonePlacer.cpp" />
 		<Unit filename="rmg/CZonePlacer.h" />
+		<Unit filename="spells/AdventureSpellMechanics.cpp" />
+		<Unit filename="spells/AdventureSpellMechanics.h" />
+		<Unit filename="spells/BattleSpellMechanics.cpp" />
+		<Unit filename="spells/BattleSpellMechanics.h" />
+		<Unit filename="spells/CDefaultSpellMechanics.cpp" />
+		<Unit filename="spells/CDefaultSpellMechanics.h" />
+		<Unit filename="spells/CSpellHandler.cpp" />
+		<Unit filename="spells/CSpellHandler.h" />
+		<Unit filename="spells/CreatureSpellMechanics.cpp" />
+		<Unit filename="spells/CreatureSpellMechanics.h" />
+		<Unit filename="spells/ISpellMechanics.cpp" />
+		<Unit filename="spells/ISpellMechanics.h" />
 		<Unit filename="vcmi_endian.h" />
 		<Extensions>
 			<code_completion />

+ 242 - 0
lib/spells/AdventureSpellMechanics.cpp

@@ -11,3 +11,245 @@
 #include "StdInc.h"
 
 #include "AdventureSpellMechanics.h"
+
+#include "../CRandomGenerator.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../NetPacks.h"
+#include "../BattleState.h"
+#include "../CGameState.h"
+#include "../CGameInfoCallback.h"
+
+
+///AdventureBonusingMechanics
+bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
+	const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
+
+	GiveBonus gb;
+	gb.id = parameters.caster->id.getNum();
+	gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype);
+	env->sendAndApply(&gb);	
+	return true;
+}
+
+///SummonBoatMechanics
+bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	//try to find unoccupied boat to summon
+	const CGBoat * nearest = nullptr;
+	double dist = 0;
+	int3 summonPos = parameters.caster->bestLocation();
+	if(summonPos.x < 0)
+	{
+		env->complain("There is no water tile available!");
+		return false;
+	}	
+	
+	for(const CGObjectInstance * obj : env->getMap()->objects)
+	{
+		if(obj && obj->ID == Obj::BOAT)
+		{
+			const CGBoat *b = static_cast<const CGBoat*>(obj);
+			if(b->hero) 
+				continue; //we're looking for unoccupied boat
+
+			double nDist = b->pos.dist2d(parameters.caster->getPosition());
+			if(!nearest || nDist < dist) //it's first boat or closer than previous
+			{
+				nearest = b;
+				dist = nDist;
+			}
+		}						
+	}
+
+	if(nullptr != nearest) //we found boat to summon
+	{
+		ChangeObjPos cop;
+		cop.objid = nearest->id;
+		cop.nPos = summonPos + int3(1,0,0);;
+		cop.flags = 1;
+		env->sendAndApply(&cop);
+	}
+	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
+		env->sendAndApply(&iw);
+	}
+	else //create boat
+	{
+		NewObject no;
+		no.ID = Obj::BOAT;
+		no.subID = parameters.caster->getBoatType();
+		no.pos = summonPos + int3(1,0,0);;
+		env->sendAndApply(&no);
+	}	
+	return true;	
+}
+
+///ScuttleBoatMechanics
+bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+		
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Invalid dst tile for scuttle!");
+		return false;
+	}
+
+	//TODO: test range, visibility
+	const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
+	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
+	{
+		env->complain("There is no boat to scuttle!");
+		return false;
+	}
+		
+
+	RemoveObject ro;
+	ro.id = t->visitableObjects.back()->id;
+	env->sendAndApply(&ro);
+	return true;	
+}
+
+///DimensionDoorMechanics
+bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination is out of map!");
+		return false;
+	}	
+	
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
+
+	if(nullptr == dest)
+	{
+		env->complain("Destination tile doesn't exist!");
+		return false;
+	}
+	
+	if(nullptr == curr)
+	{
+		env->complain("Source tile doesn't exist!");
+		return false;
+	}	
+		
+	if(parameters.caster->movement <= 0)
+	{
+		env->complain("Hero needs movement points to cast Dimension Door!");
+		return false;
+	}
+	
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+			
+	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	GiveBonus gb;
+	gb.id = parameters.caster->id.getNum();
+	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
+	env->sendAndApply(&gb);
+
+	if(!dest->isClear(curr)) //wrong dest tile
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
+		env->sendAndApply(&iw);		
+	}
+	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
+	{
+		SetMovePoints smp;
+		smp.hid = parameters.caster->id;
+		smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
+		env->sendAndApply(&smp);
+	}
+	return true; 	
+}
+
+///TownPortalMechanics
+bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
+{
+	if (!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination tile not present!");
+		return false;
+	}	
+
+	TerrainTile tile = env->getMap()->getTile(parameters.pos);
+	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
+	{
+		env->complain("Town not found for Town Portal!");	
+		return false;
+	}		
+
+	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
+	if (town->tempOwner != parameters.caster->tempOwner)
+	{
+		env->complain("Can't teleport to another player!");
+		return false;
+	}			
+	
+	if (town->visitingHero)
+	{
+		env->complain("Can't teleport to occupied town!");
+		return false;
+	}
+	
+	if (parameters.caster->getSpellSchoolLevel(owner) < 2)
+	{
+		si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
+		ObjectInstanceID nearest = town->id; //nearest town's ID
+		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
+		{
+			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
+			if (currDist < dist)
+			{
+				nearest = currTown->id;
+				dist = currDist;
+			}
+		}
+		if (town->id != nearest)
+		{
+			env->complain("This hero can only teleport to nearest town!");
+			return false;
+		}
+			
+	}
+	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);	
+	return true;
+}

+ 44 - 0
lib/spells/AdventureSpellMechanics.h

@@ -11,3 +11,47 @@
  #pragma once
  
  #include "CDefaultSpellMechanics.h"
+
+
+//todo: make configurable
+class AdventureBonusingMechanics: public DefaultSpellMechanics 
+{
+public:	
+	AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+private:
+	Bonus::BonusType bonusTypeID;
+};
+
+class SummonBoatMechanics: public DefaultSpellMechanics 
+{
+public:
+	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+class ScuttleBoatMechanics: public DefaultSpellMechanics 
+{
+public:
+	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+class DimensionDoorMechanics: public DefaultSpellMechanics 
+{
+public:	
+	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+class TownPortalMechanics: public DefaultSpellMechanics 
+{
+public:	
+	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};

+ 406 - 0
lib/spells/BattleSpellMechanics.cpp

@@ -11,3 +11,409 @@
 #include "StdInc.h"
 
 #include "BattleSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.h"
+
+///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::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(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
+		});
+	}		
+}
+
+///DispellMechanics
+void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(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
+{
+	std::vector<BattleHex> ret;	
+	
+	//Special case - shape of obstacle depends on caster's side
+	//TODO make it possible through spell 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 = calculateHealedHP(caster, obj, nullptr);
+		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);	
+}
+	

+ 106 - 0
lib/spells/BattleSpellMechanics.h

@@ -11,3 +11,109 @@
  #pragma once
  
  #include "CDefaultSpellMechanics.h"
+
+
+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 applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;	
+};
+
+
+
+class DispellMechanics: public DefaultSpellMechanics
+{
+public:
+	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	
+	void applyBattle(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;		
+};

+ 691 - 0
lib/spells/CDefaultSpellMechanics.cpp

@@ -11,3 +11,694 @@
 #include "StdInc.h"
 
 #include "CDefaultSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.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;
+	}
+}
+
+
+///DefaultSpellMechanics
+void DefaultSpellMechanics::applyBattle(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);
+		});
+	}	
+}
+
+bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(!owner->isAdventureSpell())
+	{
+		env->complain("Attempt to cast non adventure spell in adventure mode");
+		return false;
+	}
+	
+	const CGHeroInstance * caster = parameters.caster;
+	const int cost = caster->getSpellCost(owner);
+	
+	if(!caster->canCastThisSpell(owner))
+	{
+		env->complain("Hero cannot cast this spell!");
+		return false;		
+	}
+
+	if(caster->mana < cost)
+	{
+		env->complain("Hero doesn't have enough spell points to cast this spell!");
+		return false;		
+	}
+
+	{
+		AdvmapSpellCast asc;
+		asc.caster = caster;
+		asc.spellID = owner->id;
+		env->sendAndApply(&asc);		
+	}
+	
+	if(applyAdventureEffects(env, parameters))
+	{
+		SetMana sm;
+		sm.hid = caster->id;
+		sm.absolute = false;
+		sm.val = -cost;
+		env->sendAndApply(&sm);
+		return true;
+	}
+	return false;
+}
+
+bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	//There is no generic algorithm of adventure cast
+	env->complain("Unimplemented adventure spell");
+	return false;		
+}
+
+
+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);
+	}	
+}
+
+ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
+{
+	int healedHealth;
+	
+	if(!owner->isHealingSpell())
+	{
+		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
+		return 0;
+	}		
+	
+	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
+	const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
+	
+	if (owner->id == SpellID::SACRIFICE && sacrificedStack)
+		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
+	else
+		healedHealth = spellPowerSkill * owner->power + levelPower; //???
+	healedHealth = owner->calculateBonus(healedHealth, caster, stack);
+	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));		
+}
+
+
+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 = 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);
+}

+ 40 - 0
lib/spells/CDefaultSpellMechanics.h

@@ -10,4 +10,44 @@
 
 #pragma once
 
+
 #include "ISpellMechanics.h"
+
+class BattleSpellCast;
+class StacksInjured;
+
+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 SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final; 
+	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
+	
+	void applyBattle(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;
+	
+	///calculate healed HP for all spells casted by hero
+	ui32 calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const;
+	
+	///actual adventure cast implementation
+	virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
+};

+ 83 - 0
lib/spells/CreatureSpellMechanics.cpp

@@ -11,3 +11,86 @@
 #include "StdInc.h"
 
 #include "CreatureSpellMechanics.h"
+
+#include "../NetPacks.h"
+#include "../BattleState.h"
+
+///AcidBreathDamageMechanics
+void AcidBreathDamageMechanics::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);
+	}	
+}
+
+///DeathStareMechanics
+void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	//calculating dmg to display
+	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	if(!ctx.attackedCres.empty())
+		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::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	DefaultSpellMechanics::applyBattle(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);	
+}

+ 26 - 0
lib/spells/CreatureSpellMechanics.h

@@ -11,3 +11,29 @@
  #pragma once
  
  #include "CDefaultSpellMechanics.h"
+
+class AcidBreathDamageMechanics: public DefaultSpellMechanics
+{
+public:
+	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;		
+};
+
+class DeathStareMechanics: public DefaultSpellMechanics
+{
+public:
+	DeathStareMechanics(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 applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
+	
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;	
+};

+ 7 - 1640
lib/spells/ISpellMechanics.cpp

@@ -1,5 +1,5 @@
 /*
- * SpellMechanics.cpp, part of VCMI engine
+ * ISpellMechanics.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -11,331 +11,12 @@
 #include "StdInc.h"
 #include "ISpellMechanics.h"
 
-#include "../CObstacleInstance.h"
-#include "../mapObjects/CGHeroInstance.h"
-#include "../BattleState.h"
-#include "../CRandomGenerator.h"
+#include "CDefaultSpellMechanics.h"
 
-#include "../NetPacks.h"
+#include "AdventureSpellMechanics.h"
+#include "BattleSpellMechanics.h"
+#include "CreatureSpellMechanics.h"
 
-#include "../mapping/CMap.h"
-#include "../CGameInfoCallback.h"
-#include "../CGameState.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 SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final; 
-	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
-	
-	void applyBattle(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;
-	
-	///calculate healed HP for all spells casted by hero
-	ui32 calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const;
-	
-	///actual adventure cast implementation
-	virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
-};
-
-///ADVENTURE SPELLS
-
-//todo: make configurable
-class AdventureBonusingMechanics: public DefaultSpellMechanics 
-{
-public:	
-	AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};	
-protected:
-	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
-private:
-	Bonus::BonusType bonusTypeID;
-};
-
-class SummonBoatMechanics: public DefaultSpellMechanics 
-{
-public:
-	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
-};
-
-class ScuttleBoatMechanics: public DefaultSpellMechanics 
-{
-public:
-	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
-protected:
-	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
-};
-
-class DimensionDoorMechanics: public DefaultSpellMechanics 
-{
-public:	
-	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-protected:
-	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
-};
-
-class TownPortalMechanics: public DefaultSpellMechanics 
-{
-public:	
-	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-protected:
-	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
-};
-
-
-///BATTLE SPELLS
-
-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 applyBattle(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 applyBattle(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 applyBattle(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):
@@ -349,7 +30,7 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 	switch (s->id)
 	{
 	case SpellID::ACID_BREATH_DAMAGE:
-		return new AcidBreathDamageMechnics(s);
+		return new AcidBreathDamageMechanics(s);
 	case SpellID::CHAIN_LIGHTNING:
 		return new ChainLightningMechanics(s);		
 	case SpellID::CLONE:
@@ -357,7 +38,7 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 	case SpellID::CURE:
 		return new CureMechanics(s);
 	case SpellID::DEATH_STARE:
-		return new DeathStareMechnics(s);			
+		return new DeathStareMechanics(s);			
 	case SpellID::DISPEL:
 		return new DispellMechanics(s);	
 	case SpellID::DISPEL_HELPFUL_SPELLS:
@@ -404,1317 +85,3 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 			return new DefaultSpellMechanics(s);		
 	}	
 }
-
-
-///DefaultSpellMechanics
-void DefaultSpellMechanics::applyBattle(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);
-		});
-	}	
-}
-
-bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
-{
-	if(!owner->isAdventureSpell())
-	{
-		env->complain("Attempt to cast non adventure spell in adventure mode");
-		return false;
-	}
-	
-	const CGHeroInstance * caster = parameters.caster;
-	const int cost = caster->getSpellCost(owner);
-	
-	if(!caster->canCastThisSpell(owner))
-	{
-		env->complain("Hero cannot cast this spell!");
-		return false;		
-	}
-
-	if(caster->mana < cost)
-	{
-		env->complain("Hero doesn't have enough spell points to cast this spell!");
-		return false;		
-	}
-
-	{
-		AdvmapSpellCast asc;
-		asc.caster = caster;
-		asc.spellID = owner->id;
-		env->sendAndApply(&asc);		
-	}
-	
-	if(applyAdventureEffects(env, parameters))
-	{
-		SetMana sm;
-		sm.hid = caster->id;
-		sm.absolute = false;
-		sm.val = -cost;
-		env->sendAndApply(&sm);
-		return true;
-	}
-	return false;
-}
-
-bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
-{
-	//There is no generic algorithm of adventure cast
-	env->complain("Unimplemented adventure spell");
-	return false;		
-}
-
-
-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);
-	}	
-}
-
-ui32 DefaultSpellMechanics::calculateHealedHP(const CGHeroInstance* caster, const CStack* stack, const CStack* sacrificedStack) const
-{
-	int healedHealth;
-	
-	if(!owner->isHealingSpell())
-	{
-		logGlobal->errorStream() << "calculateHealedHP called for nonhealing spell "<< owner->name;
-		return 0;
-	}		
-	
-	const int spellPowerSkill = caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER);
-	const int levelPower = owner->getPower(caster->getSpellSchoolLevel(owner));
-	
-	if (owner->id == SpellID::SACRIFICE && sacrificedStack)
-		healedHealth = (spellPowerSkill + sacrificedStack->MaxHealth() + levelPower) * sacrificedStack->count;
-	else
-		healedHealth = spellPowerSkill * owner->power + levelPower; //???
-	healedHealth = owner->calculateBonus(healedHealth, caster, stack);
-	return std::min<ui32>(healedHealth, stack->MaxHealth() - stack->firstHPleft + (owner->isRisingSpell() ? stack->baseAmount * stack->MaxHealth() : 0));		
-}
-
-
-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 = 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);
-}
-
-///ADVENTURE SPELLS
-
-///AdventureBonusingMechanics
-bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
-{
-	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
-	const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
-
-	GiveBonus gb;
-	gb.id = parameters.caster->id.getNum();
-	gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype);
-	env->sendAndApply(&gb);	
-	return true;
-}
-
-///SummonBoatMechanics
-bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
-{
-	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
-	//check if spell works at all
-	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->tempOwner;
-		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
-		iw.text.addReplacement(parameters.caster->name);
-		env->sendAndApply(&iw);
-		return true;
-	}
-
-	//try to find unoccupied boat to summon
-	const CGBoat * nearest = nullptr;
-	double dist = 0;
-	int3 summonPos = parameters.caster->bestLocation();
-	if(summonPos.x < 0)
-	{
-		env->complain("There is no water tile available!");
-		return false;
-	}	
-	
-	for(const CGObjectInstance * obj : env->getMap()->objects)
-	{
-		if(obj && obj->ID == Obj::BOAT)
-		{
-			const CGBoat *b = static_cast<const CGBoat*>(obj);
-			if(b->hero) 
-				continue; //we're looking for unoccupied boat
-
-			double nDist = b->pos.dist2d(parameters.caster->getPosition());
-			if(!nearest || nDist < dist) //it's first boat or closer than previous
-			{
-				nearest = b;
-				dist = nDist;
-			}
-		}						
-	}
-
-	if(nullptr != nearest) //we found boat to summon
-	{
-		ChangeObjPos cop;
-		cop.objid = nearest->id;
-		cop.nPos = summonPos + int3(1,0,0);;
-		cop.flags = 1;
-		env->sendAndApply(&cop);
-	}
-	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->tempOwner;
-		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
-		env->sendAndApply(&iw);
-	}
-	else //create boat
-	{
-		NewObject no;
-		no.ID = Obj::BOAT;
-		no.subID = parameters.caster->getBoatType();
-		no.pos = summonPos + int3(1,0,0);;
-		env->sendAndApply(&no);
-	}	
-	return true;	
-}
-
-///ScuttleBoatMechanics
-bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
-{
-	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-	//check if spell works at all
-	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->tempOwner;
-		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
-		iw.text.addReplacement(parameters.caster->name);
-		env->sendAndApply(&iw);
-		return true;
-	}
-		
-	if(!env->getMap()->isInTheMap(parameters.pos))
-	{
-		env->complain("Invalid dst tile for scuttle!");
-		return false;
-	}
-
-	//TODO: test range, visibility
-	const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
-	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
-	{
-		env->complain("There is no boat to scuttle!");
-		return false;
-	}
-		
-
-	RemoveObject ro;
-	ro.id = t->visitableObjects.back()->id;
-	env->sendAndApply(&ro);
-	return true;	
-}
-
-///DimensionDoorMechanics
-bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
-{
-	if(!env->getMap()->isInTheMap(parameters.pos))
-	{
-		env->complain("Destination is out of map!");
-		return false;
-	}	
-	
-	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
-	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
-
-	if(nullptr == dest)
-	{
-		env->complain("Destination tile doesn't exist!");
-		return false;
-	}
-	
-	if(nullptr == curr)
-	{
-		env->complain("Source tile doesn't exist!");
-		return false;
-	}	
-		
-	if(parameters.caster->movement <= 0)
-	{
-		env->complain("Hero needs movement points to cast Dimension Door!");
-		return false;
-	}
-	
-	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
-			
-	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->tempOwner;
-		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
-		iw.text.addReplacement(parameters.caster->name);
-		env->sendAndApply(&iw);
-		return true;
-	}
-
-	GiveBonus gb;
-	gb.id = parameters.caster->id.getNum();
-	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
-	env->sendAndApply(&gb);
-
-	if(!dest->isClear(curr)) //wrong dest tile
-	{
-		InfoWindow iw;
-		iw.player = parameters.caster->tempOwner;
-		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
-		env->sendAndApply(&iw);		
-	}
-	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
-	{
-		SetMovePoints smp;
-		smp.hid = parameters.caster->id;
-		smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
-		env->sendAndApply(&smp);
-	}
-	return true; 	
-}
-
-///TownPortalMechanics
-bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
-{
-	if (!env->getMap()->isInTheMap(parameters.pos))
-	{
-		env->complain("Destination tile not present!");
-		return false;
-	}	
-
-	TerrainTile tile = env->getMap()->getTile(parameters.pos);
-	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
-	{
-		env->complain("Town not found for Town Portal!");	
-		return false;
-	}		
-
-	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
-	if (town->tempOwner != parameters.caster->tempOwner)
-	{
-		env->complain("Can't teleport to another player!");
-		return false;
-	}			
-	
-	if (town->visitingHero)
-	{
-		env->complain("Can't teleport to occupied town!");
-		return false;
-	}
-	
-	if (parameters.caster->getSpellSchoolLevel(owner) < 2)
-	{
-		si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
-		ObjectInstanceID nearest = town->id; //nearest town's ID
-		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
-		{
-			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
-			if (currDist < dist)
-			{
-				nearest = currTown->id;
-				dist = currDist;
-			}
-		}
-		if (town->id != nearest)
-		{
-			env->complain("This hero can only teleport to nearest town!");
-			return false;
-		}
-			
-	}
-	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);	
-	return true;
-}
-
-
-///BATTLE SPELLS
-
-///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::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::applyBattle(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;
-	
-	if(!ctx.attackedCres.empty())
-		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::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::applyBattle(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::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
-{
-	DefaultSpellMechanics::applyBattle(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 = calculateHealedHP(caster, obj, nullptr);
-		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);	
-}
-	
-

+ 1 - 1
lib/spells/ISpellMechanics.h

@@ -1,5 +1,5 @@
 /*
- * SpellMechanics.h, part of VCMI engine
+ * ISpellMechanics.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *