Browse Source

Merge pull request #198 from vcmi/SpellsRefactoring8

No reason to not merge this now.
ArseniyShestakov 9 years ago
parent
commit
6c63041d1a

+ 103 - 53
AI/BattleAI/BattleAI.cpp

@@ -103,11 +103,6 @@ void CBattleAI::init(std::shared_ptr<CBattleCallback> CB)
 	CB->unlockGsWhenWaiting = false;
 }
 
-static bool thereRemainsEnemy()
-{
-	return !cbc->battleIsFinished();
-}
-
 BattleAction CBattleAI::activeStack( const CStack * stack )
 {
 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName())	;
@@ -136,21 +131,20 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 		if(cb->battleCanCastSpell())
 			attemptCastingSpell();
 
-		if(!thereRemainsEnemy())
-			return BattleAction();
+		if(auto ret = cbc->battleIsFinished())
+		{
+			//spellcast may finish battle
+			//send special preudo-action
+			BattleAction cancel;
+			cancel.actionType = Battle::CANCEL;
+			return cancel;
+		}
 
 		if(auto action = considerFleeingOrSurrendering())
 			return *action;
 
-		if(cb->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY).empty())
-		{
-			//We apparently won battle by casting spell, return defend... (accessing cb may cause trouble)
-			return BattleAction::makeDefend(stack);
-		}
-
 		PotentialTargets targets(stack);
 
-
 		if(targets.possibleAttacks.size())
 		{
 			auto hlp = targets.bestAction();
@@ -358,6 +352,7 @@ struct PossibleSpellcast
 {
 	const CSpell *spell;
 	BattleHex dest;
+	si32 value;
 };
 
 struct CurrentOffensivePotential
@@ -430,9 +425,9 @@ void CBattleAI::attemptCastingSpell()
 	std::vector<PossibleSpellcast> possibleCasts;
 	for(auto spell : possibleSpells)
 	{
-		for(auto hex : getTargetsToConsider(spell))
+		for(auto hex : getTargetsToConsider(spell, hero))
 		{
-			PossibleSpellcast ps = {spell, hex};
+			PossibleSpellcast ps = {spell, hex, 0};
 			possibleCasts.push_back(ps);
 		}
 	}
@@ -458,7 +453,7 @@ void CBattleAI::attemptCastingSpell()
 			{
 				int damageDealt = 0, damageReceived = 0;
 
-				auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero);
+				auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
 
 				if(stacksSuffering.empty())
 					return -1;
@@ -472,41 +467,52 @@ void CBattleAI::attemptCastingSpell()
 						damageDealt += dmg;
 				}
 
-				const int damageDiff = damageDealt - damageReceived;
-
+				const int damageDiff = damageDealt - damageReceived * 10;
 
-				LOGFL("Casting %s on hex %d would deal %d damage points among %d stacks.",
-					ps.spell->name % ps.dest % damageDiff % stacksSuffering.size());
+				LOGFL("Casting %s on hex %d would deal { %d %d } damage points among %d stacks.",
+					ps.spell->name % ps.dest % damageDealt % damageReceived % stacksSuffering.size());
 				//TODO tactic effect too
 				return damageDiff;
 			}
 		case TIMED_EFFECT:
 			{
-				StackWithBonuses swb;
-				swb.stack = cb->battleGetStackByPos(ps.dest);
-				if(!swb.stack)
+				auto stacksAffected = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, hero, skillLevel, ps.dest);
+
+				if(stacksAffected.empty())
 					return -1;
 
-				Bonus pseudoBonus;
-				pseudoBonus.sid = ps.spell->id;
-				pseudoBonus.val = skillLevel;
-				pseudoBonus.turnsRemain = 1; //TODO
-				CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
+				int totalGain = 0;
+
+				for(const CStack * sta : stacksAffected)
+				{
+					StackWithBonuses swb;
+					swb.stack = sta;
+
+					Bonus pseudoBonus;
+					pseudoBonus.sid = ps.spell->id;
+					pseudoBonus.val = skillLevel;
+					pseudoBonus.turnsRemain = 1; //TODO
+					CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
+
+					HypotheticChangesToBattleState state;
+					state.bonusesOfStacks[swb.stack] = &swb;
 
-				HypotheticChangesToBattleState state;
-				state.bonusesOfStacks[swb.stack] = &swb;
+					PotentialTargets pt(swb.stack, state);
+					auto newValue = pt.bestActionValue();
+					auto oldValue = valueOfStack[swb.stack];
+					auto gain = newValue - oldValue;
+					if(swb.stack->owner != playerID) //enemy
+						gain = -gain;
 
-				PotentialTargets pt(swb.stack, state);
-				auto newValue = pt.bestActionValue();
-				auto oldValue = valueOfStack[swb.stack];
-				auto gain = newValue - oldValue;
-				if(swb.stack->owner != playerID) //enemy
-					gain = -gain;
+					LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
+						ps.spell->name % sta->nodeName() % (gain) % (oldValue) % (newValue));
 
-				LOGFL("Casting %s on %s would improve the stack by %d points (from %d to %d)",
-					ps.spell->name % swb.stack->nodeName() % gain % (oldValue) % (newValue));
+					totalGain += gain;
+				}
+
+				LOGFL("Total gain of cast %s at hex %d is %d", ps.spell->name % (ps.dest.hex) % (totalGain));
 
-				return gain;
+				return totalGain;
 			}
 		default:
 			assert(0);
@@ -514,7 +520,15 @@ void CBattleAI::attemptCastingSpell()
 		}
 	};
 
-	auto castToPerform = *vstd::maxElementByFun(possibleCasts, evaluateSpellcast);
+	for(PossibleSpellcast & psc : possibleCasts)
+		psc.value = evaluateSpellcast(psc);
+
+	auto pscValue = [] (const PossibleSpellcast &ps) -> int
+	{
+		return ps.value;
+	};
+
+	auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
 	LOGFL("Best spell is %s. Will cast.", castToPerform.spell->name);
 
 	BattleAction spellcast;
@@ -527,24 +541,60 @@ void CBattleAI::attemptCastingSpell()
 	cb->battleMakeAction(&spellcast);
 }
 
-std::vector<BattleHex> CBattleAI::getTargetsToConsider( const CSpell *spell ) const
+std::vector<BattleHex> CBattleAI::getTargetsToConsider(const CSpell * spell, const ISpellCaster * caster) const
 {
-	if(spell->getTargetType() == CSpell::NO_TARGET)
-	{
-		//Spell can be cast anywhere, all hexes are potentially considerable.
-		std::vector<BattleHex> ret;
+	const CSpell::TargetInfo targetInfo(spell, caster->getSpellSchoolLevel(spell));
+	std::vector<BattleHex> ret;
 
-		for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
-			if(BattleHex(i).isAvailable())
-				ret.push_back(i);
-
-		return ret;
+	if(targetInfo.massive || targetInfo.type == CSpell::NO_TARGET)
+	{
+		ret.push_back(BattleHex());
 	}
 	else
 	{
-		//TODO when massive effect -> doesn't matter where cast
-		return cbc->battleGetPossibleTargets(playerID, spell);
+		switch(targetInfo.type)
+		{
+		case CSpell::CREATURE:
+			{
+				for(const CStack * stack : cbc->battleAliveStacks())
+				{
+					bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
+					bool casterStack = stack->owner == caster->getOwner();
+
+					if(!immune)
+						switch (spell->positiveness)
+						{
+						case CSpell::POSITIVE:
+							if(casterStack || targetInfo.smart)
+								ret.push_back(stack->position);
+							break;
+
+						case CSpell::NEUTRAL:
+							ret.push_back(stack->position);
+							break;
+
+						case CSpell::NEGATIVE:
+							if(!casterStack || targetInfo.smart)
+								ret.push_back(stack->position);
+							break;
+						}
+				}
+			}
+			break;
+		case CSpell::LOCATION:
+			{
+				for(int i = 0; i < GameConstants::BFIELD_SIZE; i++)
+					if(BattleHex(i).isAvailable())
+						ret.push_back(i);
+			}
+			break;
+
+		default:
+			break;
+		}
 	}
+
+	return ret;
 }
 
 boost::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()

+ 8 - 8
AI/BattleAI/BattleAI.h

@@ -51,11 +51,11 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 
 
 struct ThreatMap
-{	
+{
 	std::array<std::vector<BattleAttackInfo>, GameConstants::BFIELD_SIZE> threatMap; // [hexNr] -> enemies able to strike
-	
-	const CStack *endangered; 
-	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage; 
+
+	const CStack *endangered;
+	std::array<int, GameConstants::BFIELD_SIZE> sufferedDamage;
 
 	ThreatMap(const CStack *Endangered);
 };
@@ -89,7 +89,7 @@ const Val getValOr(const std::map<Key, Val> &Map, const Key &key, const Val2 def
 	auto i = Map.find(key);
 	if(i != Map.end())
 		return i->second;
-	else 
+	else
 		return defaultValue;
 }
 
@@ -111,8 +111,8 @@ class CBattleAI : public CBattleGameInterface
 {
 	int side;
 	std::shared_ptr<CBattleCallback> cb;
-	
-	//Previous setting of cb 
+
+	//Previous setting of cb
 	bool wasWaitingForRealize, wasUnlockingGs;
 
 	void print(const std::string &text) const;
@@ -148,6 +148,6 @@ public:
 	boost::optional<BattleAction> considerFleeingOrSurrendering();
 
 	void attemptCastingSpell();
-	std::vector<BattleHex> getTargetsToConsider(const CSpell *spell) const;
+	std::vector<BattleHex> getTargetsToConsider(const CSpell *spell, const ISpellCaster * caster) const;
 };
 

+ 74 - 131
client/battle/CBattleInterface.cpp

@@ -98,7 +98,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 								   std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen)
 	: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
       activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
-	  currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellSelMode(NO_LOCATION), spellToCast(nullptr), sp(nullptr),
+	  currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
 	  siegeH(nullptr), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
 	  givenCommand(nullptr), myTurn(false), resWindow(nullptr), moveStarted(false), moveSoundHander(-1), bresult(nullptr)
 {
@@ -1272,7 +1272,7 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	}
 
 	//todo: play custom cast animation
-	displaySpellCast(spellID, BattleHex::INVALID, false);
+	displaySpellCast(spellID, BattleHex::INVALID);
 
 	//playing projectile animation
 	if(sc->tile.isValid())
@@ -1323,12 +1323,9 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
 	}
 
 	//displaying message in console
-	std::vector<std::string> logLines;
-
-	spell.prepareBattleLog(curInt->cb.get(), sc, logLines);
-
-	for(auto line : logLines)
-		console->addText(line);
+	for(const auto & line : sc->battleLog)
+		if(!console->addText(line.toString()))
+			logGlobal->warn("Too long battle log line");
 
 	waitForAnims();
 	//mana absorption
@@ -1369,25 +1366,11 @@ void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 	}
 }
 
-void CBattleInterface::castThisSpell(SpellID spellID)
+CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
 {
-	auto  ba = new BattleAction;
-	ba->actionType = Battle::HERO_SPELL;
-	ba->additionalInfo = spellID; //spell number
-	ba->destinationTile = -1;
-	ba->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
-	ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
-	spellToCast = ba;
-	spellDestSelectMode = true;
-	creatureCasting = false;
+	PossibleActions spellSelMode = ANY_LOCATION;
 
-	//choosing possible tragets
-	const CGHeroInstance * castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
-	assert(castingHero); // code below assumes non-null hero
-	sp = spellID.toSpell();
-	spellSelMode = ANY_LOCATION;
-
-	const CSpell::TargetInfo ti(sp, castingHero->getSpellSchoolLevel(sp));
+	const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
 
 	if(ti.massive || ti.type == CSpell::NO_TARGET)
 		spellSelMode = NO_LOCATION;
@@ -1397,16 +1380,34 @@ void CBattleInterface::castThisSpell(SpellID spellID)
 	}
 	else if(ti.type == CSpell::CREATURE)
 	{
-		if(ti.smart)
-			spellSelMode = selectionTypeByPositiveness(*sp);
-		else
-			spellSelMode = ANY_CREATURE;
+		spellSelMode = AIMED_SPELL_CREATURE;
 	}
 	else if(ti.type == CSpell::OBSTACLE)
 	{
 		spellSelMode = OBSTACLE;
 	}
 
+	return spellSelMode;
+}
+
+void CBattleInterface::castThisSpell(SpellID spellID)
+{
+	auto  ba = new BattleAction;
+	ba->actionType = Battle::HERO_SPELL;
+	ba->additionalInfo = spellID; //spell number
+	ba->destinationTile = -1;
+	ba->stackNumber = (attackingHeroInstance->tempOwner == curInt->playerID) ? -1 : -2;
+	ba->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
+	spellToCast = ba;
+	spellDestSelectMode = true;
+	creatureCasting = false;
+
+	//choosing possible targets
+	const CGHeroInstance * castingHero = (attackingHeroInstance->tempOwner == curInt->playerID) ? attackingHeroInstance : defendingHeroInstance;
+	assert(castingHero); // code below assumes non-null hero
+	sp = spellID.toSpell();
+	PossibleActions spellSelMode = getCasterAction(sp, castingHero, ECastingMode::HERO_CASTING);
+
 	if (spellSelMode == NO_LOCATION) //user does not have to select location
 	{
 		spellToCast->destinationTile = -1;
@@ -1421,13 +1422,12 @@ void CBattleInterface::castThisSpell(SpellID spellID)
 	}
 }
 
-void CBattleInterface::displayEffect(ui32 effect, int destTile, bool areaEffect)
+void CBattleInterface::displayEffect(ui32 effect, int destTile)
 {
-	//todo: recheck areaEffect usage
 	addNewAnim(new CSpellEffectAnimation(this, effect, destTile, 0, 0, false));
 }
 
-void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect)
+void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile)
 {
 	if(animation.pause > 0)
 	{
@@ -1439,7 +1439,7 @@ void CBattleInterface::displaySpellAnimation(const CSpell::TAnimation & animatio
 	}
 }
 
-void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect)
+void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
 {
 	const CSpell * spell = spellID.toSpell();
 
@@ -1448,11 +1448,11 @@ void CBattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTi
 
 	for(const CSpell::TAnimation & animation : spell->animationInfo.cast)
 	{
-		displaySpellAnimation(animation, destinationTile, areaEffect);
+		displaySpellAnimation(animation, destinationTile);
 	}
 }
 
-void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect)
+void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
 {
 	const CSpell * spell = spellID.toSpell();
 
@@ -1461,11 +1461,11 @@ void CBattleInterface::displaySpellEffect(SpellID spellID, BattleHex destination
 
 	for(const CSpell::TAnimation & animation : spell->animationInfo.affect)
 	{
-		displaySpellAnimation(animation, destinationTile, areaEffect);
+		displaySpellAnimation(animation, destinationTile);
 	}
 }
 
-void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect)
+void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
 {
 	const CSpell * spell = spellID.toSpell();
 
@@ -1474,7 +1474,7 @@ void CBattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTil
 
 	for(const CSpell::TAnimation & animation : spell->animationInfo.hit)
 	{
-		displaySpellAnimation(animation, destinationTile, areaEffect);
+		displaySpellAnimation(animation, destinationTile);
 	}
 }
 
@@ -1586,9 +1586,10 @@ void CBattleInterface::activateStack()
 		stackCanCastSpell = true;
 		if(randomSpellcaster)
 			creatureSpellToCast = -1; //spell will be set later on cast
-
-		creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
+		else
+			creatureSpellToCast = curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
 		//TODO: what if creature can cast BOTH random genie spell and aimed spell?
+		//TODO: faerie dragon type spell should be selected by server
 	}
 	else
 	{
@@ -1633,26 +1634,15 @@ void CBattleInterface::getPossibleActionsForStack(const CStack * stack)
 		{
 			if (stack->hasBonusOfType (Bonus::SPELLCASTER))
 			{
-				 //TODO: poll possible spells
-				const CSpell * spell;
-				BonusList spellBonuses = *stack->getBonuses (Selector::type(Bonus::SPELLCASTER));
-				for (Bonus * spellBonus : spellBonuses)
+				if(creatureSpellToCast != -1)
 				{
-					spell = CGI->spellh->objects[spellBonus->subtype];
-					switch (spellBonus->subtype)
-					{
-						case SpellID::REMOVE_OBSTACLE:
-							possibleActions.push_back (OBSTACLE);
-							break;
-						default:
-							possibleActions.push_back (selectionTypeByPositiveness (*spell));
-							break;
-					}
-
+					const CSpell * spell = SpellID(creatureSpellToCast).toSpell();
+					PossibleActions act = getCasterAction(spell, stack, ECastingMode::CREATURE_ACTIVE_CASTING);
+					if(act == NO_LOCATION)
+						logGlobal->error("NO_LOCATION action target is not yet supported for creatures");
+					else
+						possibleActions.push_back(act);
 				}
-				std::sort(possibleActions.begin(), possibleActions.end());
-				auto it = std::unique (possibleActions.begin(), possibleActions.end());
-				possibleActions.erase (it, possibleActions.end());
 			}
 			if (stack->hasBonusOfType (Bonus::RANDOM_SPELLCASTER))
 				possibleActions.push_back (RANDOM_GENIE_SPELL);
@@ -1949,21 +1939,6 @@ void CBattleInterface::bTacticNextStack(const CStack *current /*= nullptr*/)
 
 }
 
-CBattleInterface::PossibleActions CBattleInterface::selectionTypeByPositiveness(const CSpell & spell)
-{
-	switch(spell.positiveness)
-	{
-	case CSpell::NEGATIVE :
-		return HOSTILE_CREATURE_SPELL;
-	case CSpell::NEUTRAL:
-		return ANY_CREATURE;
-	case CSpell::POSITIVE:
-		return FRIENDLY_CREATURE_SPELL;
-	}
-	assert(0);
-	return NO_LOCATION; //should never happen
-}
-
 std::string formatDmgRange(std::pair<ui32, ui32> dmgRange)
 {
 	if(dmgRange.first != dmgRange.second)
@@ -2076,27 +2051,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 					legalAction = true;
 				}
 				break;
-			case ANY_CREATURE:
-				if (shere && shere->alive() && isCastingPossibleHere (sactive, shere, myNumber))
+			case AIMED_SPELL_CREATURE:
+				if (shere && isCastingPossibleHere (sactive, shere, myNumber))
 					legalAction = true;
 				break;
-			case HOSTILE_CREATURE_SPELL:
-				if (shere && shere->alive() && !ourStack && isCastingPossibleHere (sactive, shere, myNumber))
-					legalAction = true;
-				break;
-			case FRIENDLY_CREATURE_SPELL:
-			{
-				if (isCastingPossibleHere (sactive, shere, myNumber)) //need to be called before sp is determined
-				{
-					bool rise = false; //TODO: can you imagine rising hostile creatures?
-					sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo];
-					if (sp && sp->isRisingSpell())
-							rise = true;
-					if (shere && (shere->alive() || rise) && ourStack)
-						legalAction = true;
-				}
-				break;
-			}
 			case RANDOM_GENIE_SPELL:
 			{
 				if (shere && ourStack && shere != sactive) //only positive spells for other allied creatures
@@ -2134,30 +2092,11 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 					notLegal = true;
 				break;
 			case FREE_LOCATION:
+				legalAction = true;
+				if(!isCastingPossibleHere(sactive, shere, myNumber))
 				{
-					ui8 side = curInt->cb->battleGetMySide();
-					auto hero = curInt->cb->battleGetMyHero();
-					assert(!creatureCasting); //we assume hero casts this spell
-					assert(hero);
-
-					legalAction = true;
-					bool hexesOutsideBattlefield = false;
-					auto tilesThatMustBeClear = sp->rangeInHexes(myNumber, hero->getSpellSchoolLevel(sp), side, &hexesOutsideBattlefield);
-					for(BattleHex hex : tilesThatMustBeClear)
-					{
-						if(curInt->cb->battleGetStackByPos(hex, true)  ||  !!curInt->cb->battleGetObstacleOnPos(hex, false)
-						 || !hex.isAvailable())
-						{
-							legalAction = false;
-							notLegal = true;
-						}
-					}
-
-					if(hexesOutsideBattlefield)
-					{
-						legalAction = false;
-						notLegal = true;
-					}
+					legalAction = false;
+					notLegal = true;
 				}
 				break;
 			case CATAPULT:
@@ -2281,9 +2220,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				consoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % sactive->shots % estDmgText).str();
 			}
 				break;
-			case HOSTILE_CREATURE_SPELL:
-			case FRIENDLY_CREATURE_SPELL:
-			case ANY_CREATURE:
+			case AIMED_SPELL_CREATURE:
 				sp = CGI->spellh->objects[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo]; //necessary if creature has random Genie spell at same time
 				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % sp->name % shere->getName()); //Cast %s on %s
 				switch (sp->id)
@@ -2324,7 +2261,6 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				isCastingPossible = true;
 				break;
 			case FREE_LOCATION:
-				//cursorFrame = ECursor::SPELLBOOK;
 				consoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % sp->name); //Cast %s
 				isCastingPossible = true;
 				break;
@@ -2357,9 +2293,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 	{
 		switch (illegalAction)
 		{
-			case ANY_CREATURE:
-			case HOSTILE_CREATURE_SPELL:
-			case FRIENDLY_CREATURE_SPELL:
+			case AIMED_SPELL_CREATURE:
 			case RANDOM_GENIE_SPELL:
 				cursorFrame = ECursor::COMBAT_BLOCKED;
 				consoleMsg = CGI->generaltexth->allTexts[23];
@@ -2495,7 +2429,7 @@ bool CBattleInterface::isCastingPossibleHere (const CStack * sactive, const CSta
 
 	if (sp)
 	{
-		const ISpellCaster * caster = creatureCasting ? dynamic_cast<const ISpellCaster *>(sactive) : dynamic_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
+		const ISpellCaster * caster = creatureCasting ? static_cast<const ISpellCaster *>(sactive) : static_cast<const ISpellCaster *>(curInt->cb->battleGetMyHero());
 		if(caster == nullptr)
 		{
 			isCastingPossible = false;//just in case
@@ -3076,18 +3010,27 @@ void CBattleInterface::showHighlightedHexes(SDL_Surface * to)
 			}
 			if (settings["battle"]["mouseShadow"].Bool())
 			{
-				if(spellToCast) //when casting spell
+				const ISpellCaster * caster = nullptr;
+				const CSpell * spell = nullptr;
+
+                if(spellToCast)//hero casts spell
 				{
-					//calculating spell school level
-					const CSpell & spToCast =  *CGI->spellh->objects[spellToCast->additionalInfo];
-					ui8 schoolLevel = 0;
+					spell = SpellID(spellToCast->additionalInfo).toSpell();
+					caster = activeStack->attackerOwned ? attackingHeroInstance : defendingHeroInstance;
+				}
+				else if(creatureSpellToCast >= 0 && stackCanCastSpell && creatureCasting)//stack casts spell
+				{
+                    spell = SpellID(creatureSpellToCast).toSpell();
+                    caster = activeStack;
+				}
 
-					auto caster = activeStack->attackerOwned ? attackingHeroInstance : defendingHeroInstance;
-					if (caster)
-						schoolLevel = caster->getSpellSchoolLevel(&spToCast);
+				if(caster && spell) //when casting spell
+				{
+					//calculating spell school level
+					ui8 schoolLevel = caster->getSpellSchoolLevel(spell);
 
 					// printing shaded hex(es)
-					auto shaded = spToCast.rangeInHexes(currentlyHoveredHex, schoolLevel, curInt->cb->battleGetMySide());
+					auto shaded = spell->rangeInHexes(currentlyHoveredHex, schoolLevel, curInt->cb->battleGetMySide());
 					for(BattleHex shadedHex : shaded)
 					{
 						if ((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH -1))

+ 15 - 14
client/battle/CBattleInterface.h

@@ -114,9 +114,10 @@ class CBattleInterface : public CIntObject
 		INVALID = -1, CREATURE_INFO,
 		MOVE_TACTICS, CHOOSE_TACTICS_STACK,
 		MOVE_STACK, ATTACK, WALK_AND_ATTACK, ATTACK_AND_RETURN, SHOOT, //OPEN_GATE, //we can open castle gate during siege
-		NO_LOCATION, ANY_LOCATION, FRIENDLY_CREATURE_SPELL, HOSTILE_CREATURE_SPELL, RISING_SPELL, ANY_CREATURE, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
+		NO_LOCATION, ANY_LOCATION, OBSTACLE, TELEPORT, SACRIFICE, RANDOM_GENIE_SPELL,
 		FREE_LOCATION, //used with Force Field and Fire Wall - all tiles affected by spell must be free
-		CATAPULT, HEAL, RISE_DEMONS
+		CATAPULT, HEAL, RISE_DEMONS,
+		AIMED_SPELL_CREATURE
 	};
 private:
 	SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
@@ -158,7 +159,6 @@ private:
 	bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
 	bool creatureCasting; //if true, stack currently aims to cats a spell
 	bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
-	PossibleActions spellSelMode;
 	BattleAction * spellToCast; //spell for which player is choosing destination
 	const CSpell * sp; //spell pointer for convenience
 	si32 creatureSpellToCast;
@@ -194,7 +194,7 @@ private:
 		const CBattleInterface * owner;
 	public:
 		const CGTownInstance * town; //besieged town
-		
+
 		SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-tor
 		~SiegeHelper(); //d-tor
 
@@ -259,6 +259,7 @@ private:
 	void redrawBackgroundWithHexes(const CStack * activeStack);
 	/** End of battle screen blitting methods */
 
+	PossibleActions getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const;
 public:
 	std::list<std::pair<CBattleAnimation *, bool> > pendingAnims; //currently displayed animations <anim, initialized>
 	void addNewAnim(CBattleAnimation * anim); //adds new anim to pendingAnims
@@ -334,20 +335,20 @@ public:
 	void spellCast(const BattleSpellCast * sc); //called when a hero casts a spell
 	void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
 	void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
-	void displayEffect(ui32 effect, int destTile, bool areaEffect = true); //displays custom effect on the battlefield
-	
-	void displaySpellCast(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s cast animation
-	void displaySpellEffect(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
-	void displaySpellHit(SpellID spellID, BattleHex destinationTile, bool areaEffect = true); //displays spell`s affected animation
-		
-	void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile, bool areaEffect = true);
-	
+	void displayEffect(ui32 effect, int destTile); //displays custom effect on the battlefield
+
+	void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
+	void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
+	void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
+
+	void displaySpellAnimation(const CSpell::TAnimation & animation, BattleHex destinationTile);
+
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 	void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
 	void endAction(const BattleAction* action);
 	void hideQueue();
 	void showQueue();
-	PossibleActions selectionTypeByPositiveness(const CSpell & spell);
+
 	Rect hexPosition(BattleHex hex) const;
 
 	void handleHex(BattleHex myNumber, int eventType);
@@ -365,7 +366,7 @@ public:
 	friend class CPlayerInterface;
 	friend class CButton;
 	friend class CInGameConsole;
-	
+
 	friend class CBattleResultWindow;
 	friend class CBattleHero;
 	friend class CSpellEffectAnimation;

+ 13 - 0
lib/BattleState.cpp

@@ -1243,6 +1243,19 @@ const PlayerColor CStack::getOwner() const
 	return owner;
 }
 
+void CStack::getCasterName(MetaString & text) const
+{
+	//always plural name in case of spell cast.
+	text.addReplacement(MetaString::CRE_PL_NAMES, type->idNumber.num);
+}
+
+void CStack::getCastDescription(const CSpell * spell, const std::vector<const CStack*> & attacked, MetaString & text) const
+{
+	text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s
+	//todo: use text 566 for single creature
+	getCasterName(text);
+	text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
+}
 
 bool CMP_stack::operator()( const CStack* a, const CStack* b )
 {

+ 4 - 0
lib/BattleState.h

@@ -249,6 +249,10 @@ public:
 
 	const PlayerColor getOwner() const override;
 
+	void getCasterName(MetaString & text) const override;
+
+	void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
+
 	///stack will be ghost in next battle state update
 	void makeGhost();
 

+ 32 - 183
lib/CBattleCallback.cpp

@@ -70,7 +70,8 @@ namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO
 		std::make_pair(45,  EWallPart::INDESTRUCTIBLE_PART),
 		std::make_pair(62,  EWallPart::INDESTRUCTIBLE_PART),
 		std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART)
+		std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART),
+		std::make_pair(165, EWallPart::INDESTRUCTIBLE_PART)
 	};
 
 	static EWallPart::EWallPart hexToWallPart(BattleHex hex)
@@ -345,9 +346,15 @@ const IBonusBearer * CBattleInfoEssentials::getBattleNode() const
 	return getBattle();
 }
 
-ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(PlayerColor player, ECastingMode::ECastingMode mode) const
+ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
 {
 	RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
+	if(caster == nullptr)
+	{
+		logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastSpell: no spellcaster.";
+		return ESpellCastProblem::INVALID;
+	}
+	const PlayerColor player = caster->getOwner();
 	const ui8 side = playerToSide(player);
 	if(!battleDoWeKnowAbout(side))
 	{
@@ -355,16 +362,17 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(Pla
 		return ESpellCastProblem::INVALID;
 	}
 
+	if(battleTacticDist())
+		return ESpellCastProblem::ONGOING_TACTIC_PHASE;
+
 	switch (mode)
 	{
 	case ECastingMode::HERO_CASTING:
 		{
-			if(battleTacticDist())
-				return ESpellCastProblem::ONGOING_TACTIC_PHASE;
 			if(battleCastSpells(side) > 0)
 				return ESpellCastProblem::ALREADY_CASTED_THIS_TURN;
 
-			auto hero = battleGetFightingHero(side);
+			auto hero = dynamic_cast<const CGHeroInstance *>(caster);
 
 			if(!hero)
 				return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
@@ -1642,7 +1650,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	if(!battleDoWeKnowAbout(side))
 		return ESpellCastProblem::INVALID;
 
-	ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(player, mode);
+	ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode);
 	if(genProblem != ESpellCastProblem::OK)
 		return genProblem;
 
@@ -1663,128 +1671,19 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 	if(!spell->combatSpell)
 		return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
 
-	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, player);
+	const ESpellCastProblem::ESpellCastProblem specificProblem = spell->canBeCast(this, mode, caster);
 
 	if(specificProblem != ESpellCastProblem::OK)
 		return specificProblem;
 
-	if(spell->isNegative() || spell->hasEffects())
-	{
-		bool allStacksImmune = true;
-		//we are interested only in enemy stacks when casting offensive spells
-		//TODO: should hero be able to cast non-smart negative spell if all enemy stacks are immune?
-		auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks();
-		for(auto stack : stacks)
-		{
-			if(ESpellCastProblem::OK == spell->isImmuneByStack(caster, stack))
-			{
-				allStacksImmune = false;
-				break;
-			}
-		}
-
-		if(allStacksImmune)
-			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-	}
-
-	if(battleMaxSpellLevel(side) < spell->level) //effect like Recanter's Cloak or Orb of Inhibition
+	//effect like Recanter's Cloak. Blocks also passive casting.
+	//TODO: check creature abilities to block
+	if(battleMaxSpellLevel(side) < spell->level)
 		return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
 
-	//checking if there exists an appropriate target
-	switch(spell->getTargetType())
-	{
-	case CSpell::CREATURE:
-		if(mode == ECastingMode::HERO_CASTING)
-		{
-			const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
-			bool targetExists = false;
-
-			for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway
-			{
-				bool immune =  ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
-				bool casterStack = stack->owner == caster->getOwner();
-
-				if(!immune)
-				{
-					switch (spell->positiveness)
-					{
-					case CSpell::POSITIVE:
-						if(casterStack || !ti.smart)
-						{
-							targetExists = true;
-							break;
-						}
-						break;
-					case CSpell::NEUTRAL:
-							targetExists = true;
-							break;
-					case CSpell::NEGATIVE:
-						if(!casterStack || !ti.smart)
-						{
-							targetExists = true;
-							break;
-						}
-						break;
-					}
-				}
-			}
-			if(!targetExists)
-			{
-				return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-			}
-		}
-		break;
-	case CSpell::OBSTACLE:
-		break;
-	}
-
 	return ESpellCastProblem::OK;
 }
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const
-{
-	std::vector<BattleHex> ret;
-	RETURN_IF_NOT_BATTLE(ret);
-
-	switch(spell->getTargetType())
-	{
-	case CSpell::CREATURE:
-		{
-			const CGHeroInstance * caster = battleGetFightingHero(playerToSide(player)); //TODO
-			const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell));
-
-			for(const CStack * stack : battleAliveStacks())
-			{
-				bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack);
-				bool casterStack = stack->owner == caster->getOwner();
-
-				if(!immune)
-					switch (spell->positiveness)
-					{
-					case CSpell::POSITIVE:
-						if(casterStack || ti.smart)
-							ret.push_back(stack->position);
-						break;
-
-					case CSpell::NEUTRAL:
-						ret.push_back(stack->position);
-						break;
-
-					case CSpell::NEGATIVE:
-						if(!casterStack || ti.smart)
-							ret.push_back(stack->position);
-						break;
-					}
-			}
-		}
-		break;
-	default:
-		logGlobal->errorStream() << "FIXME " << __FUNCTION__ << " doesn't work with target type " << spell->getTargetType();
-	}
-
-	return ret;
-}
-
 ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 {
 	RETURN_IF_NOT_BATTLE(-1);
@@ -1821,71 +1720,12 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell
 		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;
-
-	if(spell->getTargetType() == CSpell::OBSTACLE)
-	{
-		if(spell->id == SpellID::REMOVE_OBSTACLE)
-		{
-			if(auto obstacle = battleGetObstacleOnPos(dest, false))
-			{
-				switch (obstacle->obstacleType)
-				{
-				case CObstacleInstance::ABSOLUTE_OBSTACLE: //cliff-like obstacles can't be removed
-				case CObstacleInstance::MOAT:
-					return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-				case CObstacleInstance::USUAL:
-					return ESpellCastProblem::OK;
-
-// 				//TODO FIRE_WALL only for ADVANCED level casters
-// 				case CObstacleInstance::FIRE_WALL:
-// 					return
-// 				//TODO other magic obstacles for EXPERT
-// 				case CObstacleInstance::QUICKSAND:
-// 				case CObstacleInstance::LAND_MINE:
-// 				case CObstacleInstance::FORCE_FIELD:
-// 					return
-				default:
-//					assert(0);
-					return ESpellCastProblem::OK;
-				}
-			}
-		}
-		//isObstacleOnTile(dest)
-		//
-		//
-		//TODO
-		//assert that it's remove obstacle
-		//rules whether we can remove spell-created obstacle
-		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-	}
 
+	ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode);
+	if(problem != ESpellCastProblem::OK)
+		return problem;
 
-	//get dead stack if we cast resurrection or animate dead
-	const CStack *deadStack = getStackIf([dest](const CStack *s) { return !s->alive() && s->coversPos(dest); });
-	const CStack *aliveStack = getStackIf([dest](const CStack *s) { return s->alive() && s->coversPos(dest);});
-
-
-	if(spell->isRisingSpell())
-	{
-		if(!deadStack && !aliveStack)
-			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;
-	}
-	else if(spell->getTargetType() == CSpell::CREATURE)
-	{
-		if(!aliveStack)
-			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-		if(spell->isNegative() && aliveStack->owner == player)
-			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-		if(spell->isPositive() && aliveStack->owner != player)
-			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-	}
-	return spell->isImmuneAt(this, caster, mode, dest);
+	return spell->canBeCastAt(this, caster, mode, dest);
 }
 
 const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
@@ -2249,7 +2089,16 @@ bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProb
 {
 	RETURN_IF_NOT_BATTLE(false);
 	ASSERT_IF_CALLED_WITH_PLAYER
-	auto problem = CBattleInfoCallback::battleCanCastSpell(*player, ECastingMode::HERO_CASTING);
+
+	const CGHeroInstance * hero = battleGetMyHero();
+	if(!hero)
+	{
+		if(outProblem)
+			*outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
+		return false;
+	}
+
+	auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
 	if(outProblem)
 		*outProblem = problem;
 

+ 1 - 3
lib/CBattleCallback.h

@@ -212,7 +212,6 @@ public:
 	TStacks battleAliveStacks(ui8 side) const;
 	const CStack * battleGetStackByID(int ID, bool onlyAlive = true) const; //returns stack info by given ID
 	bool battleIsObstacleVisibleForSide(const CObstacleInstance & coi, BattlePerspective::BattlePerspective side) const;
-	//ESpellCastProblem::ESpellCastProblem battleCanCastSpell(int player, ECastingMode::ECastingMode mode) const; //Checks if player is able to cast spells (at all) at the moment
 };
 
 struct DLL_LINKAGE BattleAttackInfo
@@ -282,10 +281,9 @@ public:
 	//*** MAGIC
 	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 battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
 	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(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
 	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;

+ 0 - 1
lib/CObstacleInstance.cpp

@@ -131,7 +131,6 @@ std::vector<BattleHex> SpellCreatedObstacle::getAffectedTiles() const
 		return std::vector<BattleHex>(1, pos);
 	case FORCE_FIELD:
 		return SpellID(SpellID::FORCE_FIELD).toSpell()->rangeInHexes(pos, spellLevel, casterSide);
-		//TODO Fire Wall
 	default:
 		assert(0);
 		return std::vector<BattleHex>();

+ 4 - 2
lib/NetPacks.h

@@ -1524,7 +1524,6 @@ struct BattleSpellCast : public CPackForClient//3009
 	DLL_LINKAGE void applyGs(CGameState *gs);
 	void applyCl(CClient *cl);
 
-	si32 dmgToDisplay; //this amount will be displayed as amount of damage dealt by spell
 	ui8 side; //which hero did cast spell: 0 - attacker, 1 - defender
 	ui32 id; //id of spell
 	ui8 skill; //caster's skill level
@@ -1534,9 +1533,12 @@ struct BattleSpellCast : public CPackForClient//3009
 	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 cast by hero, otherwise by a creature
+
+	std::vector<MetaString> battleLog;
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & dmgToDisplay & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
+		h & side & id & skill & manaGained & tile & customEffects & affectedCres & casterStack & castByHero;
+		h & battleLog;
 	}
 };
 

+ 16 - 0
lib/NetPacksLib.cpp

@@ -1227,6 +1227,15 @@ DLL_LINKAGE void BattleNextRound::applyGs( CGameState *gs )
 		s->counterAttacksTotalCache = 0;
 		// new turn effects
 		s->battleTurnPassed();
+
+		if(s->alive() && vstd::contains(s->state, EBattleStackState::CLONED))
+		{
+			//cloned stack has special lifetime marker
+			//check it after bonuses updated in battleTurnPassed()
+
+			if(!s->hasBonus(Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))))
+				s->makeGhost();
+		}
 	}
 
 	for(auto &obst : gs->curB->obstacles)
@@ -1712,6 +1721,13 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs )
 					toRemove->cloneID = -1;
 				}
 
+				//cleanup remaining clone links if any
+				for(CStack * s : gs->curB->stacks)
+				{
+					if(s->cloneID == toRemove->ID)
+						s->cloneID = -1;
+				}
+
 				break;
 			}
 		}

+ 19 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -954,6 +954,25 @@ const PlayerColor CGHeroInstance::getOwner() const
 	return tempOwner;
 }
 
+void CGHeroInstance::getCasterName(MetaString & text) const
+{
+	//FIXME: use local name, MetaString need access to gamestate as hero name is part of map object
+
+    text.addReplacement(name);
+}
+
+void CGHeroInstance::getCastDescription(const CSpell * spell, const std::vector<const CStack*> & attacked, MetaString & text) const
+{
+	const bool singleTarget = attacked.size() == 1;
+	const int textIndex = singleTarget ? 195 : 196;
+
+	text.addTxt(MetaString::GENERAL_TXT, textIndex);
+	getCasterName(text);
+	text.addReplacement(MetaString::SPELL_NAME, spell->id.toEnum());
+	if(singleTarget)
+		text.addReplacement(MetaString::CRE_PL_NAMES, attacked.at(0)->getCreature()->idNumber.num);
+}
+
 bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
 {
 	if(nullptr == getArt(ArtifactPosition::SPELLBOOK))

+ 3 - 0
lib/mapObjects/CGHeroInstance.h

@@ -246,6 +246,9 @@ public:
 
 	const PlayerColor getOwner() const override;
 
+	void getCasterName(MetaString & text) const override;
+	void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const override;
+
 	void deserializationFix();
 
 	void initObj(CRandomGenerator & rand) override;

+ 84 - 2
lib/spells/AdventureSpellMechanics.cpp

@@ -15,12 +15,94 @@
 #include "../CRandomGenerator.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../NetPacks.h"
-#include "../BattleState.h"
-#include "../CGameState.h"
 #include "../CGameInfoCallback.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
 
+///AdventureSpellMechanics
+bool AdventureSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(!owner->isAdventureSpell())
+	{
+		env->complain("Attempt to cast non adventure spell in adventure mode");
+		return false;
+	}
+
+	const CGHeroInstance * caster = parameters.caster;
+
+	if(caster->inTownGarrison)
+	{
+		env->complain("Attempt to cast an adventure spell in town garrison");
+		return false;
+	}
+
+	const int cost = caster->getSpellCost(owner);
+
+	if(!caster->canCastThisSpell(owner))
+	{
+		env->complain("Hero cannot cast this spell!");
+		return false;
+	}
+
+	if(caster->mana < cost)
+	{
+		env->complain("Hero doesn't have enough spell points to cast this spell!");
+		return false;
+	}
+
+	{
+		AdvmapSpellCast asc;
+		asc.caster = caster;
+		asc.spellID = owner->id;
+		env->sendAndApply(&asc);
+	}
+
+	switch(applyAdventureEffects(env, parameters))
+	{
+	case ESpellCastResult::OK:
+		{
+			SetMana sm;
+			sm.hid = caster->id;
+			sm.absolute = false;
+			sm.val = -cost;
+			env->sendAndApply(&sm);
+			return true;
+		}
+		break;
+	case ESpellCastResult::CANCEL:
+		return true;
+	}
+	return false;
+}
+
+ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(owner->hasEffects())
+	{
+		const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+
+		std::vector<Bonus> bonuses;
+
+		owner->getEffects(bonuses, schoolLevel);
+
+		for(Bonus b : bonuses)
+		{
+			GiveBonus gb;
+			gb.id = parameters.caster->id.getNum();
+			gb.bonus = b;
+			env->sendAndApply(&gb);
+		}
+
+		return ESpellCastResult::OK;
+	}
+	else
+	{
+		//There is no generic algorithm of adventure cast
+		env->complain("Unimplemented adventure spell");
+		return ESpellCastResult::ERROR;
+	}
+}
+
 ///SummonBoatMechanics
 ESpellCastResult SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
 {

+ 28 - 13
lib/spells/AdventureSpellMechanics.h

@@ -10,47 +10,62 @@
 
 #pragma once
 
-#include "CDefaultSpellMechanics.h"
+#include "ISpellMechanics.h"
 
-class ISpellMechanics;
-class DefaultSpellMechanics;
+enum class ESpellCastResult
+{
+	OK,
+	CANCEL,//cast failed but it is not an error
+	ERROR//internal error occurred
+};
+
+class DLL_LINKAGE AdventureSpellMechanics: public IAdventureSpellMechanics
+{
+public:
+	AdventureSpellMechanics(CSpell * s): IAdventureSpellMechanics(s){};
+
+	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
+protected:
+	///actual adventure cast implementation
+	virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
+};
 
-class DLL_LINKAGE SummonBoatMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE SummonBoatMechanics : public AdventureSpellMechanics
 {
 public:
-	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	SummonBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
 protected:
 	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
 };
 
-class DLL_LINKAGE ScuttleBoatMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE ScuttleBoatMechanics : public AdventureSpellMechanics
 {
 public:
-	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ScuttleBoatMechanics(CSpell * s): AdventureSpellMechanics(s){};
 protected:
 	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
 };
 
-class DLL_LINKAGE DimensionDoorMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE DimensionDoorMechanics : public AdventureSpellMechanics
 {
 public:
-	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	DimensionDoorMechanics(CSpell * s): AdventureSpellMechanics(s){};
 protected:
 	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
 };
 
-class DLL_LINKAGE TownPortalMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE TownPortalMechanics : public AdventureSpellMechanics
 {
 public:
-	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	TownPortalMechanics(CSpell * s): AdventureSpellMechanics(s){};
 protected:
 	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
 };
 
-class DLL_LINKAGE ViewMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE ViewMechanics : public AdventureSpellMechanics
 {
 public:
-	ViewMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ViewMechanics(CSpell * s): AdventureSpellMechanics(s){};
 protected:
 	ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;
 	virtual bool filterObject(const CGObjectInstance * obj, const int spellLevel) const = 0;

+ 338 - 154
lib/spells/BattleSpellMechanics.cpp

@@ -41,9 +41,7 @@ void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 
 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); //???
+	return parameters.getEffectValue();
 }
 
 ///AntimagicMechanics
@@ -62,12 +60,12 @@ void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast
 }
 
 ///ChainLightningMechanics
-std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+std::vector<const CStack *> ChainLightningMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
-	std::set<const CStack* > attackedCres;
+	std::vector<const CStack *> res;
 
 	std::set<BattleHex> possibleHexes;
-	for(auto stack : ctx.cb->battleGetAllStacks())
+	for(auto stack : cb->battleGetAllStacks())
 	{
 		if(stack->isValidTarget())
 		{
@@ -82,20 +80,20 @@ std::set<const CStack *> ChainLightningMechanics::getAffectedStacks(SpellTargeti
 	BattleHex lightningHex = ctx.destination;
 	for(int i = 0; i < targetsOnLevel[ctx.schoolLvl]; ++i)
 	{
-		auto stack = ctx.cb->battleGetStackByPos(lightningHex, true);
+		auto stack = cb->battleGetStackByPos(lightningHex, true);
 		if(!stack)
 			break;
-		attackedCres.insert (stack);
+		res.push_back(stack);
 		for(auto hex : stack->getHexes())
 		{
-			possibleHexes.erase(hex); //can't hit same place twice
+			possibleHexes.erase(hex); //can't hit same stack twice
 		}
 		if(possibleHexes.empty()) //not enough targets
 			break;
-		lightningHex = BattleHex::getClosestTile(stack->attackerOwned, ctx.destination, possibleHexes);
+		lightningHex = BattleHex::getClosestTile(stack->attackerOwned, lightningHex, possibleHexes);
 	}
 
-	return attackedCres;
+	return res;
 }
 
 ///CloneMechanics
@@ -131,6 +129,13 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
 	ssp.val = bsa.newStackID;
 	ssp.absolute = 1;
 	env->sendAndApply(&ssp);
+
+	SetStackEffect sse;
+	sse.stacks.push_back(bsa.newStackID);
+	Bonus lifeTimeMarker(Bonus::N_TURNS, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id.num);
+	lifeTimeMarker.turnsRemain = parameters.enchantPower;
+	sse.effect.push_back(lifeTimeMarker);
+	env->sendAndApply(&sse);
 }
 
 ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
@@ -165,15 +170,8 @@ ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpel
 void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);
-	doDispell(battle, packet, [](const Bonus * b) -> bool
-	{
-		if(b->source == Bonus::SPELL_EFFECT)
-		{
-			CSpell * sp = SpellID(b->sid).toSpell();
-			return sp->isNegative();
-		}
-		return false; //not a spell effect
-	});
+
+	doDispell(battle, packet, dispellSelector);
 }
 
 HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) const
@@ -181,6 +179,25 @@ HealingSpellMechanics::EHealLevel CureMechanics::getHealLevel(int effectLevel) c
 	return EHealLevel::HEAL;
 }
 
+bool CureMechanics::dispellSelector(const Bonus * b)
+{
+	if(b->source == Bonus::SPELL_EFFECT)
+	{
+		CSpell * sp = SpellID(b->sid).toSpell();
+		return sp->isNegative();
+	}
+	return false; //not a spell effect
+}
+
+ESpellCastProblem::ESpellCastProblem CureMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
+{
+	//Selector method name is ok as cashing string. --AVS
+	if(!obj->canBeHealed() && !obj->hasBonus(dispellSelector, "CureMechanics::dispellSelector"))
+		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
+
+	return DefaultSpellMechanics::isImmuneByStack(caster, obj);
+}
+
 ///DispellMechanics
 void DispellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
@@ -218,8 +235,6 @@ ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISp
 
 void DispellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
-	DefaultSpellMechanics::applyBattleEffects(env, parameters, ctx);
-
 	if(parameters.spellLvl > 2)
 	{
 		//expert DISPELL also removes spell-created obstacles
@@ -326,8 +341,14 @@ void EarthquakeMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
 	env->sendAndApply(&ca);
 }
 
-ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
+	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
+	{
+		logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
+		return ESpellCastProblem::INVALID;
+	}
+
 	if(nullptr == cb->battleGetDefendedTown())
 	{
 		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
@@ -338,17 +359,27 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattl
 		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
 
-	CSpell::TargetInfo ti(owner, 0);//TODO: use real spell level
+	CSpell::TargetInfo ti(owner, caster->getSpellSchoolLevel(owner));
 	if(ti.smart)
 	{
 		//if spell targeting is smart, then only attacker can use it
-		if(cb->playerToSide(player) != 0)
+		if(cb->playerToSide(caster->getOwner()) != 0)
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
 
+	const auto attackableBattleHexes = cb->getAttackableBattleHexes();
+
+	if(attackableBattleHexes.empty())
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+
 	return ESpellCastProblem::OK;
 }
 
+bool EarthquakeMechanics::requiresCreatureTarget() const
+{
+	return false;
+}
+
 ///HypnotizeMechanics
 ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
@@ -366,105 +397,115 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const I
 }
 
 ///ObstacleMechanics
-void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+ESpellCastProblem::ESpellCastProblem ObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
 {
-	auto placeObstacle = [&, this](const BattleHex & pos)
-	{
-		static int obstacleIdToGive =  parameters.cb->obstacles.size()
-									? (parameters.cb->obstacles.back()->uniqueID+1)
-									: 0;
+	ui8 side = cb->playerToSide(ctx.caster->getOwner());
 
-		auto obstacle = std::make_shared<SpellCreatedObstacle>();
-		switch(owner->id) // :/
-		{
-		case SpellID::QUICKSAND:
-			obstacle->obstacleType = CObstacleInstance::QUICKSAND;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::LAND_MINE:
-			obstacle->obstacleType = CObstacleInstance::LAND_MINE;
-			obstacle->turnsRemaining = -1;
-			obstacle->visibleForAnotherSide = false;
-			break;
-		case SpellID::FIRE_WALL:
-			obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		case SpellID::FORCE_FIELD:
-			obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
-			obstacle->turnsRemaining = 2;
-			obstacle->visibleForAnotherSide = true;
-			break;
-		default:
-			//this function cannot be used with spells that do not create obstacles
-			assert(0);
-		}
+	bool hexesOutsideBattlefield = false;
 
-		obstacle->pos = pos;
-		obstacle->casterSide = parameters.casterSide;
-		obstacle->ID = owner->id;
-		obstacle->spellLevel = parameters.effectLevel;
-		obstacle->casterSpellPower = parameters.effectPower;
-		obstacle->uniqueID = obstacleIdToGive++;
+	auto tilesThatMustBeClear = owner->rangeInHexes(ctx.destination, ctx.schoolLvl, side, &hexesOutsideBattlefield);
 
-		BattleObstaclePlaced bop;
-		bop.obstacle = obstacle;
-		env->sendAndApply(&bop);
-	};
+	for(const BattleHex & hex : tilesThatMustBeClear)
+		if(!isHexAviable(cb, hex, ctx.ti.clearAffected))
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 
-	const BattleHex destination = parameters.getFirstDestinationHex();
+	if(hexesOutsideBattlefield)
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+
+	return ESpellCastProblem::OK;
+}
 
-	switch(owner->id)
+bool ObstacleMechanics::isHexAviable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear)
+{
+	if(!hex.isAvailable())
+		return false;
+
+	if(!mustBeClear)
+		return true;
+
+	if(cb->battleGetStackByPos(hex, true) || !!cb->battleGetObstacleOnPos(hex, false))
+		return false;
+
+	if(nullptr != cb->battleGetDefendedTown() && CGTownInstance::NONE != cb->battleGetDefendedTown()->fortLevel())
 	{
-	case SpellID::QUICKSAND:
-	case SpellID::LAND_MINE:
-		{
-			std::vector<BattleHex> availableTiles;
-			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
-			{
-				BattleHex hex = i;
-				if(hex.getX() > 2 && hex.getX() < 14 && !(parameters.cb->battleGetStackByPos(hex, false)) && !(parameters.cb->battleGetObstacleOnPos(hex, false)))
-					availableTiles.push_back(hex);
-			}
-			RandomGeneratorUtil::randomShuffle(availableTiles, env->getRandomGenerator());
+		EWallPart::EWallPart part = cb->battleHexToWallPart(hex);
 
-			const int patchesForSkill[] = {4, 4, 6, 8};
-			const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
+		if(part == EWallPart::INVALID)
+			return true;//no fortification here
+		else if(static_cast<int>(part) < 0)
+			return false;//indestuctible part (cant be checked by battleGetWallState)
+		else if(part == EWallPart::BOTTOM_TOWER || part == EWallPart::UPPER_TOWER)
+			return false;//destructible, but should not be available
+		else if(cb->battleGetWallState(part) != EWallState::DESTROYED && cb->battleGetWallState(part) != EWallState::NONE)
+			return false;
+	}
 
-			//land mines or quicksand patches are handled as spell created obstacles
-			for (int i = 0; i < patchesToPut; i++)
-				placeObstacle(availableTiles.at(i));
-		}
+	return true;
+}
 
-		break;
-	case SpellID::FORCE_FIELD:
-		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(destination, parameters.spellLvl, parameters.casterSide);
-			for(BattleHex hex : affectedHexes)
-				placeObstacle(hex);
-		}
-		break;
-	default:
-		assert(0);
+void ObstacleMechanics::placeObstacle(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const BattleHex & pos) const
+{
+	const int obstacleIdToGive = parameters.cb->obstacles.size()+1;
+
+	auto obstacle = std::make_shared<SpellCreatedObstacle>();
+	setupObstacle(obstacle.get());
+
+	obstacle->pos = pos;
+	obstacle->casterSide = parameters.casterSide;
+	obstacle->ID = owner->id;
+	obstacle->spellLevel = parameters.effectLevel;
+	obstacle->casterSpellPower = parameters.effectPower;
+	obstacle->uniqueID = obstacleIdToGive;
+
+	BattleObstaclePlaced bop;
+	bop.obstacle = obstacle;
+	env->sendAndApply(&bop);
+}
+
+///PatchObstacleMechanics
+void PatchObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	std::vector<BattleHex> availableTiles;
+	for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
+	{
+		BattleHex hex = i;
+		if(isHexAviable(parameters.cb, hex, true))
+			availableTiles.push_back(hex);
 	}
+	RandomGeneratorUtil::randomShuffle(availableTiles, env->getRandomGenerator());
+	const int patchesForSkill[] = {4, 4, 6, 8};
+	const int patchesToPut = std::min<int>(patchesForSkill[parameters.spellLvl], availableTiles.size());
+
+	//land mines or quicksand patches are handled as spell created obstacles
+	for (int i = 0; i < patchesToPut; i++)
+		placeObstacle(env, parameters, availableTiles.at(i));
 }
 
+///LandMineMechanics
+bool LandMineMechanics::requiresCreatureTarget() const
+{
+	return true;
+}
+
+void LandMineMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
+{
+	obstacle->obstacleType = CObstacleInstance::LAND_MINE;
+	obstacle->turnsRemaining = -1;
+	obstacle->visibleForAnotherSide = false;
+}
+
+///QuicksandMechanics
+bool QuicksandMechanics::requiresCreatureTarget() const
+{
+	return false;
+}
+
+void QuicksandMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
+{
+	obstacle->obstacleType = CObstacleInstance::QUICKSAND;
+	obstacle->turnsRemaining = -1;
+	obstacle->visibleForAnotherSide = false;
+}
 
 ///WallMechanics
 std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes) const
@@ -503,19 +544,136 @@ std::vector<BattleHex> WallMechanics::rangeInHexes(BattleHex centralHex, ui8 sch
 	return ret;
 }
 
+///FireWallMechanics
+bool FireWallMechanics::requiresCreatureTarget() const
+{
+	return true;
+}
+
+void FireWallMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	const BattleHex destination = parameters.getFirstDestinationHex();
+
+	if(!destination.isValid())
+	{
+		env->complain("Invalid destination for FIRE_WALL");
+		return;
+	}
+	//firewall is build from multiple obstacles - one fire piece for each affected hex
+	auto affectedHexes = owner->rangeInHexes(destination, parameters.spellLvl, parameters.casterSide);
+	for(BattleHex hex : affectedHexes)
+		placeObstacle(env, parameters, hex);
+}
+
+void FireWallMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
+{
+	obstacle->obstacleType = CObstacleInstance::FIRE_WALL;
+	obstacle->turnsRemaining = 2;
+	obstacle->visibleForAnotherSide = true;
+}
+
+///ForceFieldMechanics
+bool ForceFieldMechanics::requiresCreatureTarget() const
+{
+	return false;
+}
+
+void ForceFieldMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
+{
+	const BattleHex destination = parameters.getFirstDestinationHex();
+
+	if(!destination.isValid())
+	{
+		env->complain("Invalid destination for FORCE_FIELD");
+		return;
+	}
+	placeObstacle(env, parameters, destination);
+}
+
+void ForceFieldMechanics::setupObstacle(SpellCreatedObstacle * obstacle) const
+{
+	obstacle->obstacleType = CObstacleInstance::FORCE_FIELD;
+	obstacle->turnsRemaining = 2;
+	obstacle->visibleForAnotherSide = true;
+}
+
 ///RemoveObstacleMechanics
 void RemoveObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	if(auto obstacleToRemove = parameters.cb->battleGetObstacleOnPos(parameters.getFirstDestinationHex(), false))
 	{
-		ObstaclesRemoved obr;
-		obr.obstacles.insert(obstacleToRemove->uniqueID);
-		env->sendAndApply(&obr);
+		if(canRemove(obstacleToRemove.get(), parameters.spellLvl))
+		{
+			ObstaclesRemoved obr;
+			obr.obstacles.insert(obstacleToRemove->uniqueID);
+			env->sendAndApply(&obr);
+		}
+		else
+		{
+			env->complain("Cant remove this obstacle!");
+		}
 	}
 	else
 		env->complain("There's no obstacle to remove!");
 }
 
+ESpellCastProblem::ESpellCastProblem RemoveObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
+{
+	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
+	{
+		logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
+		return ESpellCastProblem::INVALID;
+	}
+
+	const int spellLevel = caster->getSpellSchoolLevel(owner);
+
+	for(auto obstacle : cb->battleGetAllObstacles())
+		if(canRemove(obstacle.get(), spellLevel))
+			return ESpellCastProblem::OK;
+
+	return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+}
+
+ESpellCastProblem::ESpellCastProblem RemoveObstacleMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
+{
+	if(auto obstacle = cb->battleGetObstacleOnPos(ctx.destination, false))
+		if(canRemove(obstacle.get(), ctx.schoolLvl))
+			return ESpellCastProblem::OK;
+
+	return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+}
+
+bool RemoveObstacleMechanics::canRemove(const CObstacleInstance * obstacle, const int spellLevel) const
+{
+	switch (obstacle->obstacleType)
+	{
+	case CObstacleInstance::ABSOLUTE_OBSTACLE: //cliff-like obstacles can't be removed
+	case CObstacleInstance::MOAT:
+		return false;
+	case CObstacleInstance::USUAL:
+		return true;
+	case CObstacleInstance::FIRE_WALL:
+		if(spellLevel >= 2)
+			return true;
+		break;
+	case CObstacleInstance::QUICKSAND:
+	case CObstacleInstance::LAND_MINE:
+	case CObstacleInstance::FORCE_FIELD:
+		if(spellLevel >= 3)
+			return true;
+		break;
+	default:
+		break;
+	}
+	return false;
+}
+
+bool RemoveObstacleMechanics::requiresCreatureTarget() const
+{
+	return false;
+}
+
+///RisingSpellMechanics
 HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectLevel) const
 {
 	//this may be even distinct class
@@ -526,18 +684,19 @@ HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectL
 }
 
 ///SacrificeMechanics
-ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
+	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
+	{
+		logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
+		return ESpellCastProblem::INVALID;
+	}
+
 	// 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
@@ -545,7 +704,7 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattle
 		//TODO: check that we really should check immunity for both stacks
 		ESpellCastProblem::ESpellCastProblem res = owner->internalIsImmune(caster, stack);
 		const bool immune =  ESpellCastProblem::OK != res && ESpellCastProblem::NOT_DECIDED != res;
-		const bool casterStack = stack->owner == player;
+		const bool casterStack = stack->owner == caster->getOwner();
 
 		if(!immune && casterStack)
 		{
@@ -572,11 +731,7 @@ void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, co
 	{
 		victim = parameters.destinations[1].stackValue;
 	}
-	else
-	{
-		//todo: remove and report error
-		victim = parameters.selectedStack;
-	}
+
 	if(nullptr == victim)
 	{
 		env->complain("SacrificeMechanics: No stack to sacrifice");
@@ -592,29 +747,43 @@ void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, co
 
 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;
+	return (parameters.effectPower + victim->MaxHealth() + owner->getPower(parameters.effectLevel)) * victim->count;
+}
+
+bool SacrificeMechanics::requiresCreatureTarget() const
+{
+	return false;//canBeCast do all target existence checks
 }
 
 ///SpecialRisingSpellMechanics
+ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
+{
+	const CStack * stack = cb->getStackIf([ctx](const CStack * s)
+	{
+		const bool ownerMatches = !ctx.ti.smart || s->getOwner() == ctx.caster->getOwner();
+
+		return ownerMatches && s->isValidTarget(!ctx.ti.onlyAlive) && s->coversPos(ctx.destination);
+	});
+
+	if(nullptr == stack)
+		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+
+	return ESpellCastProblem::OK;
+}
+
 ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	// following does apply to resurrect and animate dead(?) only
@@ -635,13 +804,19 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 }
 
 ///SummonMechanics
-ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
+	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
+	{
+		logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
+		return ESpellCastProblem::INVALID;
+	}
+
 	//check if there are summoned elementals of other type
 
-	auto otherSummoned = cb->battleGetStacksIf([player, this](const CStack * st)
+	auto otherSummoned = cb->battleGetStacksIf([caster, this](const CStack * st)
 	{
-		return (st->owner == player)
+		return (st->owner == caster->getOwner())
 			&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
 			&& (!vstd::contains(st->state, EBattleStackState::CLONED))
 			&& (st->getCreature()->idNumber != creatureToSummon);
@@ -673,26 +848,33 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
 		env->complain("Summoning didn't summon any!");
 }
 
+bool SummonMechanics::requiresCreatureTarget() const
+{
+	return false;
+}
+
 ///TeleportMechanics
 void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//todo: check legal teleport
 	if(parameters.destinations.size() == 2)
 	{
-		//first destination creature to move
-		const CStack * target = parameters.destinations[0].stackValue;
-		if(nullptr == target)
+		//first destination hex to move to
+		const BattleHex destination = parameters.destinations[0].hexValue;
+		if(!destination.isValid())
 		{
-			env->complain("TeleportMechanics: no stack to teleport");
+			env->complain("TeleportMechanics: invalid teleport destination");
 			return;
 		}
-		//second destination hex to move to
-		const BattleHex destination = parameters.destinations[1].hexValue;
-		if(!destination.isValid())
+
+		//second destination creature to move
+		const CStack * target = parameters.destinations[1].stackValue;
+		if(nullptr == target)
 		{
-			env->complain("TeleportMechanics: invalid teleport destination");
+			env->complain("TeleportMechanics: no stack to teleport");
 			return;
 		}
+
 		BattleStackMoved bsm;
 		bsm.distance = -1;
 		bsm.stack = target->ID;
@@ -704,16 +886,18 @@ void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, con
 	}
 	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);
+		env->complain("TeleportMechanics: 2 destinations required.");
+			return;
 	}
 }
 
+ESpellCastProblem::ESpellCastProblem TeleportMechanics::canBeCast(const CBattleInfoCallback* cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
+{
+	if(mode == ECastingMode::AFTER_ATTACK_CASTING || mode == ECastingMode::SPELL_LIKE_ATTACK || mode == ECastingMode::MAGIC_MIRROR)
+	{
+		logGlobal->warn("Invalid spell cast attempt: spell %s, mode %d", owner->name, mode); //should not even try to do it
+		return ESpellCastProblem::INVALID;
+	}
 
+	return DefaultSpellMechanics::canBeCast(cb, mode, caster);
+}

+ 80 - 13
lib/spells/BattleSpellMechanics.h

@@ -12,6 +12,9 @@
 
 #include "CDefaultSpellMechanics.h"
 
+class CObstacleInstance;
+class SpellCreatedObstacle;
+
 class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
 {
 public:
@@ -41,7 +44,8 @@ class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
 {
 public:
 	ChainLightningMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+protected:
+	std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 };
 
 class DLL_LINKAGE CloneMechanics : public DefaultSpellMechanics
@@ -59,8 +63,11 @@ public:
 	CureMechanics(CSpell * s): HealingSpellMechanics(s){};
 
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 
 	EHealLevel getHealLevel(int effectLevel) const override final;
+private:
+    static bool dispellSelector(const Bonus * b);
 };
 
 class DLL_LINKAGE DispellMechanics : public DefaultSpellMechanics
@@ -74,11 +81,12 @@ protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
-class DLL_LINKAGE EarthquakeMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE EarthquakeMechanics : public SpecialSpellMechanics
 {
 public:
-	EarthquakeMechanics(CSpell * s): DefaultSpellMechanics(s){};
-	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	EarthquakeMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
+	bool requiresCreatureTarget() const	override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
@@ -90,15 +98,43 @@ public:
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
 
-class DLL_LINKAGE ObstacleMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE ObstacleMechanics : public SpecialSpellMechanics
 {
 public:
-	ObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	ObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
+protected:
+	static bool isHexAviable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear);
+	void placeObstacle(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const BattleHex & pos) const;
+	virtual void setupObstacle(SpellCreatedObstacle * obstacle) const = 0;
+};
 
+class PatchObstacleMechanics : public ObstacleMechanics
+{
+public:
+	PatchObstacleMechanics(CSpell * s): ObstacleMechanics(s){};
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
+class DLL_LINKAGE LandMineMechanics : public PatchObstacleMechanics
+{
+public:
+	LandMineMechanics(CSpell * s): PatchObstacleMechanics(s){};
+	bool requiresCreatureTarget() const	override;
+protected:
+	void setupObstacle(SpellCreatedObstacle * obstacle) const override;
+};
+
+class DLL_LINKAGE QuicksandMechanics : public PatchObstacleMechanics
+{
+public:
+	QuicksandMechanics(CSpell * s): PatchObstacleMechanics(s){};
+	bool requiresCreatureTarget() const	override;
+protected:
+	void setupObstacle(SpellCreatedObstacle * obstacle) const override;
+};
+
 class DLL_LINKAGE WallMechanics : public ObstacleMechanics
 {
 public:
@@ -106,12 +142,37 @@ public:
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes = nullptr) const override;
 };
 
-class DLL_LINKAGE RemoveObstacleMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE FireWallMechanics : public WallMechanics
+{
+public:
+	FireWallMechanics(CSpell * s): WallMechanics(s){};
+	bool requiresCreatureTarget() const	override;
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void setupObstacle(SpellCreatedObstacle * obstacle) const override;
+};
+
+class DLL_LINKAGE ForceFieldMechanics : public WallMechanics
+{
+public:
+	ForceFieldMechanics(CSpell * s): WallMechanics(s){};
+	bool requiresCreatureTarget() const	override;
+protected:
+	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+	void setupObstacle(SpellCreatedObstacle * obstacle) const override;
+};
+
+class DLL_LINKAGE RemoveObstacleMechanics : public SpecialSpellMechanics
 {
 public:
-	RemoveObstacleMechanics(CSpell * s): DefaultSpellMechanics(s){};
+	RemoveObstacleMechanics(CSpell * s): SpecialSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
+	bool requiresCreatureTarget() const	override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
+private:
+    bool canRemove(const CObstacleInstance * obstacle, const int spellLevel) const;
 };
 
 ///all rising spells
@@ -119,6 +180,7 @@ class DLL_LINKAGE RisingSpellMechanics : public HealingSpellMechanics
 {
 public:
 	RisingSpellMechanics(CSpell * s): HealingSpellMechanics(s){};
+
 	EHealLevel getHealLevel(int effectLevel) const override;
 };
 
@@ -127,26 +189,29 @@ class DLL_LINKAGE SacrificeMechanics : public RisingSpellMechanics
 public:
 	SacrificeMechanics(CSpell * s): RisingSpellMechanics(s){};
 
-	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
+	bool requiresCreatureTarget() const	override;
 protected:
 	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
+///ANIMATE_DEAD and RESURRECTION
 class DLL_LINKAGE SpecialRisingSpellMechanics : public RisingSpellMechanics
 {
 public:
 	SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){};
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 };
 
-class DLL_LINKAGE SummonMechanics : public DefaultSpellMechanics
+class DLL_LINKAGE SummonMechanics : public SpecialSpellMechanics
 {
 public:
-	SummonMechanics(CSpell * s, CreatureID cre): DefaultSpellMechanics(s), creatureToSummon(cre){};
+	SummonMechanics(CSpell * s, CreatureID cre): SpecialSpellMechanics(s), creatureToSummon(cre){};
 
-	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
+	bool requiresCreatureTarget() const	override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 private:
@@ -157,6 +222,8 @@ class DLL_LINKAGE TeleportMechanics: public DefaultSpellMechanics
 {
 public:
 	TeleportMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };

+ 282 - 318
lib/spells/CDefaultSpellMechanics.cpp

@@ -12,7 +12,6 @@
 
 #include "CDefaultSpellMechanics.h"
 
-#include "../NetPacks.h"
 #include "../BattleState.h"
 
 #include "../CGeneralTextHandler.h"
@@ -119,139 +118,67 @@ namespace SRSLPraserHelpers
 	}
 }
 
-///DefaultSpellMechanics
-void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+SpellCastContext::SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_):
+	mechanics(mechanics_), env(env_), attackedCres(), sc(), si(), parameters(parameters_), otherHero(nullptr), spellCost(0), damageToDisplay(0)
 {
-	if (packet->castByHero)
-	{
-		if (packet->side < 2)
-		{
-			battle->sides[packet->side].castSpellsCount++;
-		}
-	}
-
-	//handle countering spells
-	for(auto stackID : packet->affectedCres)
-	{
-		CStack * s = battle->getStack(stackID);
-		s->popBonuses([&](const Bonus * b) -> bool
-		{
-			//check for each bonus if it should be removed
-			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
-			const int spellID = isSpellEffect ? b->sid : -1;
-			//No exceptions, ANY spell can be countered, even if it can`t be dispelled.
-			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
-		});
-	}
-}
-
-bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
-{
-	if(!owner->isAdventureSpell())
-	{
-		env->complain("Attempt to cast non adventure spell in adventure mode");
-		return false;
-	}
-
-	const CGHeroInstance * caster = parameters.caster;
-
-	if(caster->inTownGarrison)
-	{
-		env->complain("Attempt to cast an adventure spell in town garrison");
-		return false;
-	}
+	sc.side = parameters.casterSide;
+	sc.id = mechanics->owner->id;
+	sc.skill = parameters.spellLvl;
+	sc.tile = parameters.getFirstDestinationHex();
+	sc.castByHero = parameters.mode == ECastingMode::HERO_CASTING;
+	sc.casterStack = (parameters.casterStack ? parameters.casterStack->ID : -1);
+	sc.manaGained = 0;
 
-	const int cost = caster->getSpellCost(owner);
+	//check it there is opponent hero
+	const ui8 otherSide = 1-parameters.casterSide;
 
-	if(!caster->canCastThisSpell(owner))
-	{
-		env->complain("Hero cannot cast this spell!");
-		return false;
-	}
+	if(parameters.cb->battleHasHero(otherSide))
+		otherHero = parameters.cb->battleGetFightingHero(otherSide);
 
-	if(caster->mana < cost)
-	{
-		env->complain("Hero doesn't have enough spell points to cast this spell!");
-		return false;
-	}
+	logGlobal->debugStream() << "Started spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
+}
 
-	{
-		AdvmapSpellCast asc;
-		asc.caster = caster;
-		asc.spellID = owner->id;
-		env->sendAndApply(&asc);
-	}
+SpellCastContext::~SpellCastContext()
+{
+	logGlobal->debugStream() << "Finished spell cast. Spell: " << mechanics->owner->name << "; mode:" << parameters.mode;
+}
 
-	switch(applyAdventureEffects(env, parameters))
-	{
-	case ESpellCastResult::OK:
-		{
-			SetMana sm;
-			sm.hid = caster->id;
-			sm.absolute = false;
-			sm.val = -cost;
-			env->sendAndApply(&sm);
-			return true;
-		}
-		break;
-	case ESpellCastResult::CANCEL:
-		return true;
-	}
-	return false;
+void SpellCastContext::addDamageToDisplay(const si32 value)
+{
+	damageToDisplay += value;
 }
 
-ESpellCastResult DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+void SpellCastContext::setDamageToDisplay(const si32 value)
 {
-	if(owner->hasEffects())
-	{
-		const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	damageToDisplay = value;
+}
 
-		std::vector<Bonus> bonuses;
+void SpellCastContext::prepareBattleLog()
+{
+	bool displayDamage = true;
 
-		owner->getEffects(bonuses, schoolLevel);
+	mechanics->battleLog(sc.battleLog, parameters, attackedCres, damageToDisplay, displayDamage);
 
-		for(Bonus b : bonuses)
-		{
-			GiveBonus gb;
-			gb.id = parameters.caster->id.getNum();
-			gb.bonus = b;
-			env->sendAndApply(&gb);
-		}
+	displayDamage = displayDamage && damageToDisplay > 0;
 
-		return ESpellCastResult::OK;
-	}
-	else
+	if(displayDamage)
 	{
-		//There is no generic algorithm of adventure cast
-		env->complain("Unimplemented adventure spell");
-		return ESpellCastResult::ERROR;
-	}
-}
+        MetaString line;
 
-void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
-{
-	logGlobal->debugStream() << "Started spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
+        line.addTxt(MetaString::GENERAL_TXT, 376);
+        line.addReplacement(MetaString::SPELL_NAME, mechanics->owner->id.toEnum());
+        line.addReplacement(damageToDisplay);
 
-	if(nullptr == parameters.caster)
-	{
-		env->complain("No spell-caster provided.");
-		return;
+        sc.battleLog.push_back(line);
 	}
+}
 
-	BattleSpellCast sc;
-	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;
-
+void SpellCastContext::beforeCast()
+{
 	//calculate spell cost
 	if(parameters.mode == ECastingMode::HERO_CASTING)
 	{
-		spellCost = parameters.cb->battleGetSpellCost(owner, parameters.casterHero);
+		spellCost = parameters.cb->battleGetSpellCost(mechanics->owner, parameters.casterHero);
 
 		if(nullptr != otherHero) //handle mana channel
 		{
@@ -265,60 +192,25 @@ 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.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
-	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)
-	{
-		for(auto s : attackedCres)
-		{
-			const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
-			if(env->getRandomGenerator().nextInt(99) < mirrorChance)
-				reflected.push_back(s);
-		}
-
-		vstd::erase_if(attackedCres, [&reflected](const CStack * s)
-		{
-			return vstd::contains(reflected, s);
-		});
-
-		for(auto s : reflected)
-		{
-			BattleSpellCast::CustomEffect effect;
-			effect.effect = 3;
-			effect.stack = s->ID;
-			sc.customEffects.push_back(effect);
-		}
+		logGlobal->debugStream() << "spellCost: " << spellCost;
 	}
+}
 
-	for(auto cre : attackedCres)
+void SpellCastContext::afterCast()
+{
+	for(auto sta : attackedCres)
 	{
-		sc.affectedCres.insert(cre->ID);
+		sc.affectedCres.insert(sta->ID);
 	}
 
-	StacksInjured si;
-	SpellCastContext ctx(attackedCres, sc, si);
-	applyBattleEffects(env, parameters, ctx);
+	prepareBattleLog();
 
 	env->sendAndApply(&sc);
 
-	//spend mana
 	if(parameters.mode == ECastingMode::HERO_CASTING)
 	{
+		//spend mana
 		SetMana sm;
 		sm.absolute = false;
 
@@ -336,14 +228,9 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 			env->sendAndApply(&sm);
 		}
 	}
-
-	if(!si.stacks.empty()) //after spellcast info shows
-		env->sendAndApply(&si);
-
-	//reduce number of casts remaining
-	//TODO: this should be part of BattleSpellCast apply
-	if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
+	else if (parameters.mode == ECastingMode::CREATURE_ACTIVE_CASTING || parameters.mode == ECastingMode::ENCHANTER_CASTING)
 	{
+		//reduce number of casts remaining
 		assert(parameters.casterStack);
 
 		BattleSetStackProperty ssp;
@@ -353,10 +240,57 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		ssp.absolute = false;
 		env->sendAndApply(&ssp);
 	}
-	logGlobal->debugStream() << "Finished spell cast. Spell: "<<owner->name<<"; mode:"<<parameters.mode;
+	if(!si.stacks.empty()) //after spellcast info shows
+		env->sendAndApply(&si);
+}
+
+///DefaultSpellMechanics
+void DefaultSpellMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
+{
+	if (packet->castByHero)
+	{
+		if (packet->side < 2)
+		{
+			battle->sides[packet->side].castSpellsCount++;
+		}
+	}
+
+	//handle countering spells
+	for(auto stackID : packet->affectedCres)
+	{
+		CStack * s = battle->getStack(stackID);
+		s->popBonuses([&](const Bonus * b) -> bool
+		{
+			//check for each bonus if it should be removed
+			const bool isSpellEffect = Selector::sourceType(Bonus::SPELL_EFFECT)(b);
+			const int spellID = isSpellEffect ? b->sid : -1;
+			//No exceptions, ANY spell can be countered, even if it can`t be dispelled.
+			return isSpellEffect && vstd::contains(owner->counteredSpells, spellID);
+		});
+	}
+}
+
+void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const
+{
+	if(nullptr == parameters.caster)
+	{
+		env->complain("No spell-caster provided.");
+		return;
+	}
+
+	std::vector <const CStack*> reflected;//for magic mirror
+
+	cast(env, parameters, reflected);
+
 	//Magic Mirror effect
 	for(auto & attackedCre : reflected)
 	{
+		if(parameters.mode == ECastingMode::MAGIC_MIRROR)
+		{
+			logGlobal->error("Magic mirror recurrence!");
+			return;
+		}
+
 		TStacks mirrorTargets = parameters.cb->battleGetStacksIf([this, parameters](const CStack * battleStack)
 		{
 			//Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
@@ -367,48 +301,68 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		{
 			int targetHex = (*RandomGeneratorUtil::nextItem(mirrorTargets, env->getRandomGenerator()))->position;
 
-			BattleSpellCastParameters mirrorParameters(parameters.cb, attackedCre, owner);
-			mirrorParameters.spellLvl = 0;
+			BattleSpellCastParameters mirrorParameters(parameters, attackedCre);
 			mirrorParameters.aimToHex(targetHex);
-			mirrorParameters.mode = ECastingMode::MAGIC_MIRROR;
-			mirrorParameters.selectedStack = nullptr;
-			mirrorParameters.spellLvl = parameters.spellLvl;
-			mirrorParameters.effectLevel = parameters.effectLevel;
-			mirrorParameters.effectPower = parameters.effectPower;
-			mirrorParameters.effectValue = parameters.effectValue;
-			mirrorParameters.enchantPower = parameters.enchantPower;
-			castMagicMirror(env, mirrorParameters);
+			mirrorParameters.cast(env);
 		}
 	}
 }
 
-void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
-	const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const
+void DefaultSpellMechanics::cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const
+{
+	SpellCastContext ctx(this, env, parameters);
+
+	ctx.beforeCast();
+
+	ctx.attackedCres = owner->getAffectedStacks(parameters.cb, parameters.mode, parameters.caster, parameters.spellLvl, parameters.getFirstDestinationHex());
+
+	logGlobal->debugStream() << "will affect " << ctx.attackedCres.size() << " stacks";
+
+	handleResistance(env, ctx);
+
+	if(parameters.mode != ECastingMode::MAGIC_MIRROR)
+		handleMagicMirror(env, ctx, reflected);
+
+	applyBattleEffects(env, parameters, ctx);
+
+	ctx.afterCast();
+}
+
+void DefaultSpellMechanics::battleLog(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
+	const std::vector<const CStack *> & attacked, const si32 damageToDisplay, bool & displayDamage) const
 {
-	const std::string attackedName = attackedStack->getName();
-	const std::string attackedNameSing = attackedStack->getCreature()->nameSing;
-	const std::string attackedNamePl = attackedStack->getCreature()->namePl;
+	if(attacked.size() != 1)
+	{
+		displayDamage = true;
+		battleLogDefault(logLines, parameters, attacked);
+		return;
+	}
 
-	auto getPluralFormat = [attackedStack](const int baseTextID) -> boost::format
+	auto attackedStack = attacked.at(0);
+
+	auto getPluralFormat = [attackedStack](const int baseTextID) -> si32
 	{
-		return boost::format(VLC->generaltexth->allTexts[(attackedStack->count > 1 ? baseTextID + 1 : baseTextID)]);
+		return attackedStack->count > 1 ? baseTextID + 1 : baseTextID;
 	};
 
-	auto logSimple = [&logLines, getPluralFormat, attackedName](const int baseTextID)
+	auto logSimple = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
 	{
-		boost::format fmt = getPluralFormat(baseTextID);
-		fmt % attackedName;
-		logLines.push_back(fmt.str());
+		MetaString line;
+		line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(baseTextID));
+		line.addReplacement(*attackedStack);
+		logLines.push_back(line);
 	};
 
-	auto logPlural = [&logLines, attackedNamePl](const int baseTextID)
+	auto logPlural = [attackedStack, &logLines, getPluralFormat](const int baseTextID)
 	{
-		boost::format fmt(VLC->generaltexth->allTexts[baseTextID]);
-		fmt % attackedNamePl;
-		logLines.push_back(fmt.str());
+		MetaString line;
+		line.addTxt(MetaString::GENERAL_TXT, baseTextID);
+		line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
+		logLines.push_back(line);
 	};
 
 	displayDamage = false; //in most following cases damage info text is custom
+
 	switch(owner->id)
 	{
 	case SpellID::STONE_GAZE:
@@ -428,74 +382,78 @@ void DefaultSpellMechanics::battleLogSingleTarget(std::vector<std::string> & log
 		break;
 	case SpellID::AGE:
 		{
-			boost::format text = getPluralFormat(551);
-			text % attackedName;
 			//The %s shrivel with age, and lose %d hit points."
+			MetaString line;
+			line.addTxt(MetaString::GENERAL_TXT, getPluralFormat(551));
+			line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
+
+			//todo: display effect from only this cast
 			TBonusListPtr bl = attackedStack->getBonuses(Selector::type(Bonus::STACK_HEALTH));
 			const int fullHP = bl->totalValue();
 			bl->remove_if(Selector::source(Bonus::SPELL_EFFECT, SpellID::AGE));
-			text % (fullHP - bl->totalValue());
-			logLines.push_back(text.str());
+			line.addReplacement(fullHP - bl->totalValue());
+			logLines.push_back(line);
 		}
 		break;
 	case SpellID::THUNDERBOLT:
 		{
 			logPlural(367);
+			MetaString line;
+			//todo: handle newlines in metastring
 			std::string text = VLC->generaltexth->allTexts[343].substr(1, VLC->generaltexth->allTexts[343].size() - 1); //Does %d points of damage.
-			boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay)); //no more text afterwards
-			logLines.push_back(text);
+			line << text;
+			line.addReplacement(damageToDisplay); //no more text afterwards
+			logLines.push_back(line);
 		}
 		break;
 	case SpellID::DISPEL_HELPFUL_SPELLS:
 		logPlural(555);
 		break;
 	case SpellID::DEATH_STARE:
-		if (packet->dmgToDisplay > 0)
+		if (damageToDisplay > 0)
 		{
-			std::string text;
-			if (packet->dmgToDisplay > 1)
+			MetaString line;
+			if (damageToDisplay > 1)
 			{
-				text = VLC->generaltexth->allTexts[119]; //%d %s die under the terrible gaze of the %s.
-				boost::algorithm::replace_first(text, "%d", boost::lexical_cast<std::string>(packet->dmgToDisplay));
-				boost::algorithm::replace_first(text, "%s", attackedNamePl);
+				line.addTxt(MetaString::GENERAL_TXT, 119); //%d %s die under the terrible gaze of the %s.
+				line.addReplacement(damageToDisplay);
+				line.addReplacement(MetaString::CRE_PL_NAMES, attackedStack->getCreature()->idNumber.num);
 			}
 			else
 			{
-				text = VLC->generaltexth->allTexts[118]; //One %s dies under the terrible gaze of the %s.
-				boost::algorithm::replace_first(text, "%s", attackedNameSing);
+				line.addTxt(MetaString::GENERAL_TXT, 118); //One %s dies under the terrible gaze of the %s.
+				line.addReplacement(MetaString::CRE_SING_NAMES, attackedStack->getCreature()->idNumber.num);
 			}
-			boost::algorithm::replace_first(text, "%s", casterName); //casting stack
-			logLines.push_back(text);
+			parameters.caster->getCasterName(line);
+			logLines.push_back(line);
 		}
 		break;
 	default:
-		{
-			boost::format text(VLC->generaltexth->allTexts[565]); //The %s casts %s
-			text % casterName % owner->name;
-			displayDamage = true;
-			logLines.push_back(text.str());
-		}
+		displayDamage = true;
+		battleLogDefault(logLines, parameters, attacked);
 		break;
 	}
 }
 
+void DefaultSpellMechanics::battleLogDefault(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters, const std::vector<const CStack*> & attacked) const
+{
+	MetaString line;
+	parameters.caster->getCastDescription(owner, attacked, line);
+	logLines.push_back(line);
+}
+
 void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//applying effects
 	if(owner->isOffensiveSpell())
 	{
-		int spellDamage = parameters.effectValue;
-
+		const int rawDamage = parameters.getEffectValue();
 		int chainLightningModifier = 0;
 		for(auto & attackedCre : ctx.attackedCres)
 		{
 			BattleStackAttacked bsa;
-			if(spellDamage != 0)
-				bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, spellDamage) >> chainLightningModifier;
-			else
-				bsa.damageAmount = owner->calculateDamage(parameters.caster, attackedCre, parameters.effectLevel, parameters.effectPower) >> chainLightningModifier;
-
-			ctx.sc.dmgToDisplay += bsa.damageAmount;
+			bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
+			ctx.addDamageToDisplay(bsa.damageAmount);
 
 			bsa.stackAttacked = (attackedCre)->ID;
 			if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
@@ -598,7 +556,6 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 
 		if(!sse.stacks.empty())
 			env->sendAndApply(&sse);
-
 	}
 }
 
@@ -609,7 +566,7 @@ std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex,
 	std::vector<BattleHex> ret;
 	std::string rng = owner->getLevelInfo(schoolLvl).range + ','; //copy + artificial comma for easier handling
 
-	if(rng.size() >= 2 && rng[0] != 'X') //there is at lest one hex in range (+artificial comma)
+	if(rng.size() >= 2 && rng[0] != 'X') //there is at least one hex in range (+artificial comma)
 	{
 		std::string number1, number2;
 		int beg, end;
@@ -668,85 +625,94 @@ std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex,
 	return ret;
 }
 
-std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const
+std::vector<const CStack *> DefaultSpellMechanics::getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const
+{
+	std::vector<const CStack *> attackedCres = calculateAffectedStacks(cb, ctx);
+	handleImmunities(cb, ctx, attackedCres);
+	return attackedCres;
+}
+
+std::vector<const CStack *> DefaultSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback* cb, const SpellTargetingContext& ctx) const
 {
 	std::set<const CStack* > attackedCres;//std::set to exclude multiple occurrences of two hex creatures
 
-	const ui8 attackerSide = ctx.cb->playerToSide(ctx.casterColor) == 1;
-	const auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
+	const ui8 attackerSide = cb->playerToSide(ctx.caster->getOwner()) == 1;
+	auto attackedHexes = rangeInHexes(ctx.destination, ctx.schoolLvl, attackerSide);
 
-	const CSpell::TargetInfo ti(owner, ctx.schoolLvl, ctx.mode);
+	//hackfix for banned creature massive spells
+	if(!ctx.ti.massive && owner->getLevelInfo(ctx.schoolLvl).range == "X")
+		attackedHexes.push_back(ctx.destination);
 
-	//TODO: more generic solution for mass spells
-	if(owner->getLevelInfo(ctx.schoolLvl).range.size() > 1) //custom many-hex range
-	{
-		for(BattleHex hex : attackedHexes)
-		{
-			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
-			{
-				attackedCres.insert(st);
-			}
-		}
-	}
-	else if(ti.type == CSpell::CREATURE)
+	auto mainFilter = [=](const CStack * s)
 	{
-		auto predicate = [=](const CStack * s){
-			const bool positiveToAlly = owner->isPositive() && s->owner == ctx.casterColor;
-			const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.casterColor;
-			const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class
+		const bool positiveToAlly = owner->isPositive() && s->owner == ctx.caster->getOwner();
+		const bool negativeToEnemy = owner->isNegative() && s->owner != ctx.caster->getOwner();
+		const bool validTarget = s->isValidTarget(!ctx.ti.onlyAlive); //todo: this should be handled by spell class
+		const bool positivenessFlag = !ctx.ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
+
+		return positivenessFlag && validTarget;
+	};
 
-			//for single target spells select stacks covering destination tile
-			const bool rangeCovers = ti.massive || s->coversPos(ctx.destination);
-			//handle smart targeting
-			const bool positivenessFlag = !ti.smart || owner->isNeutral() || positiveToAlly || negativeToEnemy;
+	if(ctx.ti.type == CSpell::CREATURE && attackedHexes.size() == 1)
+	{
+		//for single target spells we must select one target. Alive stack is preferred (issue #1763)
 
-			return rangeCovers && positivenessFlag && validTarget;
+		auto predicate = [&](const CStack * s)
+		{
+			return s->coversPos(attackedHexes.at(0)) && mainFilter(s);
 		};
 
-		TStacks stacks = ctx.cb->battleGetStacksIf(predicate);
+		TStacks stacks = cb->battleGetStacksIf(predicate);
 
-		if(ti.massive)
+		for(auto stack : stacks)
 		{
-			//for massive spells add all targets
-			for (auto stack : stacks)
-				attackedCres.insert(stack);
-		}
-		else
-		{
-			//for single target spells we must select one target. Alive stack is preferred (issue #1763)
-			for(auto stack : stacks)
+			if(stack->alive())
 			{
-				if(stack->alive())
-				{
-					attackedCres.insert(stack);
-					break;
-				}
+				attackedCres.insert(stack);
+				break;
 			}
+		}
 
-			if(attackedCres.empty() && !stacks.empty())
-			{
-				attackedCres.insert(stacks.front());
-			}
+		if(attackedCres.empty() && !stacks.empty())
+		{
+			attackedCres.insert(stacks.front());
 		}
 	}
+	else if(ctx.ti.massive)
+	{
+		TStacks stacks = cb->battleGetStacksIf(mainFilter);
+		for (auto stack : stacks)
+			attackedCres.insert(stack);
+	}
 	else //custom range from attackedHexes
 	{
 		for(BattleHex hex : attackedHexes)
 		{
-			if(const CStack * st = ctx.cb->battleGetStackByPos(hex, ti.onlyAlive))
-				attackedCres.insert(st);
+			if(const CStack * st = cb->battleGetStackByPos(hex, ctx.ti.onlyAlive))
+				if(mainFilter(st))
+					attackedCres.insert(st);;
 		}
 	}
 
-	return attackedCres;
+	std::vector<const CStack *> res;
+	std::copy(attackedCres.begin(), attackedCres.end(), std::back_inserter(res));
+
+	return res;
 }
 
-ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
 	//no problems by default, this method is for spell-specific problems
 	return ESpellCastProblem::OK;
 }
 
+ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
+{
+	//no problems by default, this method is for spell-specific problems
+	//common problems handled by CSpell
+	return ESpellCastProblem::OK;
+}
+
 ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	//by default use general algorithm
@@ -774,58 +740,56 @@ void DefaultSpellMechanics::doDispell(BattleInfo * battle, const BattleSpellCast
 	}
 }
 
-void DefaultSpellMechanics::castMagicMirror(const SpellCastEnvironment* env, BattleSpellCastParameters& parameters) const
+void DefaultSpellMechanics::handleImmunities(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx, std::vector<const CStack*> & stacks) 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())
+	//now handle immunities
+	auto predicate = [&, this](const CStack * s)->bool
 	{
-		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
+		bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(ctx.destination);
+		bool notImmune = (ESpellCastProblem::OK == owner->isImmuneByStack(ctx.caster, s));
 
-	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);
+		return !(hitDirectly || notImmune);
+	};
+	vstd::erase_if(stacks, predicate);
+}
 
-	for(auto cre : attackedCres)
+void DefaultSpellMechanics::handleMagicMirror(const SpellCastEnvironment * env, SpellCastContext & ctx, std::vector <const CStack*> & reflected) const
+{
+	//reflection is applied only to negative spells
+	//if 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)
 	{
-		sc.affectedCres.insert(cre->ID);
-	}
+		for(auto s : ctx.attackedCres)
+		{
+			const int mirrorChance = (s)->valOfBonuses(Bonus::MAGIC_MIRROR);
+			if(env->getRandomGenerator().nextInt(99) < mirrorChance)
+				reflected.push_back(s);
+		}
 
-	StacksInjured si;
-	SpellCastContext ctx(attackedCres, sc, si);
-	applyBattleEffects(env, parameters, ctx);
+		vstd::erase_if(ctx.attackedCres, [&reflected](const CStack * s)
+		{
+			return vstd::contains(reflected, s);
+		});
 
-	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";
+		for(auto s : reflected)
+		{
+			BattleSpellCast::CustomEffect effect;
+			effect.effect = 3;
+			effect.stack = s->ID;
+			ctx.sc.customEffects.push_back(effect);
+		}
+	}
 }
 
-void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, std::vector<const CStack* >& attackedCres, BattleSpellCast& sc) const
+void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, SpellCastContext & ctx) const
 {
 	//checking if creatures resist
-	//resistance/reflection is applied only to negative spells
+	//resistance is applied only to negative spells
 	if(owner->isNegative())
 	{
 		std::vector <const CStack*> resisted;
-		for(auto s : attackedCres)
+		for(auto s : ctx.attackedCres)
 		{
 			//magic resistance
 			const int prob = std::min((s)->magicResistance(), 100); //probability of resistance in %
@@ -836,7 +800,7 @@ void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, s
 			}
 		}
 
-		vstd::erase_if(attackedCres, [&resisted](const CStack * s)
+		vstd::erase_if(ctx.attackedCres, [&resisted](const CStack * s)
 		{
 			return vstd::contains(resisted, s);
 		});
@@ -846,19 +810,19 @@ void DefaultSpellMechanics::handleResistance(const SpellCastEnvironment * env, s
 			BattleSpellCast::CustomEffect effect;
 			effect.effect = 78;
 			effect.stack = s->ID;
-			sc.customEffects.push_back(effect);
+			ctx.sc.customEffects.push_back(effect);
 		}
 	}
 }
 
-void DefaultSpellMechanics::prepareBattleCast(const BattleSpellCastParameters& parameters, BattleSpellCast& sc) const
+bool DefaultSpellMechanics::requiresCreatureTarget() 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;
+	//most spells affects creatures somehow regardless of Target Type
+	//for few exceptions see overrides
+	return true;
+}
+
+std::vector<const CStack *> SpecialSpellMechanics::calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const
+{
+	return std::vector<const CStack *>();
 }

+ 55 - 26
lib/spells/CDefaultSpellMechanics.h

@@ -11,54 +11,83 @@
 #pragma once
 
 #include "ISpellMechanics.h"
+#include "../NetPacks.h"
 
-struct StacksInjured;
+class DefaultSpellMechanics;
 
-struct SpellCastContext
+class DLL_LINKAGE SpellCastContext
 {
-	SpellCastContext(std::vector<const CStack *> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
-		attackedCres(attackedCres), sc(sc), si(si)
-	{
-	};
-	std::vector<const CStack *> & attackedCres;
-	BattleSpellCast & sc;
-	StacksInjured & si;
-};
+public:
+	const DefaultSpellMechanics * mechanics;
+	const SpellCastEnvironment * env;
+	std::vector<const CStack *> attackedCres;//must be vector, as in Chain Lightning order matters
+	BattleSpellCast sc;//todo: make private
+	StacksInjured si;
+	const BattleSpellCastParameters & parameters;
 
-enum class ESpellCastResult
-{
-	OK,
-	CANCEL,//cast failed but it is not an error
-	ERROR//internal error occurred
+	SpellCastContext(const DefaultSpellMechanics * mechanics_, const SpellCastEnvironment * env_, const BattleSpellCastParameters & parameters_);
+	virtual ~SpellCastContext();
+
+	void addDamageToDisplay(const si32 value);
+	void setDamageToDisplay(const si32 value);
+
+	void beforeCast();
+	void afterCast();
+private:
+	const CGHeroInstance * otherHero;
+	int spellCost;
+	si32 damageToDisplay;
+
+	void prepareBattleLog();
 };
 
+///all combat spells
 class DLL_LINKAGE DefaultSpellMechanics : public ISpellMechanics
 {
 public:
 	DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){};
 
 	std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const override;
-	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
+	std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const override final;
 
-	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const override;
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) 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 final;
 
-	void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
-		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
+	void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const override final;
+
+	void battleLog(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
+		const std::vector<const CStack *> & attacked, const si32 damageToDisplay, bool & displayDamage) const;
+
+	void battleLogDefault(std::vector<MetaString> & logLines, const BattleSpellCastParameters & parameters,
+		const std::vector<const CStack *> & attacked) const;
+
+	bool requiresCreatureTarget() const	override;
 protected:
 	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;
+	virtual std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const;
 
+protected:
 	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;
+	void cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const;
+
+	void handleImmunities(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx, std::vector<const CStack *> & stacks) const;
+	void handleMagicMirror(const SpellCastEnvironment * env, SpellCastContext & ctx, std::vector <const CStack*> & reflected) const;
+	void handleResistance(const SpellCastEnvironment * env, SpellCastContext & ctx) const;
+
+	friend class SpellCastContext;
+};
+
+///not affecting creatures directly
+class DLL_LINKAGE SpecialSpellMechanics : public DefaultSpellMechanics
+{
+public:
+	SpecialSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	std::vector<const CStack *> calculateAffectedStacks(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) const override;
 };

+ 101 - 86
lib/spells/CSpellHandler.cpp

@@ -91,14 +91,15 @@ CSpell::CSpell():
 	defaultProbability(0),
 	isRising(false), isDamage(false), isOffensive(false),
 	targetType(ETargetType::NO_TARGET),
-	mechanics(nullptr)
+	mechanics(),
+	adventureMechanics()
 {
 	levels.resize(GameConstants::SPELL_SCHOOL_LEVELS);
 }
 
 CSpell::~CSpell()
 {
-	delete mechanics;
+
 }
 
 void CSpell::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
@@ -110,10 +111,15 @@ bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastP
 {
 	assert(env);
 
-	return mechanics->adventureCast(env, parameters);
+	if(!adventureMechanics.get())
+	{
+		env->complain("Invalid adventure spell cast attempt!");
+		return false;
+	}
+	return adventureMechanics->adventureCast(env, parameters);
 }
 
-void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
+void CSpell::battleCast(const SpellCastEnvironment * env,  const BattleSpellCastParameters & parameters) const
 {
 	assert(env);
 	if(parameters.destinations.size()<1)
@@ -143,9 +149,61 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte
 	return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower));
 }
 
-ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
+ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
 {
-	return mechanics->canBeCast(cb, player);
+	const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);
+
+	if(generalProblem != ESpellCastProblem::OK)
+		return generalProblem;
+
+	//check for creature target existence
+	if(mechanics->requiresCreatureTarget())
+	{
+		switch(mode)
+		{
+		case ECastingMode::HERO_CASTING:
+		case ECastingMode::CREATURE_ACTIVE_CASTING:
+		case ECastingMode::ENCHANTER_CASTING:
+		case ECastingMode::PASSIVE_CASTING:
+			{
+				TargetInfo tinfo(this, caster->getSpellSchoolLevel(this), mode);
+
+				bool targetExists = false;
+
+				for(const CStack * stack : cb->battleGetAllStacks())
+				{
+					bool immune = !(stack->isValidTarget(!tinfo.onlyAlive) && ESpellCastProblem::OK == isImmuneByStack(caster, stack));
+					bool casterStack = stack->owner == caster->getOwner();
+
+					if(!immune)
+					{
+						switch (positiveness)
+						{
+						case CSpell::POSITIVE:
+							if(casterStack || !tinfo.smart)
+								targetExists = true;
+							break;
+						case CSpell::NEUTRAL:
+								targetExists = true;
+								break;
+						case CSpell::NEGATIVE:
+							if(!casterStack || !tinfo.smart)
+								targetExists = true;
+							break;
+						}
+					}
+					if(targetExists)
+						break;
+				}
+				if(!targetExists)
+				{
+					return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+				}
+			}
+			break;
+		}
+	}
+	return ESpellCastProblem::OK;
 }
 
 std::vector<BattleHex> CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
@@ -153,23 +211,10 @@ 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 ISpellCaster * caster) const
+std::vector<const CStack *> CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster, int spellLvl, BattleHex destination) const
 {
-	ISpellMechanics::SpellTargetingContext ctx(this, cb,mode,casterColor,spellLvl,destination);
-
-	std::set<const CStack* > attackedCres = mechanics->getAffectedStacks(ctx);
-
-	//now handle immunities
-	auto predicate = [&, this](const CStack * s)->bool
-	{
-		bool hitDirectly = ctx.ti.alwaysHitDirectly && s->coversPos(destination);
-		bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s));
-
-		return !(hitDirectly || notImmune);
-	};
-	vstd::erase_if(attackedCres, predicate);
-
-	return attackedCres;
+	SpellTargetingContext ctx(this, mode, caster, spellLvl, destination);
+	return mechanics->getAffectedStacks(cb, ctx);;
 }
 
 CSpell::ETargetType CSpell::getTargetType() const
@@ -222,11 +267,6 @@ bool CSpell::isNeutral() const
 	return positiveness == NEUTRAL;
 }
 
-bool CSpell::isHealingSpell() const
-{
-	return isRisingSpell() || (id == SpellID::CURE);
-}
-
 bool CSpell::isRisingSpell() const
 {
 	return isRising;
@@ -305,6 +345,35 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
 	}
 }
 
+ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
+{
+	SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
+
+	ESpellCastProblem::ESpellCastProblem specific = mechanics->canBeCast(cb, ctx);
+
+	if(specific != ESpellCastProblem::OK)
+		return specific;
+
+	//todo: this should be moved to mechanics
+	//rising spells handled by mechanics
+	if(ctx.ti.onlyAlive && getTargetType() == CSpell::CREATURE)
+	{
+		const CStack * aliveStack = cb->getStackIf([destination](const CStack * s)
+		{
+			return s->isValidTarget(false) && s->coversPos(destination);
+		});
+
+		if(!aliveStack)
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(ctx.ti.smart && isNegative() && aliveStack->owner == caster->getOwner())
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+		if(ctx.ti.smart && isPositive() && aliveStack->owner != caster->getOwner())
+			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
+	}
+
+	return isImmuneAt(cb, caster, mode, destination);
+}
+
 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
@@ -517,59 +586,6 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const ISpellCaster
 	return ESpellCastProblem::OK;
 }
 
-void CSpell::prepareBattleLog(const CBattleInfoCallback * cb,  const BattleSpellCast * packet, std::vector<std::string> & logLines) const
-{
-	bool displayDamage = true;
-
-	std::string casterName("Something"); //todo: localize
-
-	if(packet->castByHero)
-		casterName = cb->battleGetHeroInfo(packet->side).name;
-
-	{
-		const auto casterStackID = packet->casterStack;
-
-		if(casterStackID > 0)
-		{
-			const CStack * casterStack = cb->battleGetStackByID(casterStackID);
-			if(casterStack != nullptr)
-				casterName = casterStack->type->namePl;
-		}
-	}
-
-	if(packet->affectedCres.size() == 1)
-	{
-		const CStack * attackedStack = cb->battleGetStackByID(*packet->affectedCres.begin(), false);
-
-		const std::string attackedNamePl = attackedStack->getCreature()->namePl;
-
-		if(packet->castByHero)
-		{
-			const std::string fmt = VLC->generaltexth->allTexts[195];
-			logLines.push_back(boost::to_string(boost::format(fmt) % casterName % this->name % attackedNamePl));
-		}
-		else
-		{
-			mechanics->battleLogSingleTarget(logLines, packet, casterName, attackedStack, displayDamage);
-		}
-	}
-	else
-	{
-		boost::format text(VLC->generaltexth->allTexts[196]);
-		text % casterName % this->name;
-		logLines.push_back(text.str());
-	}
-
-
-	if(packet->dmgToDisplay > 0 && displayDamage)
-	{
-		boost::format dmgInfo(VLC->generaltexth->allTexts[376]);
-		dmgInfo % this->name % packet->dmgToDisplay;
-		logLines.push_back(dmgInfo.str());
-	}
-}
-
-
 void CSpell::setIsOffensive(const bool val)
 {
 	isOffensive = val;
@@ -598,13 +614,8 @@ void CSpell::setup()
 
 void CSpell::setupMechanics()
 {
-	if(nullptr != mechanics)
-	{
-		logGlobal->errorStream() << "Spell " << this->name << ": mechanics already set";
-		delete mechanics;
-	}
-
 	mechanics = ISpellMechanics::createMechanics(this);
+	adventureMechanics = IAdventureSpellMechanics::createMechanics(this);
 }
 
 ///CSpell::AnimationInfo
@@ -661,6 +672,10 @@ CSpell::TargetInfo::TargetInfo(const CSpell * spell, const int level, ECastingMo
 	{
 		alwaysHitDirectly = true;
 	}
+	else if(mode == ECastingMode::CREATURE_ACTIVE_CASTING)
+	{
+		massive = false;//FIXME: find better solution for Commander spells
+	}
 }
 
 void CSpell::TargetInfo::init(const CSpell * spell, const int level)

+ 12 - 11
lib/spells/CSpellHandler.h

@@ -20,6 +20,7 @@
 class CGObjectInstance;
 class CSpell;
 class ISpellMechanics;
+class IAdventureSpellMechanics;
 class CLegacyConfigParser;
 class CGHeroInstance;
 class CStack;
@@ -204,7 +205,6 @@ public:
 	bool isNeutral() const;
 
 	bool isDamageSpell() const;
-	bool isHealingSpell() const;
 	bool isRisingSpell() const;
 	bool isOffensiveSpell() const;
 
@@ -218,7 +218,7 @@ public:
 	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 ISpellCaster * caster) const;
+	std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster, int spellLvl, BattleHex destination) const;
 
 	si32 getCost(const int skillLevel) const;
 
@@ -266,11 +266,11 @@ public:
 public:
 	///internal interface (for callbacks)
 
-	///Checks general but spell-specific problems for all casting modes. Use only during battle.
-	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const;
+	///Checks general but spell-specific problems. Use only during battle.
+	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) 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;
+	ESpellCastProblem::ESpellCastProblem canBeCastAt(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 ISpellCaster * caster, const CStack * obj) const;
@@ -279,7 +279,7 @@ public:
 	///May be executed on client side by (future) non-cheat-proof scripts.
 
 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
-	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;
+	void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const;
 
 public:
 	///Client-server logic. Has direct write access to GameState.
@@ -287,10 +287,6 @@ public:
 
 	///implementation of BattleSpellCast applying
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const;
-
-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;
@@ -300,6 +296,10 @@ public://internal, for use only by Mechanics classes
 	ESpellCastProblem::ESpellCastProblem internalIsImmune(const ISpellCaster * caster, const CStack *obj) const;
 
 private:
+
+	///checks for creature immunity *at given hex*.
+	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
+
 	void setIsOffensive(const bool val);
 	void setIsRising(const bool val);
 
@@ -335,7 +335,8 @@ private:
 
 	std::vector<LevelInfo> levels;
 
-	ISpellMechanics * mechanics;//(!) do not serialize
+	std::unique_ptr<ISpellMechanics> mechanics;//(!) do not serialize
+	std::unique_ptr<IAdventureSpellMechanics> adventureMechanics;//(!) do not serialize
 };
 
 bool DLL_LINKAGE isInScreenRange(const int3 &center, const int3 &pos); //for spells like Dimension Door

+ 26 - 4
lib/spells/CreatureSpellMechanics.cpp

@@ -20,9 +20,9 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
 {
 	//todo: this should be effectValue
 	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.effectPower;
+	ctx.setDamageToDisplay(parameters.effectPower);
 
-	for(auto & attackedCre : ctx.attackedCres) //no immunities
+	for(auto & attackedCre : ctx.attackedCres)
 	{
 		BattleStackAttacked bsa;
 		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
@@ -35,13 +35,35 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment *
 	}
 }
 
+ESpellCastProblem::ESpellCastProblem AcidBreathDamageMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
+{
+	//just in case
+	if(!obj->alive())
+		return ESpellCastProblem::WRONG_SPELL_TARGET;
+
+	//there should be no immunities by design
+	//but make it a bit configurable
+	//ignore 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;
+	}
+	return ESpellCastProblem::OK;
+}
+
 ///DeathStareMechanics
 void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {
 	//calculating dmg to display
-	ctx.sc.dmgToDisplay = parameters.effectPower;
+	si32 damageToDisplay = parameters.effectPower;
+
 	if(!ctx.attackedCres.empty())
-		vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
+		vstd::amin(damageToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack
+
+	ctx.setDamageToDisplay(damageToDisplay);
 
 	for(auto & attackedCre : ctx.attackedCres)
 	{

+ 3 - 0
lib/spells/CreatureSpellMechanics.h

@@ -17,6 +17,9 @@ class DLL_LINKAGE AcidBreathDamageMechanics : public DefaultSpellMechanics
 {
 public:
 	AcidBreathDamageMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
+
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };

+ 84 - 50
lib/spells/ISpellMechanics.cpp

@@ -34,15 +34,35 @@ BattleSpellCastParameters::Destination::Destination(const BattleHex & destinatio
 
 }
 
-BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell)
-	: cb(cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
+BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell_)
+	: spell(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)
+	mode(ECastingMode::HERO_CASTING), casterStack(nullptr),
+	spellLvl(0),  effectLevel(0), effectPower(0), enchantPower(0), effectValue(0)
+{
+	casterStack = dynamic_cast<const CStack *>(caster);
+	casterHero = dynamic_cast<const CGHeroInstance *>(caster);
+
+	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);
+}
+
+BattleSpellCastParameters::BattleSpellCastParameters(const BattleSpellCastParameters & orig, const ISpellCaster * caster)
+	:spell(orig.spell), cb(orig.cb), caster(caster), casterColor(caster->getOwner()), casterSide(cb->whatSide(casterColor)),
+	casterHero(nullptr), mode(ECastingMode::MAGIC_MIRROR), casterStack(nullptr),
+	spellLvl(orig.spellLvl),  effectLevel(orig.effectLevel), effectPower(orig.effectPower), enchantPower(orig.enchantPower), effectValue(orig.effectValue)
 {
 	casterStack = dynamic_cast<const CStack *>(caster);
 	casterHero = dynamic_cast<const CGHeroInstance *>(caster);
-	prepare(spell);
 }
 
 void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
@@ -52,28 +72,25 @@ void BattleSpellCastParameters::aimToHex(const BattleHex& destination)
 
 void BattleSpellCastParameters::aimToStack(const CStack * destination)
 {
-	destinations.push_back(Destination(destination));
+	if(nullptr == destination)
+		logGlobal->error("BattleSpellCastParameters::aimToStack invalid stack.");
+	else
+		destinations.push_back(Destination(destination));
 }
 
+void BattleSpellCastParameters::cast(const SpellCastEnvironment * env)
+{
+	spell->battleCast(env, *this);
+}
 
 BattleHex BattleSpellCastParameters::getFirstDestinationHex() const
 {
 	return destinations.at(0).hexValue;
 }
 
-void BattleSpellCastParameters::prepare(const CSpell * spell)
+int BattleSpellCastParameters::getEffectValue() const
 {
-	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);
+	return (effectValue == 0) ? spell->calculateRawEffectValue(effectLevel, effectPower) : effectValue;
 }
 
 ///ISpellMechanics
@@ -83,72 +100,89 @@ ISpellMechanics::ISpellMechanics(CSpell * s):
 
 }
 
-ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
+std::unique_ptr<ISpellMechanics> ISpellMechanics::createMechanics(CSpell * s)
 {
 	switch (s->id)
 	{
 	case SpellID::ANTI_MAGIC:
-		return new AntimagicMechanics(s);
+		return make_unique<AntimagicMechanics>(s);
 	case SpellID::ACID_BREATH_DAMAGE:
-		return new AcidBreathDamageMechanics(s);
+		return make_unique<AcidBreathDamageMechanics>(s);
 	case SpellID::CHAIN_LIGHTNING:
-		return new ChainLightningMechanics(s);
+		return make_unique<ChainLightningMechanics>(s);
 	case SpellID::CLONE:
-		return new CloneMechanics(s);
+		return make_unique<CloneMechanics>(s);
 	case SpellID::CURE:
-		return new CureMechanics(s);
+		return make_unique<CureMechanics>(s);
 	case SpellID::DEATH_STARE:
-		return new DeathStareMechanics(s);
+		return make_unique<DeathStareMechanics>(s);
 	case SpellID::DISPEL:
-		return new DispellMechanics(s);
+		return make_unique<DispellMechanics>(s);
 	case SpellID::DISPEL_HELPFUL_SPELLS:
-		return new DispellHelpfulMechanics(s);
+		return make_unique<DispellHelpfulMechanics>(s);
 	case SpellID::EARTHQUAKE:
-		return new EarthquakeMechanics(s);
+		return make_unique<EarthquakeMechanics>(s);
 	case SpellID::FIRE_WALL:
+		return make_unique<FireWallMechanics>(s);
 	case SpellID::FORCE_FIELD:
-		return new WallMechanics(s);
+		return make_unique<ForceFieldMechanics>(s);
 	case SpellID::HYPNOTIZE:
-		return new HypnotizeMechanics(s);
+		return make_unique<HypnotizeMechanics>(s);
 	case SpellID::LAND_MINE:
+		return make_unique<LandMineMechanics>(s);
 	case SpellID::QUICKSAND:
-		return new ObstacleMechanics(s);
+		return make_unique<QuicksandMechanics>(s);
 	case SpellID::REMOVE_OBSTACLE:
-		return new RemoveObstacleMechanics(s);
+		return make_unique<RemoveObstacleMechanics>(s);
 	case SpellID::SACRIFICE:
-		return new SacrificeMechanics(s);
+		return make_unique<SacrificeMechanics>(s);
 	case SpellID::SUMMON_FIRE_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::FIRE_ELEMENTAL);
+		return make_unique<SummonMechanics>(s, CreatureID::FIRE_ELEMENTAL);
 	case SpellID::SUMMON_EARTH_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::EARTH_ELEMENTAL);
+		return make_unique<SummonMechanics>(s, CreatureID::EARTH_ELEMENTAL);
 	case SpellID::SUMMON_WATER_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::WATER_ELEMENTAL);
+		return make_unique<SummonMechanics>(s, CreatureID::WATER_ELEMENTAL);
 	case SpellID::SUMMON_AIR_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::AIR_ELEMENTAL);
+		return make_unique<SummonMechanics>(s, CreatureID::AIR_ELEMENTAL);
 	case SpellID::TELEPORT:
-		return new TeleportMechanics(s);
+		return make_unique<TeleportMechanics>(s);
+	default:
+		if(s->isRisingSpell())
+			return make_unique<SpecialRisingSpellMechanics>(s);
+		else
+			return make_unique<DefaultSpellMechanics>(s);
+	}
+}
+
+//IAdventureSpellMechanics
+IAdventureSpellMechanics::IAdventureSpellMechanics(CSpell * s):
+	owner(s)
+{
+
+}
+
+std::unique_ptr<IAdventureSpellMechanics> IAdventureSpellMechanics::createMechanics(CSpell * s)
+{
+	switch (s->id)
+	{
 	case SpellID::SUMMON_BOAT:
-		return new SummonBoatMechanics(s);
+		return make_unique<SummonBoatMechanics>(s);
 	case SpellID::SCUTTLE_BOAT:
-		return new ScuttleBoatMechanics(s);
+		return make_unique<ScuttleBoatMechanics>(s);
 	case SpellID::DIMENSION_DOOR:
-		return new DimensionDoorMechanics(s);
+		return make_unique<DimensionDoorMechanics>(s);
 	case SpellID::FLY:
 	case SpellID::WATER_WALK:
 	case SpellID::VISIONS:
 	case SpellID::DISGUISE:
-		return new DefaultSpellMechanics(s); //implemented using bonus system
+		return make_unique<AdventureSpellMechanics>(s); //implemented using bonus system
 	case SpellID::TOWN_PORTAL:
-		return new TownPortalMechanics(s);
+		return make_unique<TownPortalMechanics>(s);
 	case SpellID::VIEW_EARTH:
-		return new ViewEarthMechanics(s);
+		return make_unique<ViewEarthMechanics>(s);
 	case SpellID::VIEW_AIR:
-		return new ViewAirMechanics(s);
+		return make_unique<ViewAirMechanics>(s);
 	default:
-		if(s->isRisingSpell())
-			return new SpecialRisingSpellMechanics(s);
-		else
-			return new DefaultSpellMechanics(s);
+		return std::unique_ptr<IAdventureSpellMechanics>();
 	}
 }
-

+ 52 - 29
lib/spells/ISpellMechanics.h

@@ -45,11 +45,22 @@ public:
 		const BattleHex hexValue;
 	};
 
-	BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell);
+	//normal constructor
+	BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell_);
+
+	//magic mirror constructor
+	BattleSpellCastParameters(const BattleSpellCastParameters & orig, const ISpellCaster * caster);
+
 	void aimToHex(const BattleHex & destination);
 	void aimToStack(const CStack * destination);
+
+	void cast(const SpellCastEnvironment * env);
+
 	BattleHex getFirstDestinationHex() const;
 
+	int getEffectValue() const;
+
+	const CSpell * spell;
 	const BattleInfo * cb;
 	const ISpellCaster * caster;
 	const PlayerColor casterColor;
@@ -60,7 +71,6 @@ public:
 	const CGHeroInstance * casterHero; //deprecated
 	ECastingMode::ECastingMode mode;
 	const CStack * casterStack; //deprecated
-	const CStack * selectedStack;//deprecated
 
 	///spell school level
 	int spellLvl;
@@ -70,54 +80,67 @@ public:
 	int effectPower;
 	///actual spell-power affecting effect duration
 	int enchantPower;
+
+private:
 	///for Archangel-like casting
 	int effectValue;
-private:
-	void prepare(const CSpell * spell);
 };
 
-struct DLL_LINKAGE AdventureSpellCastParameters
+struct DLL_LINKAGE SpellTargetingContext
 {
-	const CGHeroInstance * caster;
-	int3 pos;
+	CSpell::TargetInfo ti;
+	ECastingMode::ECastingMode mode;
+	BattleHex destination;
+	const ISpellCaster * caster;
+	int schoolLvl;
+
+	SpellTargetingContext(const CSpell * s, ECastingMode::ECastingMode mode_, const ISpellCaster * caster_, int schoolLvl_, BattleHex destination_)
+		: ti(s,schoolLvl_, mode_), mode(mode_), destination(destination_), caster(caster_), schoolLvl(schoolLvl_)
+	{};
+
 };
 
 class DLL_LINKAGE ISpellMechanics
 {
-public:
-	struct DLL_LINKAGE SpellTargetingContext
-	{
-		const CBattleInfoCallback * cb;
-		CSpell::TargetInfo ti;
-		ECastingMode::ECastingMode mode;
-		BattleHex destination;
-		PlayerColor casterColor;
-		int schoolLvl;
-
-		SpellTargetingContext(const CSpell * s, const CBattleInfoCallback * c, ECastingMode::ECastingMode m, PlayerColor cc, int lvl, BattleHex dest)
-			: cb(c), ti(s,lvl, m), mode(m), destination(dest), casterColor(cc), schoolLvl(lvl)
-		{};
-
-	};
 public:
 	ISpellMechanics(CSpell * s);
 	virtual ~ISpellMechanics(){};
 
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
-	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
+	virtual std::vector<const CStack *> getAffectedStacks(const CBattleInfoCallback * cb, SpellTargetingContext & ctx) const = 0;
+
+	virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const ECastingMode::ECastingMode mode, const ISpellCaster * caster) const = 0;
 
-	virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
+	virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, const SpellTargetingContext & ctx) 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;
-	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
+	virtual void battleCast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters) const = 0;
+
+	//if true use generic algorithm for target existence check, see CSpell::canBeCast
+	virtual bool requiresCreatureTarget() const = 0;
+
+	static std::unique_ptr<ISpellMechanics> createMechanics(CSpell * s);
+protected:
+	CSpell * owner;
+};
+
+struct DLL_LINKAGE AdventureSpellCastParameters
+{
+	const CGHeroInstance * caster;
+	int3 pos;
+};
 
-	virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
-		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0;
+class DLL_LINKAGE IAdventureSpellMechanics
+{
+public:
+	IAdventureSpellMechanics(CSpell * s);
+	virtual ~IAdventureSpellMechanics() = default;
+
+	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
 
-	static ISpellMechanics * createMechanics(CSpell * s);
+	static std::unique_ptr<IAdventureSpellMechanics> createMechanics(CSpell * s);
 protected:
 	CSpell * owner;
 };

+ 7 - 0
lib/spells/Magic.h

@@ -18,6 +18,7 @@
 class CSpell;
 class CStack;
 class PlayerColor;
+struct MetaString;
 
 class DLL_LINKAGE ISpellCaster
 {
@@ -45,4 +46,10 @@ public:
 	virtual int getEffectValue(const CSpell * spell) const = 0;
 
 	virtual const PlayerColor getOwner() const = 0;
+
+	///only name substitution
+	virtual void getCasterName(MetaString & text) const = 0;
+
+	///full default text
+	virtual void getCastDescription(const CSpell * spell, const std::vector<const CStack *> & attacked, MetaString & text) const = 0;
 };

+ 17 - 18
server/CGameHandler.cpp

@@ -846,7 +846,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, att);
+		auto attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att, bonus->val, targetHex);
 
 		//TODO: get exact attacked hex for defender
 
@@ -2520,7 +2520,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 
 void CGameHandler::sendToAllClients( CPackForClient * info )
 {
-	logGlobal->trace("Sending to all clients a package of type %s", typeid(*info).name());
+	logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
 	for(auto & elem : conns)
 	{
 		boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
@@ -4179,7 +4179,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			bsa.creID = summonedType;
 			ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID.toEnum());
-			ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;//todo: ignore AGE effect
+			ui64 targetHealth = destStack->getCreature()->MaxHealth() * destStack->baseAmount;
 
 			ui64 canRiseHp = std::min(targetHealth, risedHp);
 			ui32 canRiseAmount = canRiseHp / VLC->creh->creatures.at(bsa.creID)->MaxHealth();
@@ -4241,8 +4241,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 				parameters.effectLevel = parameters.spellLvl;
 				parameters.mode = ECastingMode::CREATURE_ACTIVE_CASTING;
 				parameters.aimToHex(destination);//todo: allow multiple destinations
-				parameters.selectedStack = nullptr;
-				spell->battleCast(spellEnv, parameters);
+				parameters.cast(spellEnv);
 			}
 			sendAndApply(&end_action);
 			break;
@@ -4440,7 +4439,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			BattleSpellCastParameters parameters(gs->curB, h, s);
 			parameters.aimToHex(ba.destinationTile);//todo: allow multiple destinations
 			parameters.mode = ECastingMode::HERO_CASTING;
-			parameters.selectedStack = gs->curB->battleGetStackByID(ba.selectedStack, false);
+			if(ba.selectedStack >= 0)
+				parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
 
 			ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
 			if(escp != ESpellCastProblem::OK)
@@ -4452,7 +4452,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			StartAction start_action(ba);
 			sendAndApply(&start_action); //start spell casting
 
-			s->battleCast(spellEnv, parameters);
+			parameters.cast(spellEnv);
 
 			sendAndApply(&end_action);
 			if( !gs->curB->battleGetStackByID(gs->curB->activeStack))
@@ -4592,9 +4592,8 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 					parameters.effectLevel = bonus->val;//todo: recheck
 					parameters.aimToHex(BattleHex::INVALID);
 					parameters.mode = ECastingMode::ENCHANTER_CASTING;
-					parameters.selectedStack = nullptr;
 
-					spell->battleCast(spellEnv, parameters);
+					parameters.cast(spellEnv);
 
 					//todo: move to mechanics
 					BattleSetStackProperty ssp;
@@ -5303,9 +5302,8 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
 				parameters.effectLevel = spellLevel;
 				parameters.aimToStack(oneOfAttacked);
 				parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
-				parameters.selectedStack = nullptr;
 
-				spell->battleCast(spellEnv, parameters);
+				parameters.cast(spellEnv);
 			}
 		}
 	}
@@ -5333,9 +5331,8 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 		parameters.aimToStack(gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked));
 		parameters.effectPower = power;
 		parameters.mode = ECastingMode::AFTER_ATTACK_CASTING;
-		parameters.selectedStack = nullptr;
 
-		spell->battleCast(this->spellEnv, parameters);
+		parameters.cast(spellEnv);
 	};
 
 	attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
@@ -5356,9 +5353,8 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 		vstd::amin(chanceToKill, 1); //cap at 100%
 
 		std::binomial_distribution<> distribution(attacker->count, chanceToKill);
-		std::mt19937 rng(std::time(nullptr));
 
-		int staredCreatures = distribution(rng);
+		int staredCreatures = distribution(getRandomGenerator().getStdGenerator());
 
 		double cap = 1 / std::max(chanceToKill, (double)(0.01));//don't divide by 0
 		int maxToKill = (attacker->count + cap - 1) / cap; //not much more than chance * count
@@ -5628,21 +5624,24 @@ void CGameHandler::runBattle()
 	for(int i = 0; i < 2; ++i)
 	{
 		auto h = gs->curB->battleGetFightingHero(i);
-		if(h && h->hasBonusOfType(Bonus::OPENING_BATTLE_SPELL))
+		if(h)
 		{
 			TBonusListPtr bl = h->getBonuses(Selector::type(Bonus::OPENING_BATTLE_SPELL));
 
 			for (Bonus *b : *bl)
 			{
 				const CSpell * spell = SpellID(b->subtype).toSpell();
+
+				if(ESpellCastProblem::OK != gs->curB->battleCanCastThisSpell(h, spell, ECastingMode::PASSIVE_CASTING))
+					continue;
+
 				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);
+				parameters.cast(spellEnv);
 			}
 		}
 	}

+ 4 - 4
server/CQuery.cpp

@@ -126,7 +126,7 @@ void CObjectVisitQuery::onExposure(CGameHandler *gh, QueryPtr topQuery)
 
 void Queries::popQuery(PlayerColor player, QueryPtr query)
 {
-	LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
+	//LOG_TRACE_PARAMS(logGlobal, "player='%s', query='%s'", player % query);
 	if(topQuery(player) != query)
 	{
 		logGlobal->trace("Cannot remove, not a top!");
@@ -147,7 +147,7 @@ void Queries::popQuery(PlayerColor player, QueryPtr query)
 
 void Queries::popQuery(const CQuery &query)
 {
-	LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
+	//LOG_TRACE_PARAMS(logGlobal, "query='%s'", query);
 
 	assert(query.players.size());
 	for(auto player : query.players)
@@ -174,7 +174,7 @@ void Queries::addQuery(QueryPtr query)
 
 void Queries::addQuery(PlayerColor player, QueryPtr query)
 {
-	LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
+	//LOG_TRACE_PARAMS(logGlobal, "player='%d', query='%s'", player.getNum() % query);
 	query->onAdding(gh, player);
 	queries[player].push_back(query);
 }
@@ -186,7 +186,7 @@ QueryPtr Queries::topQuery(PlayerColor player)
 
 void Queries::popIfTop(QueryPtr query)
 {
-	LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
+	//LOG_TRACE_PARAMS(logGlobal, "query='%d'", query);
 	if(!query)
 		logGlobal->error("The query is nullptr! Ignoring.");