فهرست منبع

Merge pull request #197 from vcmi/issue/2304_2418

Issues/2304/2418/2293
ArseniyShestakov 9 سال پیش
والد
کامیت
7886e8986e

+ 17 - 13
client/battle/CBattleInterface.cpp

@@ -1018,9 +1018,8 @@ void CBattleInterface::stackRemoved(int stackID)
 		}
 	}
 
-	delete creAnims[stackID];
-	creAnims.erase(stackID);
-	creDir.erase(stackID);
+	//todo: ensure that ghost stack animation has fadeout effect
+
 	redrawBackgroundWithHexes(activeStack);
 	queue->update();
 }
@@ -1071,9 +1070,6 @@ void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attacked
 			stackRemoved(attackedInfo.defender->ID);
 	}
 
-/*	if (attackedInfos.front().cloneKilled) //FIXME: cloned stack is already removed
-		return;*/
-
 	if (targets > 1)
 		printConsoleAttacked(attackedInfos.front().defender, damage, killed, attackedInfos.front().attacker, true); //creatures perish
 	else
@@ -2169,14 +2165,14 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			case RISE_DEMONS:
 				if (shere && ourStack && !shere->alive())
 				{
-					if(!(shere->hasBonusOfType(Bonus::UNDEAD) 
-						|| shere->hasBonusOfType(Bonus::NON_LIVING) 
+					if(!(shere->hasBonusOfType(Bonus::UNDEAD)
+						|| shere->hasBonusOfType(Bonus::NON_LIVING)
 						|| vstd::contains(shere->state, EBattleStackState::SUMMONED)
 						|| vstd::contains(shere->state, EBattleStackState::CLONED)
 						|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
 						))
 						legalAction = true;
-				}					
+				}
 				break;
 		}
 		if (legalAction)
@@ -2334,8 +2330,8 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			case RISE_DEMONS:
 				cursorType = ECursor::SPELLBOOK;
 				realizeAction = [=]
-				{ 
-					giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID); 
+				{
+					giveCommand(Battle::DAEMON_SUMMONING, myNumber, activeStack->ID);
 				};
 				break;
 			case CATAPULT:
@@ -3411,8 +3407,13 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
 
 	BattleObjectsByHex sorted;
 
+	auto stacks = curInt->cb->battleGetStacksIf([](const CStack * s)
+	{
+		return !s->isTurret();
+	});
+
 	// Sort creatures
-	for (auto & stack : curInt->cb->battleGetAllStacks())
+	for (auto & stack : stacks)
 	{
 		if(creAnims.find(stack->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
 			continue;
@@ -3420,7 +3421,10 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
 		if (stack->position < 0) // turret shooters are handled separately
 			continue;
 
-		if(!creAnims[stack->ID]->isDead())
+		//FIXME: hack to ignore ghost stacks
+		if((creAnims[stack->ID]->getType() == CCreatureAnim::DEAD || creAnims[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
+			;//ignore
+		else if(!creAnims[stack->ID]->isDead())
 		{
 			if (!creAnims[stack->ID]->isMoving())
 				sorted.hex[stack->position].alive.push_back(stack);

+ 12 - 9
client/windows/CSpellWindow.cpp

@@ -647,16 +647,18 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 				break;
 			case ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED:
 				{
-					std::string text = CGI->generaltexth->allTexts[538], summoner, elemental, caster;
-					std::vector<const CStack *> stacks = owner->myInt->cb->battleGetStacks();
+					std::string text = CGI->generaltexth->allTexts[538], elemental, caster;
+					const PlayerColor player = owner->myInt->playerID;
+
+					const TStacks stacks = owner->myInt->cb->battleGetStacksIf([player](const CStack * s)
+					{
+						return s->owner == player
+							&& vstd::contains(s->state, EBattleStackState::SUMMONED)
+							&& !vstd::contains(s->state, EBattleStackState::CLONED);
+					});
 					for(const CStack * s : stacks)
 					{
-						if(vstd::contains(s->state, EBattleStackState::SUMMONED))
-						{
-							elemental = s->getCreature()->namePl;
-							summoner = owner->myInt->cb->battleGetHeroInfo(!s->attackerOwned).name;
-							break;
-						}
+						elemental = s->getCreature()->namePl;
 					}
 					if (owner->myHero->type->sex)
 					{ //female
@@ -666,8 +668,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 					{ //male
 						caster = CGI->generaltexth->allTexts[539];
 					}
-					text = boost::str(boost::format(text) % summoner % elemental % caster);
+					std::string summoner = owner->myHero->name;
 
+					text = boost::str(boost::format(text) % summoner % elemental % caster);
 
 					owner->myInt->showInfoDialog(text);
 				}

+ 23 - 27
lib/BattleState.cpp

@@ -142,26 +142,6 @@ CStack * BattleInfo::generateNewStack(const CStackBasicDescriptor &base, bool at
 	return ret;
 }
 
-const CStack * BattleInfo::battleGetStack(BattleHex pos, bool onlyAlive)
-{
-	CStack * stack = nullptr;
-	for(auto & elem : stacks)
-	{
-		if(elem->position == pos
-			|| (elem->doubleWide()
-			&&( (elem->attackerOwned && elem->position-1 == pos)
-			||	(!elem->attackerOwned && elem->position+1 == pos)	)
-			) )
-		{
-			if (elem->alive())
-				return elem; //we prefer living stacks - there can be only one stack on the tile, so return it immediately
-			else if (!onlyAlive)
-				stack = elem; //dead stacks are only accessible when there's no alive stack on this tile
-		}
-	}
-	return stack;
-}
-
 const CGHeroInstance * BattleInfo::battleGetOwner(const CStack * stack) const
 {
 	return sides[!stack->attackerOwned].hero;
@@ -741,11 +721,6 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive /*= true*/)
 	return const_cast<CStack *>(battleGetStackByID(stackID, onlyAlive));
 }
 
-CStack * BattleInfo::getStackT(BattleHex tileID, bool onlyAlive /*= true*/)
-{
-	return const_cast<CStack *>(battleGetStackByPos(tileID, onlyAlive));
-}
-
 BattleInfo::BattleInfo()
 {
 	setBattle(this);
@@ -1159,9 +1134,24 @@ std::string CStack::getName() const
 	return (count > 1) ? type->namePl : type->nameSing; //War machines can't use base
 }
 
-bool CStack::isValidTarget(bool allowDead/* = false*/) const /*alive non-turret stacks (can be attacked or be object of magic effect) */
+bool CStack::isValidTarget(bool allowDead/* = false*/) const
+{
+	return (alive() || (allowDead && isDead())) && position.isValid() && !isTurret();
+}
+
+bool CStack::isDead() const
+{
+	return !alive() && !isGhost();
+}
+
+bool CStack::isGhost() const
 {
-	return (alive() || allowDead) && position.isValid();
+	return vstd::contains(state,EBattleStackState::GHOST);
+}
+
+bool CStack::isTurret() const
+{
+	return type->idNumber == CreatureID::ARROW_TOWERS;
 }
 
 bool CStack::canBeHealed() const
@@ -1171,6 +1161,12 @@ bool CStack::canBeHealed() const
 		&& !hasBonusOfType(Bonus::SIEGE_WEAPON);
 }
 
+void CStack::makeGhost()
+{
+	state.erase(EBattleStackState::ALIVE);
+	state.insert(EBattleStackState::GHOST_PENDING);
+}
+
 ui32 CStack::calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const
 {
 	if(!resurrect && !alive())

+ 11 - 10
lib/BattleState.h

@@ -107,12 +107,11 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	~BattleInfo(){};
 
 	//////////////////////////////////////////////////////////////////////////
-	CStack * getStackT(BattleHex tileID, bool onlyAlive = true);
 	CStack * getStack(int stackID, bool onlyAlive = true);
 	using CBattleInfoEssentials::battleGetArmyObject;
-	CArmedInstance * battleGetArmyObject(ui8 side) const; 
+	CArmedInstance * battleGetArmyObject(ui8 side) const;
 	using CBattleInfoEssentials::battleGetFightingHero;
-	CGHeroInstance * battleGetFightingHero(ui8 side) const; 
+	CGHeroInstance * battleGetFightingHero(ui8 side) const;
 
 	const CStack * getNextStack() const; //which stack will have turn after current one
 	//void getStackQueue(std::vector<const CStack *> &out, int howMany, int turn = 0, int lastMoved = -1) const; //returns stack in order of their movement action
@@ -142,7 +141,6 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 
 	const CGHeroInstance * getHero(PlayerColor player) const; //returns fighting hero that belongs to given player
 
-	const CStack * battleGetStack(BattleHex pos, bool onlyAlive); //returns stack at given tile
 	const CGHeroInstance * battleGetOwner(const CStack * stack) const; //returns hero that owns given stack; nullptr if none
 	void localInit();
 
@@ -247,7 +245,10 @@ public:
 	int getEffectValue(const CSpell * spell) const override;
 
 	const PlayerColor getOwner() const override;
-	
+
+	///stack will be ghost in next battle state update
+	void makeGhost();
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		assert(isIndependentNode());
@@ -288,11 +289,11 @@ public:
 	{
 		return vstd::contains(state,EBattleStackState::ALIVE);
 	}
-	bool idDeadClone() const //determines if stack is alive
-	{
-		return vstd::contains(state,EBattleStackState::DEAD_CLONE);
-	}
-	bool isValidTarget(bool allowDead = false) const; //alive non-turret stacks (can be attacked or be object of magic effect)
+
+	bool isDead() const;
+	bool isGhost() const; //determines if stack was removed
+	bool isValidTarget(bool allowDead = false) const; //non-turret non-ghost stacks (can be attacked or be object of magic effect)
+	bool isTurret() const;
 };
 
 class DLL_LINKAGE CMP_stack

+ 20 - 16
lib/CBattleCallback.cpp

@@ -180,33 +180,33 @@ bool CBattleInfoEssentials::battleHasNativeStack(ui8 side) const
 
 TStacks CBattleInfoEssentials::battleGetAllStacks(bool includeTurrets /*= false*/) const
 {
-	return battleGetStacksIf([](const CStack * s){return true;},includeTurrets);
+	return battleGetStacksIf([=](const CStack * s)
+	{
+		return !s->isGhost() && (includeTurrets || !s->isTurret());
+	});
 }
 
-TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate, bool includeTurrets /*= false*/) const
+TStacks CBattleInfoEssentials::battleGetStacksIf(TStackFilter predicate) const
 {
 	TStacks ret;
 	RETURN_IF_NOT_BATTLE(ret);
 
-	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), [=](const CStack * s){
-		return predicate(s) && (includeTurrets || !(s->type->idNumber == CreatureID::ARROW_TOWERS));
-	});
+	vstd::copy_if(getBattle()->stacks, std::back_inserter(ret), predicate);
 
 	return ret;
 }
 
-
 TStacks CBattleInfoEssentials::battleAliveStacks() const
 {
 	return battleGetStacksIf([](const CStack * s){
-		return s->alive();
+		return s->isValidTarget(false);
 	});
 }
 
 TStacks CBattleInfoEssentials::battleAliveStacks(ui8 side) const
 {
 	return battleGetStacksIf([=](const CStack * s){
-		return s->alive() && s->attackerOwned == !side;
+		return s->isValidTarget(false) && s->attackerOwned == !side;
 	});
 }
 
@@ -256,11 +256,15 @@ const CStack* CBattleInfoEssentials::battleGetStackByID(int ID, bool onlyAlive)
 {
 	RETURN_IF_NOT_BATTLE(nullptr);
 
-	for(auto s : battleGetAllStacks(true))
-		if(s->ID == ID  &&  (!onlyAlive || s->alive()))
-			return s;
+	auto stacks = battleGetStacksIf([=](const CStack * s)
+	{
+		return s->ID == ID && (!onlyAlive || s->alive());
+	});
 
-	return nullptr;
+	if(stacks.empty())
+		return nullptr;
+	else
+		return stacks[0];
 }
 
 bool CBattleInfoEssentials::battleDoWeKnowAbout(ui8 side) const
@@ -1304,8 +1308,8 @@ std::pair<const CStack *, BattleHex> CBattleInfoCallback::getNearestStack(const
 
 	std::vector<const CStack *> possibleStacks = battleGetStacksIf([=](const CStack * s)
 	{
-		return s != closest && s->alive() && (boost::logic::indeterminate(attackerOwned) || s->attackerOwned == attackerOwned);
-	}, false);
+		return s->isValidTarget(false) && s != closest && (boost::logic::indeterminate(attackerOwned) || s->attackerOwned == attackerOwned);
+	});
 
 	for(const CStack * st : possibleStacks)
 		for(BattleHex hex : avHexes)
@@ -2225,8 +2229,8 @@ TStacks CPlayerBattleCallback::battleGetStacks(EStackOwnership whose /*= MINE_AN
 		const bool ownerMatches = (whose == MINE_AND_ENEMY)
 			|| (whose == ONLY_MINE && s->owner == player)
 			|| (whose == ONLY_ENEMY && s->owner != player);
-		const bool alivenessMatches = s->alive()  ||  !onlyAlive;
-		return ownerMatches && alivenessMatches;
+
+		return ownerMatches && s->isValidTarget(!onlyAlive);
 	});
 }
 

+ 13 - 13
lib/CBattleCallback.h

@@ -56,7 +56,7 @@ protected:
 	CCallbackBase()
 		: battle(nullptr), gs(nullptr)
 	{}
-	
+
 	void setBattle(const BattleInfo *B);
 	bool duringBattle() const;
 
@@ -83,8 +83,8 @@ namespace EAccessibility
 {
 	enum EAccessibility
 	{
-		ACCESSIBLE, 
-		ALIVE_STACK, 
+		ACCESSIBLE,
+		ALIVE_STACK,
 		OBSTACLE,
 		DESTRUCTIBLE_WALL,
 		GATE, //sieges -> gate opens only for defender stacks
@@ -125,7 +125,7 @@ struct DLL_LINKAGE ReachabilityInfo
 	struct DLL_LINKAGE Parameters
 	{
 		const CStack *stack; //stack for which calculation is mage => not required (kept for debugging mostly), following variables are enough
-		
+
 		bool attackerOwned;
 		bool doubleWide;
 		bool flying;
@@ -171,15 +171,15 @@ public:
 	ETerrainType battleTerrainType() const;
 	BFieldType battleGetBattlefieldType() const;
 	std::vector<std::shared_ptr<const CObstacleInstance> > battleGetAllObstacles(boost::optional<BattlePerspective::BattlePerspective> perspective = boost::none) const; //returns all obstacles on the battlefield
-    
+
     /** @brief Main method for getting battle stacks
      *
      * @param predicate Functor that shall return true for desired stack
      * @return filtered stacks
      *
-     */                             	
-	TStacks battleGetStacksIf(TStackFilter predicate, bool includeTurrets = false) const;
-	
+     */
+	TStacks battleGetStacksIf(TStackFilter predicate) const;
+
 	bool battleHasNativeStack(ui8 side) const;
 	int battleGetMoatDmg() const; //what dmg unit will suffer if ending turn in the moat
 	const CGTownInstance * battleGetDefendedTown() const; //returns defended town if current battle is a siege, nullptr instead
@@ -193,7 +193,7 @@ public:
 	bool battleHasHero(ui8 side) const;
 	int battleCastSpells(ui8 side) const; //how many spells has given side cast
 	const CGHeroInstance * battleGetFightingHero(ui8 side) const; //depracated for players callback, easy to get wrong
-	const CArmedInstance * battleGetArmyObject(ui8 side) const; 
+	const CArmedInstance * battleGetArmyObject(ui8 side) const;
 	InfoAboutHero battleGetHeroInfo(ui8 side) const;
 
 	// for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall,
@@ -204,7 +204,7 @@ public:
 	//helpers
 	///returns all stacks, alive or dead or undead or mechanical :)
 	TStacks battleGetAllStacks(bool includeTurrets = false) const;
-	
+
 	///returns all alive stacks excluding turrets
 	TStacks battleAliveStacks() const;
 	///returns all alive stacks from particular side excluding turrets
@@ -255,12 +255,12 @@ public:
 	int battleGetSurrenderCost(PlayerColor Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
 	ReachabilityInfo::TDistances battleGetDistances(const CStack * stack, BattleHex hex = BattleHex::INVALID, BattleHex * predecessors = nullptr) const; //returns vector of distances to [dest hex number]
 	std::set<BattleHex> battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const;
-	
+
 	bool battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination
 	bool battleCanShoot(const CStack * stack, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination
 	bool battleIsStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
 	std::set<const CStack*>  batteAdjacentCreatures (const CStack * stack) const;
-	
+
 	TDmgRange calculateDmgRange(const BattleAttackInfo &info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
@@ -278,7 +278,7 @@ public:
 	bool isWallPartPotentiallyAttackable(EWallPart::EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not
 	std::vector<BattleHex> getAttackableBattleHexes() const;
 
-	//*** MAGIC 
+	//*** 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

+ 3 - 2
lib/GameConstants.h

@@ -471,7 +471,7 @@ namespace EBattleStackState
 	{
 		ALIVE = 180,
 		SUMMONED, CLONED,
-		DEAD_CLONE,
+		GHOST, //stack was removed from battlefield
 		HAD_MORALE,
 		WAITING,
 		MOVED,
@@ -480,7 +480,8 @@ namespace EBattleStackState
 		//remember to drain mana only once per turn
 		DRAINED_MANA,
 		//only for defending animation
-		DEFENDING_ANIM
+		DEFENDING_ANIM,
+		GHOST_PENDING// stack will become GHOST in next battle state update
 	};
 }
 

+ 37 - 6
lib/NetPacksLib.cpp

@@ -1308,6 +1308,16 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 	if(killed())
 	{
 		at->state -= EBattleStackState::ALIVE;
+
+		if(at->cloneID >= 0)
+		{
+			//remove clone as well
+			CStack * clone = gs->curB->getStack(at->cloneID);
+			if(clone)
+				clone->makeGhost();
+
+			at->cloneID = -1;
+		}
 	}
 	//life drain handling
 	for (auto & elem : healedStacks)
@@ -1322,7 +1332,7 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 	if (cloneKilled())
 	{
 		//"hide" killed creatures instead so we keep info about it
-		at->state.insert(EBattleStackState::DEAD_CLONE);
+		at->makeGhost();
 
 		for(CStack * s : gs->curB->stacks)
 		{
@@ -1330,6 +1340,12 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 				s->cloneID = -1;
 		}
 	}
+
+	//killed summoned creature should be removed like clone
+	if(killed() && vstd::contains(at->state, EBattleStackState::SUMMONED))
+	{
+		at->makeGhost();
+	}
 }
 
 DLL_LINKAGE void BattleAttack::applyGs( CGameState *gs )
@@ -1613,20 +1629,35 @@ DLL_LINKAGE void BattleStacksRemoved::applyGs( CGameState *gs )
 {
 	if(!gs->curB)
 		return;
-	for(ui32 rem_stack : stackIDs)
+
+	while(!stackIDs.empty())
 	{
+		ui32 rem_stack = *stackIDs.begin();
+
 		for(int b=0; b<gs->curB->stacks.size(); ++b) //find it in vector of stacks
 		{
 			if(gs->curB->stacks[b]->ID == rem_stack) //if found
 			{
-				CStack *toRemove = gs->curB->stacks[b];
-				gs->curB->stacks.erase(gs->curB->stacks.begin() + b); //remove
+				CStack * toRemove = gs->curB->stacks[b];
+
+				toRemove->state.erase(EBattleStackState::ALIVE);
+				toRemove->state.erase(EBattleStackState::GHOST_PENDING);
+				toRemove->state.insert(EBattleStackState::GHOST);
+				toRemove->detachFromAll();//TODO: may be some bonuses should remain
+
+				//stack may be removed instantly (not being killed first)
+				//handle clone remove also here
+				if(toRemove->cloneID >= 0)
+				{
+					stackIDs.insert(toRemove->cloneID);
+					toRemove->cloneID = -1;
+				}
 
-				toRemove->detachFromAll();
-				delete toRemove;
 				break;
 			}
 		}
+
+		stackIDs.erase(rem_stack);
 	}
 }
 

+ 3 - 4
lib/spells/BattleSpellMechanics.cpp

@@ -637,14 +637,13 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 ///SummonMechanics
 ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const
 {
-	const ui8 side = cb->playerToSide(player);
-
 	//check if there are summoned elementals of other type
 
-	auto otherSummoned = cb->battleGetStacksIf([side, this](const CStack * st)
+	auto otherSummoned = cb->battleGetStacksIf([player, this](const CStack * st)
 	{
-		return (st->attackerOwned == !side)
+		return (st->owner == player)
 			&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
+			&& (!vstd::contains(st->state, EBattleStackState::CLONED))
 			&& (st->getCreature()->idNumber != creatureToSummon);
 	});
 

+ 2 - 4
lib/spells/CDefaultSpellMechanics.cpp

@@ -360,9 +360,8 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS
 		TStacks mirrorTargets = parameters.cb->battleGetStacksIf([this, parameters](const CStack * battleStack)
 		{
 			//Get all enemy stacks. Magic mirror can reflect to immune creature (with no effect)
-			return battleStack->owner == parameters.casterColor;
-		},
-		true);//turrets included
+			return battleStack->owner == parameters.casterColor && battleStack->isValidTarget(false);
+		});
 
 		if(!mirrorTargets.empty())
 		{
@@ -711,7 +710,6 @@ std::set<const CStack *> DefaultSpellMechanics::getAffectedStacks(SpellTargeting
 			//for massive spells add all targets
 			for (auto stack : stacks)
 				attackedCres.insert(stack);
-
 		}
 		else
 		{

+ 3 - 2
lib/spells/CSpellHandler.cpp

@@ -309,8 +309,9 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
 ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
 {
 	// Get all stacks at destination hex. only alive if not rising spell
-	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s){
-		return s->coversPos(destination) && (isRisingSpell() || s->alive());
+	TStacks stacks = cb->battleGetStacksIf([=](const CStack * s)
+	{
+		return s->coversPos(destination) && s->isValidTarget(isRisingSpell());
 	});
 
 	if(!stacks.empty())

+ 50 - 62
server/CGameHandler.cpp

@@ -4400,7 +4400,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			s->battleCast(spellEnv, parameters);
 
 			sendAndApply(&end_action);
-			if( !gs->curB->battleGetStackByID(gs->curB->activeStack, true))
+			if( !gs->curB->battleGetStackByID(gs->curB->activeStack))
 			{
 				battleMadeAction.setn(true);
 			}
@@ -5606,24 +5606,26 @@ void CGameHandler::runBattle()
 
 		const BattleInfo & curB = *gs->curB;
 
-		//remove clones after all mechanics and animations are handled!
-		std::set <const CStack*> stacksToRemove;
-		for (auto stack : curB.stacks)
-		{
-			if (stack->idDeadClone())
-				stacksToRemove.insert(stack);
-		}
-		for (auto stack : stacksToRemove)
-		{
-			BattleStacksRemoved bsr;
-			bsr.stackIDs.insert(stack->ID);
-			sendAndApply(&bsr);
-		}
 		//stack loop
 
 		const CStack *next;
 		while(!battleResult.get() && (next = curB.getNextStack()) && next->willMove())
 		{
+
+			std::set <const CStack *> stacksToRemove;
+			for(auto stack : curB.stacks)
+			{
+				if(vstd::contains(stack->state, EBattleStackState::GHOST_PENDING))
+					stacksToRemove.insert(stack);
+			}
+
+			for(auto stack : stacksToRemove)
+			{
+				BattleStacksRemoved bsr;
+				bsr.stackIDs.insert(stack->ID);
+				sendAndApply(&bsr);
+			}
+
 			//check for bad morale => freeze
 			int nextStackMorale = next->MoraleVal();
 			if( nextStackMorale < 0 &&
@@ -5716,8 +5718,9 @@ void CGameHandler::runBattle()
 
 			if(next->getCreature()->idNumber == CreatureID::FIRST_AID_TENT)
 			{
-				TStacks possibleStacks = battleGetStacksIf([&](const CStack * s){
-					return s->owner == next->owner  &&  s->canBeHealed();
+				TStacks possibleStacks = battleGetStacksIf([=](const CStack * s)
+				{
+					return s->owner == next->owner && s->canBeHealed();
 				});
 
 				if(!possibleStacks.size())
@@ -6091,44 +6094,6 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
 	if(color == PlayerColor::UNFLAGGABLE)
 		color = PlayerColor::NEUTRAL;
 
-	auto killStack = [&, this](const SlotID slot, const CStackInstance * instance)
-	{
-		StackLocation sl(army, slot);
-		newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
-		if(nullptr == instance)
-			return;
-		auto c = dynamic_cast <const CCommanderInstance *>(instance);
-		if (c) //switch commander status to dead
-		{
-			auto h = dynamic_cast <const CGHeroInstance *>(army);
-			if (h && h->commander == c)
-				heroWithDeadCommander = army->id; //TODO: unify commander handling
-		}
-	};
-
-	//1. Find removed stacks.
-	for(const auto & slotInfo : army->stacks)
-	{
-		const SlotID slot = slotInfo.first;
-		const CStackInstance * instance = slotInfo.second;
-
-		if(nullptr != instance)//just in case
-		{
-			bool found = false;
-			for(const CStack * sta : bat->stacks)
-			{
-				if(sta->base == instance)
-				{
-					found = true;
-					break;
-				}
-			}
-			//stack in this slot was removed == it is dead
-			if(!found)
-				killStack(slot, instance);
-		}
-	}
-
 	for(CStack *st : bat->stacks)
 	{
 		if(vstd::contains(st->state, EBattleStackState::SUMMONED)) //don't take into account temporary summoned stacks
@@ -6144,7 +6109,7 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
 		if(st->slot == SlotID::ARROW_TOWERS_SLOT)
 		{
 			//do nothing
-			logGlobal->debugStream() << "Ignored arrow towers stack";
+			logGlobal->debug("Ignored arrow towers stack.");
 		}
 		else if(st->slot == SlotID::WAR_MACHINES_SLOT)
 		{
@@ -6157,40 +6122,63 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
 			//catapult artifact remain even if "creature" killed in siege
 			else if(warMachine != ArtifactID::CATAPULT && !st->count)
 			{
-				logGlobal->debugStream() << "War machine has been destroyed";
+				logGlobal->debug("War machine has been destroyed");
 				auto hero = dynamic_ptr_cast<CGHeroInstance> (army);
 				if (hero)
 					removedWarMachines.push_back (ArtifactLocation(hero, hero->getArtPos(warMachine, true)));
 				else
-					logGlobal->errorStream() << "War machine in army without hero";
+					logGlobal->error("War machine in army without hero");
 			}
 		}
 		else if(st->slot == SlotID::SUMMONED_SLOT_PLACEHOLDER)
 		{
 			if(st->alive() && st->count > 0)
 			{
-				logGlobal->debugStream() << "Stack has been permanently summoned";
-				//this stack was permanently summoned
+				logGlobal->debugStream() << "Permanently summoned " + st->count << " units.";
 				const CreatureID summonedType = st->type->idNumber;
 				summoned[summonedType] += st->count;
 			}
 		}
+		else if(st->slot == SlotID::COMMANDER_SLOT_PLACEHOLDER)
+		{
+			if(nullptr == st->base)
+			{
+				logGlobal->error("Stack with no base in commander slot.");
+			}
+			else
+			{
+				auto c = dynamic_cast <const CCommanderInstance *>(st->base);
+				if(c)
+				{
+					auto h = dynamic_cast <const CGHeroInstance *>(army);
+					if (h && h->commander == c)
+					{
+						logGlobal->debug("Commander is dead.");
+						heroWithDeadCommander = army->id; //TODO: unify commander handling
+					}
+				}
+				else
+					logGlobal->error("Stack with invalid instance in commander slot.");
+			}
+		}
 		else if(st->base && !army->slotEmpty(st->slot))
 		{
 			if(st->count == 0 || !st->alive())
 			{
-				killStack(st->slot, st->base);
-				logGlobal->debugStream() << "Stack has been destroyed";
+				logGlobal->debug("Stack has been destroyed.");
+				StackLocation sl(army, st->slot);
+				newStackCounts.push_back(TStackAndItsNewCount(sl, 0));
 			}
 			else if(st->count < army->getStackCount(st->slot))
 			{
+				logGlobal->debugStream() << "Stack lost " << (army->getStackCount(st->slot) - st->count) << " units.";
 				StackLocation sl(army, st->slot);
 				newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
 			}
 		}
 		else
 		{
-			logGlobal->warnStream() << "Unhandled stack " << st->nodeName();
+			logGlobal->warnStream() << "Unable to process stack: " << st->nodeName();
 		}
 	}
 }