浏览代码

Merge branch 'SpellsRefactoring7' into develop

AlexVinS 10 年之前
父节点
当前提交
61af4ee89a

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -531,7 +531,7 @@ std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) co
 {
 	if(spell->getTargetType() == CSpell::NO_TARGET)
 	{
-		//Spell can be casted anywhere, all hexes are potentially considerable.
+		//Spell can be cast anywhere, all hexes are potentially considerable.
 		std::vector<BattleHex> ret;
 
 		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)

+ 14 - 7
client/CPlayerInterface.cpp

@@ -772,7 +772,8 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 {
 	THREAD_CREATED_BY_CLIENT;
 	logGlobal->traceStream() << "Awaiting command for " << stack->nodeName();
-
+	auto stackId = stack->ID;
+	auto stackName = stack->nodeName();
 	if(autofightingAI)
 	{
 		if(isAutoFightOn)
@@ -806,17 +807,23 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	while(!b->givenCommand->data)
 	{
 		b->givenCommand->cond.wait(lock);
-		if(!battleInt) //batle ended while we were waiting for movement (eg. because of spell)
+		if(!battleInt) //battle ended while we were waiting for movement (eg. because of spell)
 			throw boost::thread_interrupted(); //will shut the thread peacefully
 	}
 
 	//tidy up
 	BattleAction ret = *(b->givenCommand->data);
-	delete b->givenCommand->data;
-	b->givenCommand->data = nullptr;
-
-	//return command
-	logGlobal->traceStream() << "Giving command for " << stack->nodeName();
+	vstd::clear_pointer(b->givenCommand->data);
+	
+	if(ret.actionType == Battle::CANCEL)
+	{
+		if(stackId != ret.stackNumber)
+			logGlobal->error("Not current active stack action canceled");
+		logGlobal->traceStream() << "Canceled command for " << stackName;			
+	}
+	else
+		logGlobal->traceStream() << "Giving command for " << stackName;
+		
 	return ret;
 }
 

+ 6 - 3
client/Client.cpp

@@ -140,9 +140,12 @@ void CClient::waitForMoveAndSend(PlayerColor color)
 		setThreadName("CClient::waitForMoveAndSend");
 		assert(vstd::contains(battleints, color));
 		BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
-		logNetwork->traceStream() << "Send battle action to server: " << ba;
-		MakeAction temp_action(ba);
-		sendRequest(&temp_action, color);
+		if(ba.actionType != Battle::CANCEL)
+		{
+			logNetwork->traceStream() << "Send battle action to server: " << ba;
+			MakeAction temp_action(ba);
+			sendRequest(&temp_action, color);			
+		}
 		return;
 	}
 	catch(boost::thread_interrupted&)

+ 1 - 1
client/NetPacksClient.cpp

@@ -750,7 +750,7 @@ void CatapultAttack::applyCl( CClient *cl )
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleCatapultAttacked, *this);
 }
 
-void BattleStacksRemoved::applyCl( CClient *cl )
+void BattleStacksRemoved::applyFirstCl(CClient * cl)
 {
 	//inform interfaces about removed stacks
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksRemoved, *this);

+ 27 - 12
client/battle/CBattleInterface.cpp

@@ -1005,11 +1005,22 @@ void CBattleInterface::newStack(const CStack * stack)
 
 void CBattleInterface::stackRemoved(int stackID)
 {
+	if(activeStack != nullptr)
+	{
+		if(activeStack->ID == stackID)
+		{
+			BattleAction * action = new BattleAction();
+			action->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
+			action->actionType = Battle::CANCEL;
+			action->stackNumber = activeStack->ID;
+			givenCommand->setn(action);
+			setActiveStack(nullptr);
+		}
+	}
+	
 	delete creAnims[stackID];
 	creAnims.erase(stackID);
 	creDir.erase(stackID);
-	//FIXME: what if currently removed stack is active one (Sacrifice)?
-
 	redrawBackgroundWithHexes(activeStack);
 	queue->update();
 }
@@ -1091,10 +1102,6 @@ void CBattleInterface::newRoundFirst( int round )
 void CBattleInterface::newRound(int number)
 {
 	console->addText(CGI->generaltexth->allTexts[412]);
-
-	//unlock spellbook
-	//bSpell->block(!curInt->cb->battleCanCastSpell());
-	//don't unlock spellbook - this should be done when we have axctive creature
 }
 
 void CBattleInterface::giveCommand(Battle::ActionType action, BattleHex tile, ui32 stackID, si32 additional, si32 selected)
@@ -1379,7 +1386,7 @@ void CBattleInterface::castThisSpell(SpellID spellID)
 	sp = spellID.toSpell();
 	spellSelMode = ANY_LOCATION;
 
-	const CSpell::TargetInfo ti = sp->getTargetInfo(castingHero->getSpellSchoolLevel(sp));
+	const CSpell::TargetInfo ti(sp, castingHero->getSpellSchoolLevel(sp));
 
 	if(ti.massive || ti.type == CSpell::NO_TARGET)
 		spellSelMode = NO_LOCATION;
@@ -2103,9 +2110,9 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			{
 				ui8 skill = 0;
 				if (creatureCasting)
-					skill = sactive->getSpellSchoolLevel(SpellID(SpellID::TELEPORT).toSpell());
+					skill = sactive->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
 				else
-					skill = getActiveHero()->getSpellSchoolLevel (CGI->spellh->objects[spellToCast->additionalInfo]);
+					skill = getActiveHero()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell());
 				//TODO: explicitely save power, skill
 				if (curInt->cb->battleCanTeleportTo(selectedStack, myNumber, skill))
 					legalAction = true;
@@ -2470,11 +2477,19 @@ bool CBattleInterface::isCastingPossibleHere (const CStack * sactive, const CSta
 
 	if (sp)
 	{
-		if (creatureCasting)
-			isCastingPossible = (curInt->cb->battleCanCreatureCastThisSpell (sp, myNumber) == ESpellCastProblem::OK);
+		const ISpellCaster * caster = creatureCasting ? dynamic_cast<const ISpellCaster *>(sactive) : dynamic_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
+		if(caster == nullptr)
+		{
+			isCastingPossible = false;//just in case
+		}
 		else
-			isCastingPossible = (curInt->cb->battleCanCastThisSpell (sp, myNumber) == ESpellCastProblem::OK);
+		{
+			const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
+			isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK);
+		}
 	}
+	else
+		isCastingPossible = false;
 	if(!myNumber.isAvailable() && !shere) //empty tile outside battlefield (or in the unavailable border column)
 			isCastingPossible = false;
 

+ 12 - 3
config/artifacts.json

@@ -1313,7 +1313,8 @@
 				"subtype" : 35,
 				"type" : "SPELL_IMMUNITY",
 				"val" : 0,
-				"valueType" : "BASE_NUMBER"
+				"valueType" : "BASE_NUMBER",
+				"addInfo" : 1
 			}
 		],
 		"index" : 92,
@@ -1324,10 +1325,17 @@
 		"bonuses" : [
 			{
 				"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
+				"subtype" : 0,
 				"val" : 0,
 				"valueType" : "BASE_NUMBER",
 				"propagator": "BATTLE_WIDE"
-			}
+			},
+			{
+				"type" : "NEGATE_ALL_NATURAL_IMMUNITIES",
+				"subtype" : 1,
+				"val" : 0,
+				"valueType" : "BASE_NUMBER"
+			}			
 		],
 		"index" : 93,
 		"type" : ["HERO"]
@@ -1812,7 +1820,8 @@
 				"subtype" : "spell.armageddon",
 				"type" : "SPELL_IMMUNITY",
 				"val" : 0,
-				"valueType" : "BASE_NUMBER"
+				"valueType" : "BASE_NUMBER",
+				"addInfo" : 1
 			},
 			{
 				"subtype" : "primSkill.attack",

+ 8 - 4
config/creatures/dungeon.json

@@ -9,12 +9,14 @@
 			"blindImmunity" : 
 			{
 				"type" : "SPELL_IMMUNITY",
-				"subtype" : "spell.blind"
+				"subtype" : "spell.blind",
+				"addInfo" : 1
 			},
 			"petrifyImmunity" : 
 			{
 				"type" : "SPELL_IMMUNITY",
-				"subtype" : "spell.stoneGaze"
+				"subtype" : "spell.stoneGaze",
+				"addInfo" : 1
 			}
 		},
 		"upgrades": ["infernalTroglodyte"],
@@ -42,12 +44,14 @@
 			"blindImmunity" : 
 			{
 				"type" : "SPELL_IMMUNITY",
-				"subtype" : "spell.blind"
+				"subtype" : "spell.blind",
+				"addInfo" : 1
 			},
 			"petrifyImmunity" : 
 			{
 				"type" : "SPELL_IMMUNITY",
-				"subtype" : "spell.stoneGaze"
+				"subtype" : "spell.stoneGaze",
+				"addInfo" : 1
 			}
 		},
 		"graphics" :

+ 1 - 1
config/spells/timed.json

@@ -564,7 +564,7 @@
 						"subtype" : "primSkill.defence",
 						"val" : -3,
 						"valueType" : "ADDITIVE_VALUE",
-						"duration" : "N_TURNS"
+						"duration" : "PERMANENT"
 					}
 				}
 			},

+ 41 - 1
lib/BattleState.cpp

@@ -1170,6 +1170,17 @@ bool CStack::canBeHealed() const
 		&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
 }
 
+ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const
+{
+	if(!resurrect && !alive())
+	{
+		logGlobal->warnStream() <<"Attempt to heal corpse detected.";
+		return 0;
+	}
+
+	return std::min<ui32>(toHeal, MaxHealth() - firstHPleft + (resurrect ? (baseAmount - count) * MaxHealth() : 0));
+}
+
 ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) const
 {
 	int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->id));
@@ -1181,10 +1192,39 @@ ui8 CStack::getSpellSchoolLevel(const CSpell * spell, int * outSelectedSchool) c
 
 ui32 CStack::getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const
 {
-	//stacks does not have spellpower etc. (yet?)
+	//stacks does not have sorcery-like bonuses (yet?)
 	return base;
 }
 
+int CStack::getEffectLevel(const CSpell * spell) const
+{
+	return getSpellSchoolLevel(spell);
+}
+
+int CStack::getEffectPower(const CSpell * spell) const
+{
+	return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * count / 100;
+}
+
+int CStack::getEnchantPower(const CSpell * spell) const
+{
+	int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
+	if(res<=0)
+		res = 3;//default for creatures
+	return res;
+}
+
+int CStack::getEffectValue(const CSpell * spell) const
+{
+	return valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->id.toEnum()) * count;
+}
+
+const PlayerColor CStack::getOwner() const
+{
+	return owner;
+}
+
+
 bool CMP_stack::operator()( const CStack* a, const CStack* b )
 {
 	switch(phase)

+ 16 - 0
lib/BattleState.h

@@ -203,6 +203,8 @@ public:
 	bool waited(int turn = 0) const;
 	bool canMove(int turn = 0) const; //if stack can move
 	bool canBeHealed() const; //for first aid tent - only harmed stacks that are not war machines
+	///returns actual heal value based on internal state
+	ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const;
 	ui32 level() const;
 	si32 magicResistance() const override; //include aura of resistance
 	static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);
@@ -233,6 +235,20 @@ public:
 	///ISpellCaster
 	ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
 	ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
+
+	///default spell school level for effect calculation
+	int getEffectLevel(const CSpell * spell) const override;
+
+	///default spell-power for damage/heal calculation
+	int getEffectPower(const CSpell * spell) const override;
+
+	///default spell-power for timed effects duration
+	int getEnchantPower(const CSpell * spell) const override;
+
+	///damage/heal override(ignores spell configuration, effect level and effect power)
+	int getEffectValue(const CSpell * spell) const override;
+
+	const PlayerColor getOwner() const override;
 	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 38 - 42
lib/CBattleCallback.cpp

@@ -335,6 +335,11 @@ int CBattleInfoEssentials::battleCastSpells(ui8 side) const
 	return getBattle()->sides[side].castSpellsCount;
 }
 
+const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
+{
+	return getBattle();
+}
+
 ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
@@ -1587,9 +1592,15 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
 	return attackableBattleHexes;
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode ) const
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	if(caster == nullptr)
+	{
+		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster.";
+		return ESpellCastProblem::INVALID;
+	}
+	const PlayerColor player = caster->getOwner();
 	const ui8 side = playerToSide(player);
 	if(!battleDoWeKnowAbout(side))
 		return ESpellCastProblem::INVALID;
@@ -1598,16 +1609,11 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	if(genProblem != ESpellCastProblem::OK)
 		return genProblem;
 
-	//Casting hero, set only if he is an actual caster.
-	const CGHeroInstance *castingHero = mode == ECastingMode::HERO_CASTING
-										? battleGetFightingHero(side)
-										: nullptr;
-
-
 	switch(mode)
 	{
 	case ECastingMode::HERO_CASTING:
 		{
+			const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
 			assert(castingHero);
 			if(!castingHero->canCastThisSpell(spell))
 				return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
@@ -1617,11 +1623,10 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		break;
 	}
 
-
 	if(!spell->combatSpell)
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 
-	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCasted(this, player);
+	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, player);
 	
 	if(specificProblem != ESpellCastProblem::OK)
 		return specificProblem;	
@@ -1634,7 +1639,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
 		for(auto stack : stacks)
 		{
-			if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack))
+			if(ESpellCastProblem::OK == spell->isImmuneByStack(caster, stack))
 			{
 				allStacksImmune = false;
 				break;
@@ -1645,7 +1650,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
 
-	if(battleMaxSpellLevel() < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
+	if(battleMaxSpellLevel(side) < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
 		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
 
 	//checking if there exists an appropriate target
@@ -1654,8 +1659,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	case CSpell::CREATURE:
 		if(mode == ECastingMode::HERO_CASTING)
 		{
-			const CGHeroInstance * caster = battleGetFightingHero(side);
-			const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
+			const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
 			bool targetExists = false;
 
             for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
@@ -1710,7 +1714,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor
 	case CSpell::CREATURE:
 		{
 			const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
-			const CSpell::TargetInfo ti = spell->getTargetInfo(caster->getSpellSchoolLevel(spell));
+			const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
 			
 			for(const CStack * stack : battleAliveStacks())
 			{
@@ -1772,10 +1776,16 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst
 	return ret - manaReduction + manaIncrease;
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere( PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest ) const
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(player, spell, mode);
+	if(caster == nullptr)
+	{
+		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
+		return ESpellCastProblem::INVALID;
+	}	
+	const PlayerColor player = caster->getOwner();
+	ESpellCastProblem::ESpellCastProblem moreGeneralProblem = battleCanCastThisSpell(caster, spell, mode);
 	if(moreGeneralProblem != ESpellCastProblem::OK)
 		return moreGeneralProblem;
 
@@ -1826,8 +1836,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	{
 		if(!deadStack && !aliveStack)
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-		if(spell->id == SpellID::ANIMATE_DEAD  &&  deadStack  &&  !deadStack->hasBonusOfType(Bonus::UNDEAD))
-			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 		if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
@@ -1840,11 +1848,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		if(spell->isPositive() && aliveStack->owner != player)
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
-
-	const CGHeroInstance * caster = nullptr;
-	if (mode == ECastingMode::HERO_CASTING)
-		caster = battleGetFightingHero(playerToSide(player));
-	
 	return spell->isImmuneAt(this, caster, mode, dest);
 }
 
@@ -1927,7 +1930,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 	{
 		if (subject->hasBonusFrom(Bonus::SPELL_EFFECT, spellID)
 			//TODO: this ability has special limitations
-			|| battleCanCastThisSpellHere(subject->owner, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
+			|| battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
 			continue;
 
 		switch (spellID)
@@ -2060,12 +2063,14 @@ int CBattleInfoCallback::battleGetSurrenderCost(PlayerColor Player) const
 	return ret;
 }
 
-si8 CBattleInfoCallback::battleMaxSpellLevel() const
+si8 CBattleInfoCallback::battleMaxSpellLevel(ui8 side) const
 {
-	const CBonusSystemNode *node = nullptr;
-	if(const CGHeroInstance *h =  battleGetFightingHero(battleGetMySide()))
+	const IBonusBearer *node = nullptr;
+	if(const CGHeroInstance * h = battleGetFightingHero(side))
 		node = h;
-	//TODO else use battle node
+	else
+		node = getBattleNode();
+
 	if(!node)
 		return GameConstants::SPELL_LEVELS;
 
@@ -2163,21 +2168,12 @@ ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpe
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
 	ASSERT_IF_CALLED_WITH_PLAYER
-	return CBattleInfoCallback::battleCanCastThisSpell(*player, spell, ECastingMode::HERO_CASTING);
-}
 
-ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	ASSERT_IF_CALLED_WITH_PLAYER
-	return battleCanCastThisSpellHere(*player, spell, ECastingMode::HERO_CASTING, destination);
-}
-
-ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const
-{
-	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
-	ASSERT_IF_CALLED_WITH_PLAYER
-	return battleCanCastThisSpellHere(*player, spell, ECastingMode::CREATURE_ACTIVE_CASTING, destination);
+	const ISpellCaster * hero = battleGetMyHero();
+	if(hero == nullptr)
+		return ESpellCastProblem::INVALID;
+	else
+		return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING);
 }
 
 bool CPlayerBattleCallback::battleCanFlee() const

+ 8 - 8
lib/CBattleCallback.h

@@ -15,6 +15,7 @@ class CGameState;
 class CGTownInstance;
 class CGHeroInstance;
 class CStack;
+class ISpellCaster;
 class CSpell;
 struct BattleInfo;
 struct CObstacleInstance;
@@ -158,6 +159,7 @@ class DLL_LINKAGE CBattleInfoEssentials : public virtual CCallbackBase
 {
 protected:
 	bool battleDoWeKnowAbout(ui8 side) const;
+	const IBonusBearer * getBattleNode() const;
 public:
 	enum EStackOwnership
 	{
@@ -189,7 +191,7 @@ public:
 	ui8 playerToSide(PlayerColor player) const;
 	ui8 battleGetSiegeLevel() const; //returns 0 when there is no siege, 1 if fort, 2 is citadel, 3 is castle
 	bool battleHasHero(ui8 side) const;
-	int battleCastSpells(ui8 side) const; //how many spells has given side casted
+	int battleCastSpells(ui8 side) const; //how many spells has given side cast
 	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
 	const CArmedInstance * battleGetArmyObject(ui8 side) const; 
 	InfoAboutHero battleGetHeroInfo(ui8 side) const;
@@ -276,12 +278,11 @@ public:
 	std::vector<BattleHex> getAttackableBattleHexes() const;
 
 	//*** MAGIC 
-	si8 battleMaxSpellLevel() const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
+	si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
 	ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
 	ESpellCastProblem::ESpellCastProblem battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(PlayerColor player, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
-	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
 	std::vector<BattleHex> battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const;
 
 	SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const;
@@ -324,9 +325,8 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
 public:
 	bool battleCanFlee() const; //returns true if caller can flee from the battle
 	TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be casted (and returns problem description)
-	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, BattleHex destination) const; //if hero can cast spell here
-	ESpellCastProblem::ESpellCastProblem battleCanCreatureCastThisSpell(const CSpell * spell, BattleHex destination) const; //determines if creature can cast a spell here
+	ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description)
+
 	int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
 
 	bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.

+ 8 - 0
lib/CCreatureHandler.cpp

@@ -853,34 +853,42 @@ void CCreatureHandler::loadStackExp(Bonus & b, BonusList & bl, CLegacyConfigPars
 			case 'B': //Blind
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::BLIND;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'H': //Hypnotize
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::HYPNOTIZE;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'I': //Implosion
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::IMPLOSION;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'K': //Berserk
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::BERSERK;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'M': //Meteor Shower
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::METEOR_SHOWER;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'N': //dispell beneficial spells
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::DISPEL_HELPFUL_SPELLS;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'R': //Armageddon
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::ARMAGEDDON;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case 'S': //Slow
 				b.type = Bonus::SPELL_IMMUNITY;
 				b.subtype = SpellID::SLOW;
+				b.additionalInfo = 0;//normal immunity
 				break;
 			case '6':
 			case '7':

+ 1 - 1
lib/CGameInfoCallback.cpp

@@ -173,7 +173,7 @@ int CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstan
 	ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
 
 	if (hero) //we see hero's spellbook
-		return sp->calculateDamage(hero, nullptr, hero->getSpellSchoolLevel(sp), hero->getPrimSkillLevel(PrimarySkill::SPELL_POWER));
+		return sp->calculateDamage(hero, nullptr, hero->getEffectLevel(sp), hero->getEffectPower(sp));
 	else
 		return 0; //mage guild
 }

+ 3 - 1
lib/GameConstants.h

@@ -414,7 +414,8 @@ namespace ECastingMode
 	{
 		HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack
 		MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING,
-		SPELL_LIKE_ATTACK	
+		SPELL_LIKE_ATTACK, 
+		PASSIVE_CASTING//f.e. opening battle spells
 	};
 }
 
@@ -699,6 +700,7 @@ namespace Battle
 {
 	enum ActionType
 	{
+		CANCEL = -3,
 		END_TACTIC_PHASE = -2,
 		INVALID = -1,
 		NO_ACTION = 0,

+ 2 - 2
lib/HeroBonus.h

@@ -207,9 +207,9 @@ public:
 	BONUS_NAME(RECEPTIVE) /*accepts friendly spells even with immunity*/\
 	BONUS_NAME(DIRECT_DAMAGE_IMMUNITY) /*direct damage spells, that is*/\
 	BONUS_NAME(CASTS) /*how many times creature can cast activated spell*/ \
-	BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection casted by units, subtype - spell id */\
+	BONUS_NAME(SPECIFIC_SPELL_POWER) /* value used for Thunderbolt and Resurrection cast by units, subtype - spell id */\
 	BONUS_NAME(CREATURE_SPELL_POWER) /* value per unit, divided by 100 (so faerie Dragons have 800)*/ \
-	BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells casted by creature */ \
+	BONUS_NAME(CREATURE_ENCHANT_POWER) /* total duration of spells cast by creature */ \
 	BONUS_NAME(ENCHANTED) /* permanently enchanted with spell subID of level = val, if val > 3 then spell is mass and has level of val-3*/ \
 	BONUS_NAME(REBIRTH) /* val - percent of life restored, subtype = 0 - regular, 1 - at least one unit (sacred Phoenix) */\
 	BONUS_NAME(ADDITIONAL_UNITS) /*val of units with id = subtype will be added to hero's army at the beginning of battle */\

+ 3 - 3
lib/NetPacks.h

@@ -1323,7 +1323,7 @@ struct StacksHealedOrResurrected : public CPackForClient //3013
 	{
 		ui32 stackID;
 		ui32 healedHP;
-		ui8 lowLevelResurrection; //in case this stack is resurrected by this heal, it will be marked as SUMMONED //TODO: replace with separate counter
+		bool lowLevelResurrection; //in case this stack is resurrected by this heal, it will be marked as SUMMONED //TODO: replace with separate counter
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 			h & stackID & healedHP & lowLevelResurrection;
@@ -1502,7 +1502,7 @@ struct BattleSpellCast : public CPackForClient//3009
 	std::vector<CustomEffect> customEffects;
 	std::set<ui32> affectedCres; //ids of creatures affected by this spell, generally used if spell does not set any effect (like dispel or cure)
 	si32 casterStack;// -1 if not cated by creature, >=0 caster stack ID
-	bool castByHero; //if true - spell has been casted by hero, otherwise by a creature
+	bool castByHero; //if true - spell has been cast by hero, otherwise by a creature
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
@@ -1604,7 +1604,7 @@ struct BattleStacksRemoved : public CPackForClient //3016
 	BattleStacksRemoved(){type = 3016;}
 
 	DLL_LINKAGE void applyGs(CGameState *gs);
-	void applyCl(CClient *cl);
+	void applyFirstCl(CClient *cl);//inform client before stack objects are destroyed
 
 	std::set<ui32> stackIDs; //IDs of removed stacks
 

+ 8 - 12
lib/NetPacksLib.cpp

@@ -1136,7 +1136,7 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
 		}
 		case Bonus::POISON:
 		{
-			Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, 71)
+			Bonus * b = st->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT, SpellID::POISON)
 											.And(Selector::type(Bonus::STACK_HEALTH)));
 			if (b)
 				b->val = val;
@@ -1459,21 +1459,17 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs( CGameState *gs )
 			}
 		}
 		vstd::amin(changedStack->firstHPleft, changedStack->MaxHealth());
-		//removal of negative effects
 		if(resurrected)
 		{
-			//removing all features from negative spells
-			const BonusList tmpFeatures = changedStack->getBonusList();
-			//changedStack->bonuses.clear();
-
-			for(Bonus *b : tmpFeatures)
+			//removing all effects from negative spells
+			auto selector = [](const Bonus * b)
 			{
 				const CSpell *s = b->sourceSpell();
-				if(s && s->isNegative())
-				{
-					changedStack->removeBonus(b);
-				}
-			}
+				//Special case: DISRUPTING_RAY is "immune" to dispell
+				//Other even PERMANENT effects can be removed
+				return (s != nullptr) && s->isNegative() && (s->id != SpellID::DISRUPTING_RAY);
+			};
+			changedStack->popBonuses(selector);
 		}
 	}
 }

+ 27 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -902,6 +902,33 @@ ui32 CGHeroInstance::getSpellBonus(const CSpell * spell, ui32 base, const CStack
 	return base;	
 }
 
+int CGHeroInstance::getEffectLevel(const CSpell * spell) const
+{
+	if(hasBonusOfType(Bonus::MAXED_SPELL, spell->id))
+		return 3;//todo: recheck specialty from where this bonus is. possible bug
+	else
+		return getSpellSchoolLevel(spell);		
+}
+
+int CGHeroInstance::getEffectPower(const CSpell * spell) const
+{
+	return getPrimSkillLevel(PrimarySkill::SPELL_POWER);
+}
+
+int CGHeroInstance::getEnchantPower(const CSpell * spell) const
+{
+	return getPrimSkillLevel(PrimarySkill::SPELL_POWER) + valOfBonuses(Bonus::SPELL_DURATION);	
+}
+
+int CGHeroInstance::getEffectValue(const CSpell * spell) const
+{
+	return 0;
+}
+
+const PlayerColor CGHeroInstance::getOwner() const
+{
+	return tempOwner;
+}
 
 bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
 {

+ 14 - 0
lib/mapObjects/CGHeroInstance.h

@@ -210,6 +210,20 @@ public:
 	ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const override;
 	ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const override;
 	
+	///default spell school level for effect calculation
+	int getEffectLevel(const CSpell * spell) const override;
+
+	///default spell-power for damage/heal calculation
+	int getEffectPower(const CSpell * spell) const override;
+
+	///default spell-power for timed effects duration
+	int getEnchantPower(const CSpell * spell) const override;
+
+	///damage/heal override(ignores spell configuration, effect level and effect power)
+	int getEffectValue(const CSpell * spell) const override;
+	
+	const PlayerColor getOwner() const override;
+	
 	void deserializationFix();
 
 	void initObj() override;

+ 202 - 77
lib/spells/BattleSpellMechanics.cpp

@@ -13,6 +13,37 @@
 
 #include "../NetPacks.h"
 #include "../BattleState.h"
+#include "../mapObjects/CGHeroInstance.h"
+
+///HealingSpellMechanics
+void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	EHealLevel healLevel = getHealLevel(parameters.effectLevel);
+	int hpGained = calculateHealedHP(env, parameters, ctx);
+	StacksHealedOrResurrected shr;
+	shr.lifeDrain = false;
+	shr.tentHealing = false;
+
+	const bool resurrect = (healLevel != EHealLevel::HEAL);
+	for(auto & attackedCre : ctx.attackedCres)
+	{
+		StacksHealedOrResurrected::HealInfo hi;
+		hi.stackID = (attackedCre)->ID;		
+		int stackHPgained = parameters.caster->getSpellBonus(owner, hpGained, attackedCre);
+		hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect);
+		hi.lowLevelResurrection = (healLevel == EHealLevel::RESURRECT);
+		shr.healedStacks.push_back(hi);
+	}
+	if(!shr.healedStacks.empty())
+		env->sendAndApply(&shr);
+}
+
+int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
+{
+	if(parameters.effectValue != 0)
+		return parameters.effectValue; //Archangel
+	return owner->calculateRawEffectValue(parameters.effectLevel, parameters.effectPower); //???
+}
 
 ///AntimagicMechanics
 void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
@@ -67,7 +98,7 @@ std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargeti
 }
 
 ///CloneMechanics
-void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	const CStack * clonedStack = nullptr;
 	if(ctx.attackedCres.size())
@@ -101,23 +132,21 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battle
 	env->sendAndApply(&ssp);	
 }
 
-ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	//can't clone already cloned creature
 	if(vstd::contains(obj->state, EBattleStackState::CLONED))
 		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	if(obj->cloneID != -1)
 		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);
+		schoolLevel = caster->getEffectLevel(owner);
 	}
 	else
 	{
-		schoolLevel = 3;
+		schoolLevel = 3;//todo: remove
 	}
 
 	if(schoolLevel < 3)
@@ -146,6 +175,11 @@ void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * pac
 	});
 }
 
+HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) const
+{
+	return EHealLevel::HEAL;
+}
+
 ///DispellMechanics
 void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
@@ -153,21 +187,35 @@ void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast *
 	doDispell(battle, packet, Selector::sourceType(Bonus::SPELL_EFFECT));
 }
 
-ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
-	//DISPELL ignores all immunities, so do not call default
-	std::stringstream cachingStr;
-	cachingStr << "source_" << Bonus::SPELL_EFFECT;
-
-	if(obj->hasBonus(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str()))
 	{
-		return ESpellCastProblem::OK;
+		//just in case
+		if(!obj->alive())
+			return ESpellCastProblem::WRONG_SPELL_TARGET;			
 	}
+	//DISPELL ignores all immunities, except specific absolute immunity 
+	{
+		//SPELL_IMMUNITY absolute case
+		std::stringstream cachingStr;
+		cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << owner->id.toEnum() << "addInfo_1";
+		if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, owner->id.toEnum(), 1), cachingStr.str()))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}
+	{
+		std::stringstream cachingStr;
+		cachingStr << "source_" << Bonus::SPELL_EFFECT;
 
+		if(obj->hasBonus(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str()))
+		{
+			return ESpellCastProblem::OK;
+		}		
+	}
 	return ESpellCastProblem::WRONG_SPELL_TARGET;
+	//any other immunities are ignored - do not execute default algorithm
 }
 
-void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	DefaultSpellMechanics::applyBattleEffects(env, parameters, ctx);
 
@@ -190,15 +238,15 @@ void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, Batt
 }
 
 ///EarthquakeMechanics
-void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	if(nullptr == parameters.cb->town)
+	if(nullptr == parameters.cb->battleGetDefendedTown())
 	{
 		env->complain("EarthquakeMechanics: not town siege");
 		return;
 	}
 
-	if(CGTownInstance::NONE == parameters.cb->town->fortLevel())
+	if(CGTownInstance::NONE == parameters.cb->battleGetDefendedTown()->fortLevel())
 	{
 		env->complain("EarthquakeMechanics: town has no fort");
 		return;
@@ -277,7 +325,7 @@ void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, B
 	env->sendAndApply(&ca);
 }
 
-ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
 	if(nullptr == cb->battleGetDefendedTown())
 	{
@@ -288,8 +336,9 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBat
 	{
 		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
-
-	if(owner->getTargetInfo(0).smart) //TODO: use real spell level
+	
+	CSpell::TargetInfo ti(owner, 0);//TODO: use real spell level
+	if(ti.smart)
 	{
 		//if spell targeting is smart, then only attacker can use it
 		if(cb->playerToSide(player) != 0)
@@ -300,15 +349,15 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCasted(const CBat
 }
 
 ///HypnotizeMechanics
-ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
-	if(nullptr != caster) //do not resist hypnotize casted after attack, for example
+	//todo: maybe do not resist on passive cast
+	if(nullptr != caster) 
 	{
 		//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 = caster->getSpellBonus(owner, caster->getPrimSkillLevel(PrimarySkill::SPELL_POWER)
-			* owner->power + owner->getPower(caster->getSpellSchoolLevel(owner)), obj);
+		ui64 maxHealth = caster->getSpellBonus(owner, owner->calculateRawEffectValue(caster->getEffectLevel(owner), caster->getEffectPower(owner)), obj);
 		if (subjectHealth > maxHealth)
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
@@ -316,9 +365,9 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C
 }
 
 ///ObstacleMechanics
-void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	auto placeObstacle = [&, this](BattleHex pos)
+	auto placeObstacle = [&, this](const BattleHex & pos)
 	{
 		static int obstacleIdToGive =  parameters.cb->obstacles.size()
 									? (parameters.cb->obstacles.back()->uniqueID+1)
@@ -355,8 +404,8 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
 		obstacle->pos = pos;
 		obstacle->casterSide = parameters.casterSide;
 		obstacle->ID = owner->id;
-		obstacle->spellLevel = parameters.spellLvl;
-		obstacle->casterSpellPower = parameters.usedSpellPower;
+		obstacle->spellLevel = parameters.effectLevel;
+		obstacle->casterSpellPower = parameters.effectPower;
 		obstacle->uniqueID = obstacleIdToGive++;
 
 		BattleObstaclePlaced bop;
@@ -364,6 +413,8 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
 		env->sendAndApply(&bop);
 	};
 
+	const BattleHex destination = parameters.getFirstDestinationHex();
+
 	switch(owner->id)
 	{
 	case SpellID::QUICKSAND:
@@ -388,12 +439,22 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat
 
 		break;
 	case SpellID::FORCE_FIELD:
-		placeObstacle(parameters.destination);
+		if(!destination.isValid())
+		{
+			env->complain("Invalid destination for FORCE_FIELD");
+			return;
+		}
+		placeObstacle(destination);
 		break;
 	case SpellID::FIRE_WALL:
 		{
+			if(!destination.isValid())
+			{
+				env->complain("Invalid destination for FIRE_WALL");
+				return;
+			}
 			//fire wall is build from multiple obstacles - one fire piece for each affected hex
-			auto affectedHexes = owner->rangeInHexes(parameters.destination, parameters.spellLvl, parameters.casterSide);
+			auto affectedHexes = owner->rangeInHexes(destination, parameters.spellLvl, parameters.casterSide);
 			for(BattleHex hex : affectedHexes)
 				placeObstacle(hex);
 		}
@@ -442,9 +503,9 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
 }
 
 ///RemoveObstacleMechanics
-void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.destination, false))
+	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.getFirstDestinationHex(), false))
 	{
 		ObstaclesRemoved obr;
 		obr.obstacles.insert(obstacleToRemove->uniqueID);
@@ -454,20 +515,34 @@ void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * en
 		env->complain("There's no obstacle to remove!");
 }
 
-///SpecialRisingSpellMechanics
-ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
+{
+	//this may be even distinct class
+	if((effectLevel <= 1) && (owner->id == SpellID::RESURRECTION))
+		return EHealLevel::RESURRECT;
+	
+	return EHealLevel::TRUE_RESURRECT;
+}
+
+///SacrificeMechanics
+ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
 	// for sacrifice we have to check for 2 targets (one dead to resurrect and one living to destroy)
 
 	bool targetExists = false;
 	bool targetToSacrificeExists = false;
+	
+	const CGHeroInstance * caster = nullptr; //todo: use ISpellCaster
+	
+	if(cb->battleHasHero(cb->playerToSide(player)))
+		caster = cb->battleGetFightingHero(cb->playerToSide(player));
 
 	for(const CStack * stack : cb->battleGetAllStacks())
 	{
 		//using isImmuneBy directly as this mechanics does not have overridden immunity check
 		//therefore we do not need to check caster and casting mode
 		//TODO: check that we really should check immunity for both stacks
-		ESpellCastProblem::ESpellCastProblem res = owner->isImmuneBy(stack);
+		ESpellCastProblem::ESpellCastProblem res = owner->internalIsImmune(caster, stack);
 		const bool immune =  ESpellCastProblem::OK != res && ESpellCastProblem::NOT_DECIDED != res;
 		const bool casterStack = stack->owner == player;
 
@@ -489,39 +564,57 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCasted(const CBatt
 }
 
 
-void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, const 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?
+	const CStack * victim = nullptr;
+	if(parameters.destinations.size() == 2)
 	{
-		//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);
-
+		victim = parameters.destinations[1].stackValue;
 	}
+	else
+	{
+		//todo: remove and report error
+		victim = parameters.selectedStack;
+	}
+	if(nullptr == victim)
+	{
+		env->complain("SacrificeMechanics: No stack to sacrifice");
+		return;
+	}
+	//resurrect target after basic checks
+	RisingSpellMechanics::applyBattleEffects(env, parameters, ctx);
+	//it is safe to remove even active stack
 	BattleStacksRemoved bsr;
-	bsr.stackIDs.insert(parameters.selectedStack->ID); //somehow it works for teleport?
+	bsr.stackIDs.insert(victim->ID);
 	env->sendAndApply(&bsr);
 }
 
+int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const
+{
+	int res = 0;
+	const CStack * victim = nullptr;
+
+	if(parameters.destinations.size() == 2)
+	{
+		victim = parameters.destinations[1].stackValue;
+	}
+	else
+	{
+		//todo: remove and report error
+		victim = parameters.selectedStack;
+	}
+	if(nullptr == victim)
+	{
+		env->complain("SacrificeMechanics: No stack to sacrifice");
+		return 0;
+	}
+
+	res = (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
+	return res;
+}
 
 ///SpecialRisingSpellMechanics
-ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const ISpellCaster * 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
@@ -529,18 +622,19 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 	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;
-	}
+	//FIXME: Archangels can cast immune stack and this should be applied for them and not hero
+//	if(caster) 
+//	{
+//		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
-ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
 	const ui8 side = cb->playerToSide(player);
 
@@ -559,7 +653,7 @@ ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCasted(const CBattleI
 	return ESpellCastProblem::OK;
 }
 
-void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	BattleStackAdded bsa;
 	bsa.creID = creatureToSummon;
@@ -570,7 +664,7 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
 	//TODO stack casting -> probably power will be zero; set the proper number of creatures manually
 	int percentBonus = parameters.casterHero ? parameters.casterHero->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0;
 
-	bsa.amount = parameters.usedSpellPower
+	bsa.amount = parameters.effectPower
 		* owner->getPower(parameters.spellLvl)
 		* (100 + percentBonus) / 100.0; //new feature - percentage bonus
 	if(bsa.amount)
@@ -580,15 +674,46 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl
 }
 
 ///TeleportMechanics
-void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, const 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);
+	//todo: check legal teleport
+	if(parameters.destinations.size() == 2)
+	{
+		//first destination creature to move
+		const CStack * target = parameters.destinations[0].stackValue;
+		if(nullptr == target)
+		{
+			env->complain("TeleportMechanics: no stack to teleport");
+			return;
+		}
+		//second destination hex to move to
+		const BattleHex destination = parameters.destinations[1].hexValue;
+		if(!destination.isValid())
+		{
+			env->complain("TeleportMechanics: invalid teleport destination");
+			return;			
+		}
+		BattleStackMoved bsm;
+		bsm.distance = -1;
+		bsm.stack = target->ID;
+		std::vector<BattleHex> tiles;
+		tiles.push_back(destination);
+		bsm.tilesToMove = tiles;
+		bsm.teleporting = true;
+		env->sendAndApply(&bsm);		
+	}
+	else
+	{
+		//todo: remove and report error
+		BattleStackMoved bsm;
+		bsm.distance = -1;
+		bsm.stack = parameters.selectedStack->ID;
+		std::vector<BattleHex> tiles;
+		tiles.push_back(parameters.getFirstDestinationHex());
+		bsm.tilesToMove = tiles;
+		bsm.teleporting = true;
+		env->sendAndApply(&bsm);
+	}		
 }
 
+

+ 40 - 20
lib/spells/BattleSpellMechanics.h

@@ -12,6 +12,23 @@
 
 #include "CDefaultSpellMechanics.h"
 
+class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
+{
+public:
+	enum class EHealLevel 
+	{
+		HEAL,
+		RESURRECT,
+		TRUE_RESURRECT
+	};
+
+	HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
+	virtual EHealLevel getHealLevel(int effectLevel) const = 0;
+};
+
 class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
 {
 public:
@@ -31,44 +48,46 @@ class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
 {
 public:
 	CloneMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
-class DLL_LINKAGE CureMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE CureMechanics : public HealingSpellMechanics
 {
 public:
-	CureMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	CureMechanics(CSpell * s): HealingSpellMechanics(s){};
 
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
+	
+	EHealLevel getHealLevel(int effectLevel) const override final;
 };
 
 class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
 {
 public:
 	DispellMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 class DLL_LINKAGE EarthquakeMechanics : public DefaultSpellMechanics
 {
 public:
 	EarthquakeMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 class DLL_LINKAGE HypnotizeMechanics : public DefaultSpellMechanics
 {
 public:
 	HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
 
 class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
@@ -77,7 +96,7 @@ public:
 	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
 
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 class DLL_LINKAGE WallMechanics : public ObstacleMechanics
@@ -92,15 +111,15 @@ class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
 public:
 	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 ///all rising spells
-class DLL_LINKAGE RisingSpellMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
 {
 public:
-	RisingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
-
+	RisingSpellMechanics(CSpell * s): HealingSpellMechanics(s){};
+	EHealLevel getHealLevel(int effectLevel) const override;
 };
 
 class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
@@ -108,9 +127,10 @@ class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
 public:
 	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
 
-	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
 };
 
 ///all rising spells but SACRIFICE
@@ -118,7 +138,7 @@ class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
 {
 public:
 	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
 
 class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
@@ -126,9 +146,9 @@ class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
 public:
 	SummonMechanics(CSpell * s, CreatureID cre): DefaultSpellMechanics(s), creatureToSummon(cre){};
 
-	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 private:
 	CreatureID creatureToSummon;
 };
@@ -138,5 +158,5 @@ class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
 public:
 	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };

+ 161 - 173
lib/spells/CDefaultSpellMechanics.cpp

@@ -139,7 +139,7 @@ void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCa
 			//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;
-
+			//No exceptions, ANY spell can be countered, even if it can`t be dispelled.
 			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
 		});
 	}
@@ -223,29 +223,35 @@ ESpellCastResult DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnv
 
 void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
 {
+	logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
+	
+	if(nullptr == parameters.caster)
+	{
+		env->complain("No spell-caster provided.");
+		return;		
+	}
+	
 	BattleSpellCast sc;
-	sc.side = parameters.casterSide;
-	sc.id = owner->id;
-	sc.skill = parameters.spellLvl;
-	sc.tile = parameters.destination;
-	sc.dmgToDisplay = 0;
-	sc.castByHero = nullptr != parameters.casterHero;
-	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
-	sc.manaGained = 0;
-
+	prepareBattleCast(parameters, sc);
+	
+	//check it there is opponent hero
+	const ui8 otherSide = 1-parameters.casterSide;
+	const CGHeroInstance * otherHero = nullptr;
+	if(parameters.cb->battleHasHero(otherSide))
+		otherHero = parameters.cb->battleGetFightingHero(otherSide);
 	int spellCost = 0;
 
 	//calculate spell cost
-	if(parameters.casterHero)
+	if(parameters.mode == ECastingMode::HERO_CASTING)
 	{
 		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
 
-		if(parameters.secHero && parameters.mode == ECastingMode::HERO_CASTING) //handle mana channel
-		{
+		if(nullptr != otherHero) //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)
+				if(stack->owner == otherHero->tempOwner)
 				{
 					vstd::amax(manaChannel, stack->valOfBonuses(Bonus::MANA_CHANNELING));
 				}
@@ -253,53 +259,36 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 			sc.manaGained = (manaChannel * spellCost) / 100;
 		}
 	}
+	logGlobal->debugStream() << "spellCost: " << spellCost;
 
 	//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.casterHero);
+	auto creatures = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.casterColor, parameters.spellLvl, parameters.getFirstDestinationHex(), parameters.caster);
 	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
 
+	logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
+
 	std::vector <const CStack*> reflected;//for magic mirror
-	
-	//checking if creatures resist
-	//resistance/reflection is applied only to negative spells
-	if(owner->isNegative())
+	//checking if creatures resist	
+	handleResistance(env, attackedCres, sc);
+	//it is actual spell and can be reflected to single target, no recurrence
+	const bool tryMagicMirror = owner->isNegative() && owner->level && owner->getLevelInfo(0).range == "0";	
+	if(tryMagicMirror)
 	{
-		//it is actual spell and can be reflected to single target, no recurrence
-		const bool tryMagicMirror = parameters.mode != ECastingMode::MAGIC_MIRROR && owner->level && owner->getLevelInfo(0).range == "0";
-		std::vector <const CStack*> resisted;
 		for(auto s : attackedCres)
 		{
-			//magic resistance
-			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
-
-			if(env->getRandomGenerator().nextInt(99) < prob)
-			{
-				resisted.push_back(s);
-			}
-			//magic mirror
-			if(tryMagicMirror)
-			{
-				const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
-				if(env->getRandomGenerator().nextInt(99) < mirrorChance)
-					reflected.push_back(s);
-			}
+			const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
+			if(env->getRandomGenerator().nextInt(99) < mirrorChance)
+				reflected.push_back(s);
 		}
 
-		vstd::erase_if(attackedCres, [&resisted, reflected](const CStack * s)
+		vstd::erase_if(attackedCres, [&reflected](const CStack * s)
 		{
-			return vstd::contains(resisted, s) || vstd::contains(reflected, s);
+			return vstd::contains(reflected, s);
 		});
 
-		for(auto s : resisted)
-		{
-			BattleSpellCast::CustomEffect effect;
-			effect.effect = 78;
-			effect.stack = s->ID;
-			sc.customEffects.push_back(effect);
-		}
 		for(auto s : reflected)
 		{
 			BattleSpellCast::CustomEffect effect;
@@ -316,13 +305,12 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 
 	StacksInjured si;
 	SpellCastContext ctx(attackedCres, sc, si);
-
 	applyBattleEffects(env, parameters, ctx);
 
 	env->sendAndApply(&sc);
 
 	//spend mana
-	if(parameters.casterHero)
+	if(parameters.mode == ECastingMode::HERO_CASTING)
 	{
 		SetMana sm;
 		sm.absolute = false;
@@ -334,9 +322,9 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 
 		if(sc.manaGained > 0)
 		{
-			assert(parameters.secHero);
+			assert(otherHero);
 
-			sm.hid = parameters.secHero->id;
+			sm.hid = otherHero->id;
 			sm.val = sc.manaGained;
 			env->sendAndApply(&sm);
 		}
@@ -358,7 +346,7 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		ssp.absolute = false;
 		env->sendAndApply(&ssp);
 	}
-
+	logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
 	//Magic Mirror effect
 	for(auto & attackedCre : reflected)
 	{
@@ -373,18 +361,17 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		{
 			int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
 
-			BattleSpellCastParameters mirrorParameters = parameters;
+			BattleSpellCastParameters mirrorParameters(parameters.cb, attackedCre, owner);
 			mirrorParameters.spellLvl = 0;
-			mirrorParameters.casterSide = 1-parameters.casterSide;
-			mirrorParameters.casterColor = (attackedCre)->owner;
-			mirrorParameters.casterHero = nullptr;
-			mirrorParameters.destination = targetHex;
-			mirrorParameters.secHero = parameters.casterHero;
+			mirrorParameters.aimToHex(targetHex);
 			mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
-			mirrorParameters.casterStack = (attackedCre);
 			mirrorParameters.selectedStack = nullptr;
-
-			battleCast(env, mirrorParameters);
+			mirrorParameters.spellLvl = parameters.spellLvl;
+			mirrorParameters.effectLevel = parameters.effectLevel;
+			mirrorParameters.effectPower = parameters.effectPower;
+			mirrorParameters.effectValue = parameters.effectValue;
+			mirrorParameters.enchantPower = parameters.enchantPower;
+			castMagicMirror(env, mirrorParameters);
 		}
 	}
 }
@@ -486,74 +473,21 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & log
 	}	
 }
 
-int DefaultSpellMechanics::calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const
-{
-	if(caster == nullptr)
-	{
-		if (!usedSpellPower)
-			return 3; //default duration of all creature spells
-		else
-			return usedSpellPower; //use creature spell power			
-	}
-	else
-		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 = caster->getSpellBonus(owner, healedHealth, 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
+void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	int effectLevel = parameters.spellLvl;
-	{
-		//MAXED_SPELL bonus.
-		if(parameters.casterHero != nullptr)
-			if(parameters.casterHero->hasBonusOfType(Bonus::MAXED_SPELL, owner->id))
-				effectLevel = 3;
-	}
-
 	//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 spellDamage = parameters.effectValue;
+		
 		int chainLightningModifier = 0;
 		for(auto & attackedCre : ctx.attackedCres)
 		{
 			BattleStackAttacked bsa;
-			if(spellDamage)
-				bsa.damageAmount = spellDamage >> chainLightningModifier;
+			if(spellDamage != 0)
+				bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, spellDamage) >> chainLightningModifier;
 			else
-				bsa.damageAmount =  owner->calculateDamage(parameters.casterHero, attackedCre, effectLevel, parameters.usedSpellPower) >> chainLightningModifier;
+				bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.effectLevel, parameters.effectPower) >> chainLightningModifier;
 
 			ctx.sc.dmgToDisplay += bsa.damageAmount;
 
@@ -572,19 +506,14 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 
 	if(owner->hasEffects())
 	{
-		int stackSpellPower = 0;
-		if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR)
-		{
-			stackSpellPower =  parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);
-		}
 		SetStackEffect sse;
 		//get default spell duration (spell power with bonuses for heroes)
-		int duration = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower);		
+		int duration = parameters.enchantPower;
 		//generate actual stack bonuses
 		{
 			int maxDuration = 0;
 			std::vector<Bonus> tmp;
-			owner->getEffects(tmp, effectLevel);
+			owner->getEffects(tmp, parameters.effectLevel);
 			for(Bonus& b : tmp)
 			{
 				//use configured duration if present
@@ -664,49 +593,7 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 		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(effectLevel);
-					hi.healedHP = std::min<ui32>(healedHealth, attackedCre->MaxHealth() - attackedCre->firstHPleft + (resurrect ? attackedCre->baseAmount * attackedCre->MaxHealth() : 0));
-				}
-			}
-			else
-				hi.healedHP = calculateHealedHP(parameters.casterHero, attackedCre, parameters.selectedStack); //Casted by hero
-			hi.lowLevelResurrection = (effectLevel <= 1) && (owner->id != SpellID::ANIMATE_DEAD);
-			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
@@ -849,24 +736,125 @@ std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargeting
 	return attackedCres;
 }
 
-ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
 	//no problems by default, this method is for spell-specific problems	
 	return ESpellCastProblem::OK;
 }
 
-
-ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	//by default use general algorithm
-	return owner->isImmuneBy(obj);
+	return owner->internalIsImmune(caster, obj);
 }
 
 void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const
 {
+	auto localSelector = [](const Bonus * bonus)
+	{
+		const CSpell * sourceSpell = bonus->sourceSpell();
+		if(sourceSpell != nullptr)
+		{
+			//Special case: DISRUPTING_RAY is "immune" to dispell
+			//Other even PERMANENT effects can be removed (f.e. BIND)						
+			if(sourceSpell->id == SpellID::DISRUPTING_RAY)
+				return false;
+		}
+		return true;
+	};
 	for(auto stackID : packet->affectedCres)
 	{
 		CStack *s = battle->getStack(stackID);
-		s->popBonuses(selector);
+		s->popBonuses(CSelector(localSelector).And(selector));
+	}	
+}
+
+void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, BattleSpellCastParameters& parameters) const
+{
+	logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
+	if(parameters.mode != ECastingMode::MAGIC_MIRROR)
+	{
+		env->complain("MagicMirror: invalid mode");
+		return;
+	}
+	BattleHex destination = parameters.getFirstDestinationHex();
+	if(!destination.isValid())
+	{
+		env->complain("MagicMirror: invalid destination");
+		return;		
+	}
+
+	BattleSpellCast sc;
+	prepareBattleCast(parameters, sc);
+	
+	//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, destination, parameters.caster);
+	std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres));
+
+	logGlobal->debugStream() << "will affect: " << attackedCres.size() << " stacks";
+
+	handleResistance(env, attackedCres, sc);
+
+	for(auto cre : attackedCres)
+	{
+		sc.affectedCres.insert(cre->ID);
+	}
+	
+	StacksInjured si;
+	SpellCastContext ctx(attackedCres, sc, si);
+	applyBattleEffects(env, parameters, ctx);
+
+	env->sendAndApply(&sc);
+	if(!si.stacks.empty()) //after spellcast info shows
+		env->sendAndApply(&si);	
+	logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode: MAGIC_MIRROR";
+}
+
+void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, std::vector<const CStack* >& attackedCres, BattleSpellCast& sc) const
+{
+	//checking if creatures resist
+	//resistance/reflection is applied only to negative spells
+	if(owner->isNegative())
+	{
+		std::vector <const CStack*> resisted;
+		for(auto s : attackedCres)
+		{
+			//magic resistance
+			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
+
+			if(env->getRandomGenerator().nextInt(99) < prob)
+			{
+				resisted.push_back(s);
+			}
+		}
+
+		vstd::erase_if(attackedCres, [&resisted](const CStack * s)
+		{
+			return vstd::contains(resisted, s);
+		});
+
+		for(auto s : resisted)
+		{
+			BattleSpellCast::CustomEffect effect;
+			effect.effect = 78;
+			effect.stack = s->ID;
+			sc.customEffects.push_back(effect);
+		}		
 	}	
 }
+
+void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& parameters, BattleSpellCast& sc) const
+{
+	sc.side = parameters.casterSide;
+	sc.id = owner->id;
+	sc.skill = parameters.spellLvl;
+	sc.tile = parameters.getFirstDestinationHex();
+	sc.dmgToDisplay = 0;
+	sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
+	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
+	sc.manaGained = 0;	
+}
+

+ 11 - 10
lib/spells/CDefaultSpellMechanics.h

@@ -19,7 +19,9 @@ class StacksInjured;
 struct SpellCastContext
 {
 	SpellCastContext(std::vector<const CStack *> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
-		attackedCres(attackedCres), sc(sc), si(si){};
+		attackedCres(attackedCres), sc(sc), si(si)	
+	{
+	};
 	std::vector<const CStack *> & attackedCres;
 	BattleSpellCast & sc;
 	StacksInjured & si;
@@ -40,26 +42,25 @@ public:
 	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 canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
 	
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
-	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
+	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override final;
 
 	void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet, 
 		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) 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;
+	virtual void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
 
 	///actual adventure cast implementation
 	virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
 	
 	void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
+private:
+	void castMagicMirror(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
+	void handleResistance(const SpellCastEnvironment * env, std::vector<const CStack*> & attackedCres, BattleSpellCast & sc) const;
+	void prepareBattleCast(const BattleSpellCastParameters & parameters, BattleSpellCast & sc) const;
 };

+ 87 - 70
lib/spells/CSpellHandler.cpp

@@ -23,10 +23,11 @@
 #include "../CModHandler.h"
 #include "../StringConstants.h"
 
-#include "../mapObjects/CGHeroInstance.h"
 #include "../BattleState.h"
 #include "../CBattleCallback.h"
-#include "../CGameState.h"
+#include "../CGameState.h" //todo: remove
+
+#include "../NetPacks.h" //todo: remove
 
 #include "ISpellMechanics.h"
 
@@ -71,13 +72,6 @@ namespace SpellConfig
 	};
 }
 
-BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo* cb)
-	: spellLvl(0), destination(BattleHex::INVALID), casterSide(0),casterColor(PlayerColor::CANNOT_DETERMINE),casterHero(nullptr), secHero(nullptr),
-	usedSpellPower(0),mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr), cb(cb)
-{
-
-}
-
 ///CSpell::LevelInfo
 CSpell::LevelInfo::LevelInfo()
 	:description(""),cost(0),power(0),AIValue(0),smartTarget(true), clearTarget(false), clearAffected(false), range("0")
@@ -121,9 +115,13 @@ bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastP
 }
 
 void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
-{
+{	
 	assert(env);
-
+	if(parameters.destinations.size()<1)
+	{
+		env->complain("Spell must have at least one destination");
+		return;
+	}
 	mechanics->battleCast(env, parameters);
 }
 
@@ -140,52 +138,15 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const
 
 ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const
 {
-	ui32 ret = 0; //value to return
-
 	//check if spell really does damage - if not, return 0
 	if(!isDamageSpell())
 		return 0;
-
-	ret = usedSpellPower * power;
-	ret += getPower(spellSchoolLevel);
-
-	//affected creature-specific part
-	if(nullptr != affectedCreature)
-	{
-		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
-		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
-		{
-			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
-			{
-				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
-				ret /= 100;
-				stop = true;//only bonus from one school is used
-			}
-		});
-
-		//general spell dmg reduction
-		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
-		{
-			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
-			ret /= 100;
-		}
-
-		//dmg increasing
-		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
-		{
-			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
-			ret /= 100;
-		}
-	}
-	
-	if(nullptr != caster) //todo: make sure that caster always present	
-		ret = caster->getSpellBonus(this, ret, affectedCreature);
-	return ret;
+	return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower));	
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
-	return mechanics->canBeCasted(cb, player);
+	return mechanics->canBeCast(cb, player);
 }
 
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
@@ -193,7 +154,7 @@ std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl,
 	return mechanics->rangeInHexes(centralHex,schoolLvl,side,outDroppedHexes);
 }
 
-std::set<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const
+std::set<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const
 {
 	ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
 
@@ -217,12 +178,6 @@ CSpell::ETargetType CSpell::getTargetType() const
 	return targetType;
 }
 
-CSpell::TargetInfo CSpell::getTargetInfo(const int level) const
-{
-	TargetInfo info(this, level);
-	return info;
-}
-
 void CSpell::forEachSchool(const std::function<void(const SpellSchoolInfo &, bool &)>& cb) const
 {
 	bool stop = false;
@@ -351,7 +306,7 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
 	}
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 {
 	// Get all stacks at destination hex. only alive if not rising spell
 	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
@@ -402,7 +357,48 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallbac
 	return ESpellCastProblem::OK;
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const
+int CSpell::adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const
+{
+	int ret = rawDamage;
+	//affected creature-specific part
+	if(nullptr != affectedCreature)
+	{
+		//applying protections - when spell has more then one elements, only one protection should be applied (I think)
+		forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop)
+		{
+			if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id))
+			{
+				ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id);
+				ret /= 100;
+				stop = true;//only bonus from one school is used
+			}
+		});
+
+		//general spell dmg reduction
+		if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1))
+		{
+			ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1);
+			ret /= 100;
+		}
+
+		//dmg increasing
+		if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id))
+		{
+			ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum());
+			ret /= 100;
+		}
+	}
+	if(caster != nullptr)
+		ret = caster->getSpellBonus(this, ret, affectedCreature);
+	return ret;
+}
+
+int CSpell::calculateRawEffectValue(int effectLevel, int effectPower) const
+{
+	return effectPower * power + getPower(effectLevel);	
+}
+
+ESpellCastProblem::ESpellCastProblem CSpell::internalIsImmune(const ISpellCaster * caster, const CStack *obj) const
 {
 	//todo: use new bonus API
 	//1. Check absolute limiters
@@ -419,25 +415,46 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
 			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 	}
 
-	//spell-based spell immunity (only ANTIMAGIC in OH3) is treated as absolute
-	std::stringstream cachingStr;
-	cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "source_" << Bonus::SPELL_EFFECT;
+	{
+		//spell-based spell immunity (only ANTIMAGIC in OH3) is treated as absolute
+		std::stringstream cachingStr;
+		cachingStr << "type_" << Bonus::LEVEL_SPELL_IMMUNITY << "source_" << Bonus::SPELL_EFFECT;
 
-	TBonusListPtr levelImmunitiesFromSpell = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::sourceType(Bonus::SPELL_EFFECT)), cachingStr.str());	
+		TBonusListPtr levelImmunitiesFromSpell = obj->getBonuses(Selector::type(Bonus::LEVEL_SPELL_IMMUNITY).And(Selector::sourceType(Bonus::SPELL_EFFECT)), cachingStr.str());	
 
-	if(levelImmunitiesFromSpell->size() > 0  &&  levelImmunitiesFromSpell->totalValue() >= level  &&  level)
-	{
-		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		if(levelImmunitiesFromSpell->size() > 0  &&  levelImmunitiesFromSpell->totalValue() >= level  &&  level)
+		{
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+		}
 	}
+	{
+		//SPELL_IMMUNITY absolute case
+		std::stringstream cachingStr;
+		cachingStr << "type_" << Bonus::SPELL_IMMUNITY << "subtype_" << id.toEnum() << "addInfo_1";
+		if(obj->hasBonus(Selector::typeSubtypeInfo(Bonus::SPELL_IMMUNITY, id.toEnum(), 1), cachingStr.str()))
+			return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+	}	
 
 	//check receptivity
 	if (isPositive() && obj->hasBonusOfType(Bonus::RECEPTIVE)) //accept all positive spells
 		return ESpellCastProblem::OK;
 		
 	//3. Check negation
-	//FIXME: Orb of vulnerability mechanics is not such trivial
-	if(obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES)) //Orb of vulnerability
+	//Orb of vulnerability
+	//FIXME: Orb of vulnerability mechanics is not such trivial (issue 1791)
+	const bool battleWideNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 0);
+	const bool heroNegation = obj->hasBonusOfType(Bonus::NEGATE_ALL_NATURAL_IMMUNITIES, 1);
+	//anyone can cast on artifact holder`s stacks
+	if(heroNegation) 
 		return ESpellCastProblem::NOT_DECIDED;
+	//this stack is from other player
+	//todo: check that caster is always present (not trivial is this case)
+	//todo: NEGATE_ALL_NATURAL_IMMUNITIES special cases: dispell, chain lightning
+	else if(battleWideNegation && caster)
+	{
+		if(obj->owner != caster->getOwner())
+			return ESpellCastProblem::NOT_DECIDED;
+	}
 
 	//4. Check negatable limit
 	for(auto b : limiters)
@@ -491,7 +508,7 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj)
 	return ESpellCastProblem::NOT_DECIDED;
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	const auto immuneResult = mechanics->isImmuneByStack(caster,obj);
 

+ 21 - 55
lib/spells/CSpellHandler.h

@@ -30,6 +30,9 @@ struct BattleSpellCast;
 class CGameInfoCallback;
 class CRandomGenerator;
 class CMap;
+struct AdventureSpellCastParameters;
+struct BattleSpellCastParameters;
+class SpellCastEnvironment;
 
 struct SpellSchoolInfo
 {
@@ -41,45 +44,6 @@ struct SpellSchoolInfo
 	Bonus::BonusType knoledgeBonus;
 };
 
-///callback to be provided by server
-class DLL_LINKAGE SpellCastEnvironment
-{
-public:
-	virtual ~SpellCastEnvironment(){};
-	virtual void sendAndApply(CPackForClient * info) const = 0;
-
-	virtual CRandomGenerator & getRandomGenerator() const = 0;
-	virtual void complain(const std::string & problem) const = 0;
-
-	virtual const CMap * getMap() const = 0;
-	virtual const CGameInfoCallback * getCb() const = 0;
-
-	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0;	//TODO: remove
-};
-
-///helper struct
-struct DLL_LINKAGE BattleSpellCastParameters
-{
-public:
-	BattleSpellCastParameters(const BattleInfo * cb);
-	int spellLvl;
-	BattleHex destination;
-	ui8 casterSide;
-	PlayerColor casterColor;
-	const CGHeroInstance * casterHero; //deprecated
-	const CGHeroInstance * secHero;
-	int usedSpellPower;
-	ECastingMode::ECastingMode mode;
-	const CStack * casterStack;
-	const CStack * selectedStack;
-	const BattleInfo * cb;
-};
-
-struct DLL_LINKAGE AdventureSpellCastParameters
-{
-	const CGHeroInstance * caster;
-	int3 pos;
-};
 
 enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
 
@@ -136,10 +100,10 @@ public:
 		///displayed on caster.
 		TAnimationQueue cast;
 
-		///displayed on target hex. If spell was casted with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
+		///displayed on target hex. If spell was cast with no target selection displayed on entire battlefield (f.e. ARMAGEDDON)
 		TAnimationQueue hit;
 
-		///displayed "between" caster and (first) target. Ignored if spell was casted with no target selection.
+		///displayed "between" caster and (first) target. Ignored if spell was cast with no target selection.
 		///use selectProjectile to access
 		std::vector<ProjectileInfo> projectile;
 
@@ -188,7 +152,7 @@ public:
 	enum ETargetType {NO_TARGET, CREATURE, OBSTACLE, LOCATION};
 	enum ESpellPositiveness {NEGATIVE = -1, NEUTRAL = 0, POSITIVE = 1};
 
-	struct TargetInfo
+	struct DLL_LINKAGE TargetInfo
 	{
 		ETargetType type;
 		bool smart;
@@ -231,8 +195,6 @@ public:
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr ) const; //convert range to specific hexes; last optional out parameter is set to true, if spell would cover unavailable hexes (that are not included in ret)
 	ETargetType getTargetType() const; //deprecated
 
-	CSpell::TargetInfo getTargetInfo(const int level) const;
-
 	bool isCombatSpell() const;
 	bool isAdventureSpell() const;
 	bool isCreatureAbility() const;
@@ -251,17 +213,12 @@ public:
 	bool hasEffects() const;
 	void getEffects(std::vector<Bonus> &lst, const int level) const;
 
-	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
-
-	//internal, for use only by Mechanics classes
-	ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const;
 
 	///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
 	ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
 
 	///selects from allStacks actually affected stacks
-	std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const;
+	std::set<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const ISpellCaster * caster) const;
 
 	si32 getCost(const int skillLevel) const;
 
@@ -310,10 +267,13 @@ public:
 	///internal interface (for callbacks)
 	
 	///Checks general but spell-specific problems for all casting modes. Use only during battle.
-	ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const;
 
+	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
+	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
+	
 	///checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const;
 public:
 	///Server logic. Has write access to GameState via packets.
 	///May be executed on client side by (future) non-cheat-proof scripts.
@@ -328,11 +288,17 @@ public:
 	///implementation of BattleSpellCast applying
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
 
-public:
-	///Client logic.
-	
+public://Client logic.
 	void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector<std::string> & logLines) const;
 
+public://internal, for use only by Mechanics classes
+	///applies caster`s secondary skills and affectedCreature`s to raw damage
+	int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const;
+	///returns raw damage or healed HP
+	int calculateRawEffectValue(int effectLevel, int effectPower) const;		
+	///generic immunity calculation
+	ESpellCastProblem::ESpellCastProblem internalIsImmune(const ISpellCaster * caster, const CStack *obj) const;
+
 private:
 	void setIsOffensive(const bool val);
 	void setIsRising(const bool val);

+ 8 - 7
lib/spells/CreatureSpellMechanics.cpp

@@ -16,17 +16,18 @@
 #include "../BattleState.h"
 
 ///AcidBreathDamageMechanics
-void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
+	//todo: this should be effectValue
 	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	ctx.sc.dmgToDisplay = parameters.effectPower;
 
 	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.damageAmount = parameters.effectPower; //damage times the number of attackers
 		bsa.stackAttacked = (attackedCre)->ID;
 		bsa.attackerID = -1;
 		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
@@ -35,10 +36,10 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
 }
 
 ///DeathStareMechanics
-void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.usedSpellPower;
+	ctx.sc.dmgToDisplay = parameters.effectPower;
 	if(!ctx.attackedCres.empty())
 		vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
 
@@ -47,7 +48,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, B
 		BattleStackAttacked bsa;
 		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
 		bsa.spellID = owner->id;
-		bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);
+		bsa.damageAmount = parameters.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation 
 		bsa.stackAttacked = (attackedCre)->ID;
 		bsa.attackerID = -1;
 		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
@@ -63,7 +64,7 @@ void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpell
 	doDispell(battle, packet, Selector::positiveSpellEffects);	
 }
 
-ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance * caster,  const CStack * obj) const
+ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const ISpellCaster * caster,  const CStack * obj) const
 {
 	TBonusListPtr spellBon = obj->getSpellBonuses();
 	bool hasPositiveSpell = false;

+ 3 - 3
lib/spells/CreatureSpellMechanics.h

@@ -18,7 +18,7 @@ class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
 public:
 	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
@@ -26,7 +26,7 @@ class DLL_LINKAGE DeathStareMechanics : public DefaultSpellMechanics
 public:
 	DeathStareMechanics(CSpell * s): DefaultSpellMechanics(s){};
 protected:
-	void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 class DLL_LINKAGE DispellHelpfulMechanics : public DefaultSpellMechanics
@@ -36,5 +36,5 @@ public:
 
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 
-	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };

+ 59 - 0
lib/spells/ISpellMechanics.cpp

@@ -11,12 +11,71 @@
 #include "StdInc.h"
 #include "ISpellMechanics.h"
 
+#include "../BattleState.h"
+#include "../NetPacks.h"
+
 #include "CDefaultSpellMechanics.h"
 
 #include "AdventureSpellMechanics.h"
 #include "BattleSpellMechanics.h"
 #include "CreatureSpellMechanics.h"
 
+BattleSpellCastParameters::Destination::Destination(const CStack * destination):
+	stackValue(destination),
+	hexValue(destination->position)
+{
+	
+}
+
+BattleSpellCastParameters::Destination::Destination(const BattleHex & destination):
+	stackValue(nullptr),
+	hexValue(destination)	
+{
+	
+}
+
+BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell)
+	: cb(cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
+	casterHero(nullptr),
+	mode(ECastingMode::HERO_CASTING), casterStack(nullptr), selectedStack(nullptr),
+	spellLvl(-1),  effectLevel(-1), effectPower(0), enchantPower(0), effectValue(0)
+{
+	casterStack = dynamic_cast<const CStack *>(caster);
+	casterHero = dynamic_cast<const CGHeroInstance *>(caster);
+	prepare(spell);
+}
+
+void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
+{
+	destinations.push_back(Destination(destination));
+}
+
+void BattleSpellCastParameters::aimToStack(const CStack * destination)
+{
+	destinations.push_back(Destination(destination));
+}
+
+
+BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
+{
+	return destinations.at(0).hexValue;
+}
+
+void BattleSpellCastParameters::prepare(const CSpell * spell)
+{
+	spellLvl = caster->getSpellSchoolLevel(spell);
+	effectLevel = caster->getEffectLevel(spell);
+	effectPower = caster->getEffectPower(spell);
+	effectValue = caster->getEffectValue(spell);
+	enchantPower = caster->getEnchantPower(spell);
+	
+	vstd::amax(spellLvl, 0);
+	vstd::amax(effectLevel, 0);
+	vstd::amax(enchantPower, 0);	
+	vstd::amax(enchantPower, 0);
+	vstd::amax(effectValue, 0);
+}
+
 ///ISpellMechanics
 ISpellMechanics::ISpellMechanics(CSpell * s):
 	owner(s)

+ 71 - 4
lib/spells/ISpellMechanics.h

@@ -12,8 +12,75 @@
 
 #include "CSpellHandler.h"
 #include "../BattleHex.h"
-#include "../BattleState.h"
-#include "../NetPacks.h"
+
+
+///callback to be provided by server
+class DLL_LINKAGE SpellCastEnvironment
+{
+public:
+	virtual ~SpellCastEnvironment(){};
+	virtual void sendAndApply(CPackForClient * info) const = 0;
+
+	virtual CRandomGenerator & getRandomGenerator() const = 0;
+	virtual void complain(const std::string & problem) const = 0;
+
+	virtual const CMap * getMap() const = 0;
+	virtual const CGameInfoCallback * getCb() const = 0;
+
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0;	//TODO: remove
+};
+
+///all parameters of particular cast event
+struct DLL_LINKAGE BattleSpellCastParameters
+{
+public:
+	///Single spell destination. 
+	/// (assumes that anything but battle stack can share same hex)
+	struct DLL_LINKAGE Destination
+	{
+		explicit Destination(const CStack * destination); 
+		explicit Destination(const BattleHex & destination);
+		
+		const CStack * stackValue;
+		const BattleHex hexValue;
+	};
+
+	BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell);
+	void aimToHex(const BattleHex & destination);
+	void aimToStack(const CStack * destination);
+	BattleHex getFirstDestinationHex() const;
+	
+	const BattleInfo * cb;
+	const ISpellCaster * caster;
+	const PlayerColor casterColor;	
+	const ui8 casterSide;
+
+	std::vector<Destination> destinations;
+
+	const CGHeroInstance * casterHero; //deprecated
+	ECastingMode::ECastingMode mode;
+	const CStack * casterStack; //deprecated
+	const CStack * selectedStack;//deprecated
+
+	///spell school level
+	int spellLvl;	
+	///spell school level to use for effects
+	int effectLevel;
+	///actual spell-power affecting effect values
+	int effectPower;
+	///actual spell-power affecting effect duration
+	int enchantPower;
+	///for Archangel-like casting
+	int effectValue;
+private:	
+	void prepare(const CSpell * spell);
+};
+
+struct DLL_LINKAGE AdventureSpellCastParameters
+{
+	const CGHeroInstance * caster;
+	int3 pos;
+};
 
 class DLL_LINKAGE ISpellMechanics
 {
@@ -39,9 +106,9 @@ public:
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
 	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
 	
-	virtual ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
+	virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
 	
-	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
+	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const = 0;
 	
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
 	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;

+ 18 - 2
lib/spells/Magic.h

@@ -17,6 +17,7 @@
 
 class CSpell;
 class CStack;
+class PlayerColor;
 
 class DLL_LINKAGE ISpellCaster
 {
@@ -26,7 +27,22 @@ public:
 	/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
 	/// caster may not know this spell at all 
 	/// optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic
-	virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;		
-	
+	virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;	
+
+	///applying sorcery secondary skill etc
 	virtual ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const = 0;
+	
+	///default spell school level for effect calculation
+	virtual int getEffectLevel(const CSpell * spell) const = 0;
+
+	///default spell-power for damage/heal calculation
+	virtual int getEffectPower(const CSpell * spell) const = 0;
+
+	///default spell-power for timed effects duration
+	virtual int getEnchantPower(const CSpell * spell) const = 0;
+
+	///damage/heal override(ignores spell configuration, effect level and effect power)
+	virtual int getEffectValue(const CSpell * spell) const = 0;
+	
+	virtual const PlayerColor getOwner() const = 0;
 };

+ 104 - 130
server/CGameHandler.cpp

@@ -10,6 +10,7 @@
 #include "../lib/CBuildingHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/spells/CSpellHandler.h"
+#include "../lib/spells/ISpellMechanics.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CTownHandler.h"
 #include "../lib/CCreatureHandler.h"
@@ -805,7 +806,7 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 		
 		//TODO: should spell override creature`s projectile?
 		
-		std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex);
+		std::set<const CStack*> attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex, att);
 	
 		//TODO: get exact attacked hex for defender
 
@@ -845,14 +846,13 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 	if (att->hasBonusOfType(Bonus::LIFE_DRAIN) && def->isLiving())
 	{
 		StacksHealedOrResurrected shi;
-		shi.lifeDrain = (ui8)true;
-		shi.tentHealing = (ui8)false;
+		shi.lifeDrain = true;
+		shi.tentHealing = false;
 		shi.drainedFrom = def->ID;
 
 		StacksHealedOrResurrected::HealInfo hi;
 		hi.stackID = att->ID;
-		hi.healedHP = std::min<int> (bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100,
-			att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) );
+		hi.healedHP = att->calculateHealedHealthPoints(bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100, true);
 		hi.lowLevelResurrection = false;
 		shi.healedStacks.push_back(hi);
 
@@ -3771,14 +3771,17 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 			const CStack *healer = gs->curB->battleGetStackByID(ba.stackNumber),
 				*destStack = gs->curB->battleGetStackByPos(ba.destinationTile);
 
+			ui32 healed = 0;
+
 			if(healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
 			{
 				complain("There is either no healer, no destination, or healer cannot heal :P");
 			}
-			int maxHealable = destStack->MaxHealth() - destStack->firstHPleft;
-			int maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
-
-			int healed = std::min(maxHealable, maxiumHeal);
+			else
+			{
+				ui32 maxiumHeal = healer->count * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
+				healed = destStack->calculateHealedHealthPoints(maxiumHeal, false);
+			}
 
 			if(healed == 0)
 			{
@@ -3793,14 +3796,12 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 				StacksHealedOrResurrected::HealInfo hi;
 				hi.healedHP = healed;
-				hi.lowLevelResurrection = 0;
+				hi.lowLevelResurrection = false;
 				hi.stackID = destStack->ID;
 
 				shr.healedStacks.push_back(hi);
 				sendAndApply(&shr);
 			}
-
-
 			sendAndApply(&end_action);
 			break;
 		}
@@ -3867,27 +3868,19 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				complain("That stack can't cast spells!");
 			else
 			{
-				BattleSpellCastParameters p(gs->curB);				
-				
-				p.spellLvl = 0;
+				const CSpell * spell = SpellID(spellID).toSpell();				
+				BattleSpellCastParameters parameters(gs->curB, stack, spell);				
+				parameters.spellLvl = 0;
 				if (spellcaster)
-					vstd::amax(p.spellLvl, spellcaster->val);
+					vstd::amax(parameters.spellLvl, spellcaster->val);
 				if (randSpellcaster)
-					vstd::amax(p.spellLvl, randSpellcaster->val);
-				vstd::amin (p.spellLvl, 3);
-
-				p.casterSide = gs->curB->whatSide(stack->owner);
-				p.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(stack->owner));
-				p.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
-				p.destination = destination;
-				p.casterColor = stack->owner;	
-				p.casterHero = nullptr;
-				p.usedSpellPower = 0;	
-				p.casterStack = stack;	
-				p.selectedStack = nullptr;				
-
-				const CSpell * spell = SpellID(spellID).toSpell();
-				spell->battleCast(spellEnv, p);
+					vstd::amax(parameters.spellLvl, randSpellcaster->val);
+				vstd::amin (parameters.spellLvl, 3);
+				parameters.effectLevel = parameters.spellLvl;
+				parameters.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
+				parameters.aimToHex(destination);//todo: allow multiple destinations
+				parameters.selectedStack = nullptr;
+				spell->battleCast(spellEnv, parameters);
 			}
 			sendAndApply(&end_action);
 			break;
@@ -4062,7 +4055,6 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 
 
 			const CGHeroInstance *h = gs->curB->battleGetFightingHero(ba.side);
-			const CGHeroInstance *secondHero = gs->curB->battleGetFightingHero(!ba.side);
 			if(!h)
 			{
                 logGlobal->warnStream() << "Wrong caster!";
@@ -4076,20 +4068,12 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 
 			const CSpell * s = SpellID(ba.additionalInfo).toSpell();
 			
-			BattleSpellCastParameters parameters(gs->curB);
-			parameters.spellLvl =  h->getSpellSchoolLevel(s);
-			parameters.destination = ba.destinationTile;
-			parameters.casterSide = ba.side;
-			parameters.casterColor =  h->tempOwner;	
-			parameters.casterHero = h;
-			parameters.secHero = secondHero;
-			
-			parameters.usedSpellPower = h->getPrimSkillLevel(PrimarySkill::SPELL_POWER);	
+			BattleSpellCastParameters parameters(gs->curB, h, s);
+			parameters.aimToHex(ba.destinationTile);//todo: allow multiple destinations
 			parameters.mode = ECastingMode::HERO_CASTING;
-			parameters.casterStack = nullptr;	
 			parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);			
 
-			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h->tempOwner, s, ECastingMode::HERO_CASTING);
+			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
 			if(escp != ESpellCastProblem::OK)
 			{
 				logGlobal->warnStream() << "Spell cannot be cast!";
@@ -4186,17 +4170,17 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		}
 		if (st->hasBonusOfType(Bonus::MANA_DRAIN) && !vstd::contains(st->state, EBattleStackState::DRAINED_MANA))
 		{
-			const CGHeroInstance * enemy = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
-			//const CGHeroInstance * owner = gs->curB->getHero(st->owner);
-			if (enemy)
+			const PlayerColor opponent = gs->curB->theOtherPlayer(st->owner);
+			const CGHeroInstance * opponentHero = gs->curB->getHero(opponent);
+			if (opponentHero)
 			{
 				ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);
-				vstd::amin(manaDrained, gs->curB->battleGetFightingHero(0)->mana);
+				vstd::amin(manaDrained, opponentHero->mana);
 				if (manaDrained)
 				{
 					bte.effect = Bonus::MANA_DRAIN;
 					bte.val = manaDrained;
-					bte.additionalInfo = enemy->id.getNum(); //for sanity
+					bte.additionalInfo = opponentHero->id.getNum(); //for sanity
 					sendAndApply(&bte);
 				}
 			}
@@ -4225,30 +4209,26 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		int side = gs->curB->whatSide(st->owner);
 		if (st->casts && !gs->curB->sides.at(side).enchanterCounter)
 		{
-			bool casted = false;
-			while (!bl.empty() && !casted)
+			bool cast = false;
+			while (!bl.empty() && !cast)
 			{
 				auto bonus = *RandomGeneratorUtil::nextItem(bl, gs->getRandomGenerator());
 				auto spellID = SpellID(bonus->subtype);
 				const CSpell * spell = SpellID(spellID).toSpell();
 				bl.remove_if([&bonus](Bonus * b){return b==bonus;});					
 				
-				if (gs->curB->battleCanCastThisSpell(st->owner, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
+				if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
 				{
-					BattleSpellCastParameters parameters(gs->curB);
+					BattleSpellCastParameters parameters(gs->curB, st, spell);
 					parameters.spellLvl = bonus->val;
-					parameters.destination = BattleHex::INVALID;
-					parameters.casterSide = side;
-					parameters.casterColor = st->owner;	
-					parameters.casterHero = nullptr;
-					parameters.secHero = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
-					parameters.usedSpellPower = 0;	
+					parameters.effectLevel = bonus->val;//todo: recheck
+					parameters.aimToHex(BattleHex::INVALID);
 					parameters.mode = ECastingMode::ENCHANTER_CASTING;
-					parameters.casterStack = st;	
 					parameters.selectedStack = nullptr;
 					
 					spell->battleCast(spellEnv, parameters);				
 
+					//todo: move to mechanics
 					BattleSetStackProperty ssp;
 					ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
 					ssp.absolute = false;
@@ -4256,7 +4236,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 					ssp.stackID = st->ID;
 					sendAndApply(&ssp);
 					
-					casted = true;
+					cast = true;
 				}				
 			};
 		}
@@ -4297,7 +4277,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
 	//helper info
 	const SpellCreatedObstacle *spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&obstacle); //not nice but we may need spell params
 	const ui8 side = !curStack->attackerOwned; //if enemy is defending (false = 0), side of enemy hero is 1 (true)
-	const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);
+	const CGHeroInstance *hero = gs->curB->battleGetFightingHero(side);//FIXME: there may be no hero - landmines in Tower
 
 	if(obstacle.obstacleType == CObstacleInstance::MOAT)
 	{
@@ -4319,7 +4299,7 @@ void CGameHandler::handleDamageFromObstacle(const CObstacleInstance &obstacle, c
 
 		damage = sp->calculateDamage(hero, curStack,
 											 spellObstacle->spellLevel, spellObstacle->casterSpellPower);
-		//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if casted by hero,
+		//TODO even if obstacle wasn't created by hero (Tower "moat") it should deal dmg as if cast by hero,
 		//if it is bigger than default dmg. Or is it just irrelevant H3 implementation quirk
 	}
 	else if(obstacle.obstacleType == CObstacleInstance::FIRE_WALL)
@@ -4933,32 +4913,23 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 			}
 			int chance = attacker->valOfBonuses((Selector::typeSubtype(attackMode, spellID)));
 			vstd::amin (chance, 100);
-			int destination = oneOfAttacked->position;
 
 			const CSpell * spell = SpellID(spellID).toSpell();
-			if(gs->curB->battleCanCastThisSpellHere(attacker->owner, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
+			if(gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
 				continue;
 
-			//check if spell should be casted (probability handling)
+			//check if spell should be cast (probability handling)
 			if(gs->getRandomGenerator().nextInt(99) >= chance)
 				continue;
 
 			//casting
 			if (castMe) //stacks use 0 spell power. If needed, default = 3 or custom value is used
 			{
-				const CSpell * spell = SpellID(spellID).toSpell();
-
-				BattleSpellCastParameters parameters(gs->curB);
+				BattleSpellCastParameters parameters(gs->curB, attacker, spell);
 				parameters.spellLvl = spellLevel;
-				parameters.destination = destination;
-				parameters.casterSide = !attacker->attackerOwned;
-				parameters.casterColor = attacker->owner;	
-				parameters.casterHero = nullptr;
-				parameters.secHero = nullptr;
-
-				parameters.usedSpellPower = 0;	
+				parameters.effectLevel = spellLevel;
+				parameters.aimToStack(oneOfAttacked);
 				parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
-				parameters.casterStack = attacker;	
 				parameters.selectedStack = nullptr;
 
 				spell->battleCast(spellEnv, parameters);			
@@ -4983,17 +4954,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 	{
 		const CSpell * spell = SpellID(spellID).toSpell();
 
-		BattleSpellCastParameters parameters(gs->curB);
+		BattleSpellCastParameters parameters(gs->curB, attacker, spell);
 		parameters.spellLvl = 0;
-		parameters.destination = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked)->position;
-		parameters.casterSide = !attacker->attackerOwned;
-		parameters.casterColor = attacker->owner;	
-		parameters.casterHero = nullptr;
-		parameters.secHero = nullptr;
-
-		parameters.usedSpellPower = power;	
+		parameters.effectLevel = 0;
+		parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
+		parameters.effectPower = power;	
 		parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
-		parameters.casterStack = attacker;	
 		parameters.selectedStack = nullptr;
 
 		spell->battleCast(this->spellEnv, parameters);		
@@ -5293,25 +5259,16 @@ void CGameHandler::runBattle()
 		{
 			TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));		
 			
-			BattleSpellCastParameters parameters(gs->curB);
-			parameters.spellLvl = 3;
-			parameters.destination = BattleHex::INVALID;
-			parameters.casterSide = i;
-			parameters.casterColor = h->tempOwner;	
-			parameters.casterHero = nullptr;
-			parameters.secHero = gs->curB->battleGetFightingHero(1-i);
-			
-			
-			parameters.mode = ECastingMode::HERO_CASTING;
-			parameters.casterStack = nullptr;	
-			parameters.selectedStack = nullptr;	
-					
 			for (Bonus *b : *bl)
 			{
-				parameters.usedSpellPower = b->val;	
-				
 				const CSpell * spell = SpellID(b->subtype).toSpell();
-				
+				BattleSpellCastParameters parameters(gs->curB, h, spell);
+				parameters.spellLvl = 3;
+				parameters.effectLevel = 3;
+				parameters.aimToHex(BattleHex::INVALID);
+				parameters.mode = ECastingMode::PASSIVE_CASTING;
+				parameters.selectedStack = nullptr;					
+				parameters.enchantPower = b->val;
 				spell->battleCast(spellEnv, parameters);
 			}
 		}
@@ -5484,14 +5441,30 @@ void CGameHandler::runBattle()
 					else
 					{
                         logGlobal->traceStream() << "Activating " << next->nodeName();
-						BattleSetActiveStack sas;
-						sas.stack = next->ID;
+                        auto nextId = next->ID;
+						BattleSetActiveStack sas;						
+						sas.stack = nextId;
 						sendAndApply(&sas);
+
+						auto actionWasMade = [&]() -> bool
+						{
+							if(battleMadeAction.data)//active stack has made its action
+								return true;
+							if(battleResult.get())// battle is finished
+								return true;
+							if(next == nullptr)//active stack was been removed
+								return true;
+							return !next->alive();//active stack is dead
+						};
+
 						boost::unique_lock<boost::mutex> lock(battleMadeAction.mx);
 						battleMadeAction.data = false;
-						while (next->alive() && //next is invalid after sacrificing current stack :?
-							(!battleMadeAction.data  &&  !battleResult.get())) //active stack hasn't made its action and battle is still going
+						while(!actionWasMade())
+						{
 							battleMadeAction.cond.wait(lock);
+							if(battleGetStackByID(nextId, false) != next)
+								next = nullptr; //it may be removed, while we wait
+						}
 					}
 				}
 
@@ -5500,36 +5473,37 @@ void CGameHandler::runBattle()
 					breakOuter = true;
 					break;
 				}
-
 				//we're after action, all results applied
 				checkForBattleEnd(); //check if this action ended the battle
 
-				//check for good morale
-				nextStackMorale = next->MoraleVal();
-				if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE)  //only one extra move per turn possible
-					&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
-					&& !next->waited()
-					&& !vstd::contains(next->state, EBattleStackState::FEAR)
-					&&  next->alive()
-					&&  nextStackMorale > 0
-					&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
-						|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
-					)
+				if(next != nullptr)
 				{
-					if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
-
-						{
-							BattleTriggerEffect bte;
-							bte.stackID = next->ID;
-							bte.effect = Bonus::MORALE;
-							bte.val = 1;
-							bte.additionalInfo = 0;
-							sendAndApply(&bte); //play animation
-
-							++numberOfAsks; //move this stack once more
-						}
+					//check for good morale
+					nextStackMorale = next->MoraleVal();
+					if(!vstd::contains(next->state,EBattleStackState::HAD_MORALE)  //only one extra move per turn possible
+						&& !vstd::contains(next->state,EBattleStackState::DEFENDING)
+						&& !next->waited()
+						&& !vstd::contains(next->state, EBattleStackState::FEAR)
+						&&  next->alive()
+						&&  nextStackMorale > 0
+						&& !(NBonus::hasOfType(gs->curB->battleGetFightingHero(0), Bonus::BLOCK_MORALE)
+							|| NBonus::hasOfType(gs->curB->battleGetFightingHero(1), Bonus::BLOCK_MORALE)) //checking if gs->curB->heroes have (or don't have) morale blocking bonuses
+						)
+					{
+						if(gs->getRandomGenerator().nextInt(23) < nextStackMorale) //this stack hasn't got morale this turn
+
+							{
+								BattleTriggerEffect bte;
+								bte.stackID = next->ID;
+								bte.effect = Bonus::MORALE;
+								bte.val = 1;
+								bte.additionalInfo = 0;
+								sendAndApply(&bte); //play animation
+
+								++numberOfAsks; //move this stack once more
+							}
+					}
 				}
-
 				--numberOfAsks;
 			} while (numberOfAsks > 0);