Browse Source

Moved stacks & animations handling into a separate class

Ivan Savenko 2 years ago
parent
commit
b01737daf2

+ 2 - 0
client/CMakeLists.txt

@@ -10,6 +10,7 @@ set(client_SRCS
 		battle/CBattleObstacleController.cpp
 		battle/CBattleProjectileController.cpp
 		battle/CBattleSiegeController.cpp
+		battle/CBattleStacksController.cpp
 		battle/CCreatureAnimation.cpp
 
 		gui/CAnimation.cpp
@@ -88,6 +89,7 @@ set(client_HEADERS
 		battle/CBattleObstacleController.h
 		battle/CBattleProjectileController.h
 		battle/CBattleSiegeController.h
+		battle/CBattleStacksController.h
 		battle/CCreatureAnimation.h
 
 		gui/CAnimation.h

+ 5 - 25
client/CPlayerInterface.cpp

@@ -738,34 +738,14 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 		{
 		case UnitChanges::EOperation::RESET_STATE:
 			{
-				const battle::Unit * unit = cb->battleGetUnitByID(info.id);
+				const CStack * stack = cb->battleGetStackByID(info.id );
 
-				if(!unit)
+				if(!stack)
 				{
 					logGlobal->error("Invalid unit ID %d", info.id);
 					continue;
 				}
-
-				auto iter = battleInt->creAnims.find(info.id);
-
-				if(iter == battleInt->creAnims.end())
-				{
-					logGlobal->error("Unit %d have no animation", info.id);
-					continue;
-				}
-
-				auto animation = iter->second;
-
-				if(unit->alive() && animation->isDead())
-					animation->setType(CCreatureAnim::HOLDING);
-
-				if (unit->isClone())
-				{
-					std::unique_ptr<ColorShifterDeepBlue> shifter(new ColorShifterDeepBlue());
-					animation->shiftColor(shifter.get());
-				}
-
-				//TODO: handle more cases
+				battleInt->stackReset(stack);
 			}
 			break;
 		case UnitChanges::EOperation::REMOVE:
@@ -779,7 +759,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 					logGlobal->error("Invalid unit ID %d", info.id);
 					continue;
 				}
-				battleInt->unitAdded(unit);
+				battleInt->stackAdded(unit);
 			}
 			break;
 		default:
@@ -808,7 +788,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
 		}
 		else
 		{
-			battleInt->fieldController->redrawBackgroundWithHexes(battleInt->activeStack);
+			battleInt->fieldController->redrawBackgroundWithHexes();
 		}
 	}
 }

+ 80 - 37
client/battle/CBattleAnimations.cpp

@@ -17,6 +17,7 @@
 #include "CBattleProjectileController.h"
 #include "CBattleSiegeController.h"
 #include "CBattleFieldController.h"
+#include "CBattleStacksController.h"
 #include "CCreatureAnimation.h"
 
 #include "../CGameInfo.h"
@@ -34,7 +35,7 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 
 CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
-	: owner(_owner), ID(_owner->animIDhelper++)
+	: owner(_owner), ID(_owner->stacksController->animIDhelper++)
 {
 	logAnim->trace("Animation #%d created", ID);
 }
@@ -44,10 +45,35 @@ CBattleAnimation::~CBattleAnimation()
 	logAnim->trace("Animation #%d deleted", ID);
 }
 
+std::list<std::pair<CBattleAnimation *, bool>> & CBattleAnimation::pendingAnimations()
+{
+	return owner->stacksController->pendingAnims;
+}
+
+std::shared_ptr<CCreatureAnimation> CBattleAnimation::stackAnimation(const CStack * stack)
+{
+	return owner->stacksController->creAnims[stack->ID];
+}
+
+bool CBattleAnimation::stackFacingRight(const CStack * stack)
+{
+	return owner->stacksController->creDir[stack->ID];
+}
+
+ui32 CBattleAnimation::maxAnimationID()
+{
+	return owner->stacksController->animIDhelper;
+}
+
+void CBattleAnimation::setStackFacingRight(const CStack * stack, bool facingRight)
+{
+	owner->stacksController->creDir[stack->ID] = facingRight;
+}
+
 void CBattleAnimation::endAnim()
 {
 	logAnim->trace("Animation #%d ended, type is %s", ID, typeid(this).name());
-	for(auto & elem : owner->pendingAnims)
+	for(auto & elem : pendingAnimations())
 	{
 		if(elem.first == this)
 		{
@@ -58,13 +84,12 @@ void CBattleAnimation::endAnim()
 
 bool CBattleAnimation::isEarliest(bool perStackConcurrency)
 {
-	int lowestMoveID = owner->animIDhelper + 5;
+	int lowestMoveID = maxAnimationID() + 5;//FIXME: why 5?
 	CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
 	CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this);
 
-	for(auto & elem : owner->pendingAnims)
+	for(auto & elem : pendingAnimations())
 	{
-
 		CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
 		CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
 		if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
@@ -81,12 +106,12 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
 		if(elem.first)
 			vstd::amin(lowestMoveID, elem.first->ID);
 	}
-	return (ID == lowestMoveID) || (lowestMoveID == (owner->animIDhelper + 5));
+	return (ID == lowestMoveID) || (lowestMoveID == (maxAnimationID() + 5));
 }
 
 CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CStack * stack)
 	: CBattleAnimation(owner),
-	  myAnim(owner->creAnims[stack->ID]),
+	  myAnim(stackAnimation(stack)),
 	  stack(stack)
 {
 	assert(myAnim);
@@ -125,7 +150,7 @@ void CAttackAnimation::endAnim()
 
 bool CAttackAnimation::checkInitialConditions()
 {
-	for(auto & elem : owner->pendingAnims)
+	for(auto & elem : pendingAnimations())
 	{
 		CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
 		CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
@@ -162,8 +187,8 @@ bool CDefenceAnimation::init()
 	if(attacker == nullptr && owner->battleEffects.size() > 0)
 		return false;
 
-	ui32 lowestMoveID = owner->animIDhelper + 5;
-	for(auto & elem : owner->pendingAnims)
+	ui32 lowestMoveID = maxAnimationID() + 5;
+	for(auto & elem : pendingAnimations())
 	{
 
 		CDefenceAnimation * defAnim = dynamic_cast<CDefenceAnimation *>(elem.first);
@@ -192,9 +217,9 @@ bool CDefenceAnimation::init()
 
 
 	//reverse unit if necessary
-	if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID]))
+	if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), stackFacingRight(stack), attacker->doubleWide(), stackFacingRight(attacker)))
 	{
-		owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
+		owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
 		return false;
 	}
 	//unit reversed
@@ -209,7 +234,7 @@ bool CDefenceAnimation::init()
 	if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
 	{
 		float frameLength = AnimationControls::getCreatureAnimationSpeed(
-								  stack->getCreature(), owner->creAnims[stack->ID].get(), getMyAnimType());
+								  stack->getCreature(), stackAnimation(stack).get(), getMyAnimType());
 
 		timeToWait = myAnim->framesInGroup(getMyAnimType()) * frameLength / 2;
 
@@ -325,17 +350,17 @@ bool CMeleeAttackAnimation::init()
 		return false;
 	}
 
-	bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]);
+	bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), stackFacingRight(stack), attackedStack->doubleWide(), stackFacingRight(attackedStack));
 
 	if(toReverse)
 	{
-		owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
+		owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
 		return false;
 	}
 
 	// opponent must face attacker ( = different directions) before he can be attacked
 	if(attackingStack && attackedStack &&
-		owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
+		stackFacingRight(attackingStack) == stackFacingRight(attackedStack))
 		return false;
 
 	//reversed
@@ -428,7 +453,7 @@ bool CMovementAnimation::init()
 		return false;
 	}
 
-	if(owner->creAnims[stack->ID]->framesInGroup(CCreatureAnim::MOVING) == 0 ||
+	if(stackAnimation(stack)->framesInGroup(CCreatureAnim::MOVING) == 0 ||
 	   stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
 	{
 		//no movement or teleport, end immediately
@@ -437,18 +462,18 @@ bool CMovementAnimation::init()
 	}
 
 	//reverse unit if necessary
-	if(owner->shouldRotate(stack, oldPos, nextHex))
+	if(owner->stacksController->shouldRotate(stack, oldPos, nextHex))
 	{
 		// it seems that H3 does NOT plays full rotation animation here in most situations
 		// Logical since it takes quite a lot of time
 		if (curentMoveIndex == 0) // full rotation only for moving towards first tile.
 		{
-			owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true));
+			owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true));
 			return false;
 		}
 		else
 		{
-			CReverseAnimation::rotateStack(owner, stack, oldPos);
+			rotateStack(oldPos);
 		}
 	}
 
@@ -508,7 +533,7 @@ void CMovementAnimation::nextFrame()
 			nextHex = destTiles[curentMoveIndex];
 
 			// re-init animation
-			for(auto & elem : owner->pendingAnims)
+			for(auto & elem : pendingAnimations())
 			{
 				if (elem.first == this)
 				{
@@ -529,7 +554,7 @@ void CMovementAnimation::endAnim()
 	myAnim->pos = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
 	CBattleAnimation::endAnim();
 
-	owner->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex));
+	owner->stacksController->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex));
 
 	if(owner->moveSoundHander != -1)
 	{
@@ -662,11 +687,11 @@ void CReverseAnimation::endAnim()
 	delete this;
 }
 
-void CReverseAnimation::rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex)
+void CBattleStackAnimation::rotateStack(BattleHex hex)
 {
-	owner->creDir[stack->ID] = !owner->creDir[stack->ID];
+	setStackFacingRight(stack, !stackFacingRight(stack));
 
-	owner->creAnims[stack->ID]->pos = CClickableHex::getXYUnitAnim(hex, stack, owner);
+	stackAnimation(stack)->pos = CClickableHex::getXYUnitAnim(hex, stack, owner);
 }
 
 void CReverseAnimation::setupSecondPart()
@@ -677,7 +702,7 @@ void CReverseAnimation::setupSecondPart()
 		return;
 	}
 
-	rotateStack(owner, stack, hex);
+	rotateStack(hex);
 
 	if(myAnim->framesInGroup(CCreatureAnim::TURN_R))
 	{
@@ -716,9 +741,9 @@ bool CShootingAnimation::init()
 	}
 
 	//reverse unit if necessary
-	if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
+	if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), stackFacingRight(attackingStack), attackingStack->doubleWide(), stackFacingRight(attackedStack)))
 	{
-		owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
+		owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 		return false;
 	}
 
@@ -743,7 +768,7 @@ bool CShootingAnimation::init()
 	Point destPos;
 
 	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
-	shooterPos = owner->creAnims[shooter->ID]->pos.topLeft();
+	shooterPos = stackAnimation(shooter)->pos.topLeft();
 	//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
 
 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225);
@@ -751,7 +776,7 @@ bool CShootingAnimation::init()
 	// to properly translate coordinates when shooter is rotated
 	int multiplier = 0;
 	if (shooter)
-		multiplier = owner->creDir[shooter->ID] ? 1 : -1;
+		multiplier = stackFacingRight(shooter) ? 1 : -1;
 	else
 	{
 		assert(false); // unreachable?
@@ -800,7 +825,22 @@ bool CShootingAnimation::init()
 
 void CShootingAnimation::nextFrame()
 {
-	for(auto & it : owner->pendingAnims)
+	if (owner->projectilesController->hasActiveProjectile(attackingStack))
+	{
+		const CCreature *shooterInfo = attackingStack->getCreature();
+
+		if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
+			shooterInfo = owner->siegeController->turretCreature();
+
+		// animation should be paused if there is an active projectile
+		if ( stackAnimation(attackingStack)->getCurrentFrame() >= shooterInfo->animation.attackClimaxFrame )
+		{
+			owner->projectilesController->fireStackProjectile(attackingStack);//FIXME: should only be called once
+			return;
+		}
+	}
+
+	for(auto & it : pendingAnimations())
 	{
 		CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first);
 		CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first);
@@ -813,6 +853,9 @@ void CShootingAnimation::nextFrame()
 
 void CShootingAnimation::endAnim()
 {
+	// FIXME: is this possible? Animation is over but we're yet to fire projectile?
+	owner->projectilesController->fireStackProjectile(attackingStack);
+
 	// play wall hit/miss sound for catapult attack
 	if(!attackedStack)
 	{
@@ -851,17 +894,17 @@ bool CCastAnimation::init()
 	//reverse unit if necessary
 	if(attackedStack)
 	{
-		if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID]))
+		if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), stackFacingRight(attackingStack), attackingStack->doubleWide(), stackFacingRight(attackedStack)))
 		{
-			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
+			owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 			return false;
 		}
 	}
 	else
 	{
-		if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false))
+		if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, stackFacingRight(attackingStack), false, false))
 		{
-			owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
+			owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
 			return false;
 		}
 	}
@@ -875,7 +918,7 @@ bool CCastAnimation::init()
 	Point destPos;
 
 	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
-	fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft();
+	fromPos = stackAnimation(attackingStack)->pos.topLeft();
 	//xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner);
 
 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
@@ -932,7 +975,7 @@ bool CCastAnimation::init()
 
 void CCastAnimation::nextFrame()
 {
-	for(auto & it : owner->pendingAnims)
+	for(auto & it : pendingAnimations())
 	{
 		CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it.first);
 		if(anim && anim->stack->ID == stack->ID && anim->priority)

+ 10 - 1
client/battle/CBattleAnimations.h

@@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_END
 
 class CBattleInterface;
 class CCreatureAnimation;
+class CBattleAnimation;
 struct CatapultProjectileInfo;
 struct StackAttackedInfo;
 
@@ -28,6 +29,13 @@ class CBattleAnimation
 {
 protected:
 	CBattleInterface * owner;
+
+	std::list<std::pair<CBattleAnimation *, bool>> & pendingAnimations();
+	std::shared_ptr<CCreatureAnimation> stackAnimation(const CStack * stack);
+	bool stackFacingRight(const CStack * stack);
+	void setStackFacingRight(const CStack * stack, bool facingRight);
+	ui32 maxAnimationID();
+
 public:
 	virtual bool init() = 0; //to be called - if returned false, call again until returns true
 	virtual void nextFrame() {} //call every new frame
@@ -50,6 +58,7 @@ public:
 	CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack);
 
 	void shiftColor(const ColorShifter * shifter);
+	void rotateStack(BattleHex hex);
 };
 
 /// This class is responsible for managing the battle attack animation
@@ -177,7 +186,7 @@ public:
 	bool priority; //true - high, false - low
 	bool init() override;
 
-	static void rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex);
+
 
 	void setupSecondPart();
 	void endAnim() override;

+ 5 - 4
client/battle/CBattleControlPanel.cpp

@@ -11,6 +11,7 @@
 #include "CBattleControlPanel.h"
 #include "CBattleInterface.h"
 #include "CBattleInterfaceClasses.h"
+#include "CBattleStacksController.h"
 #include "../widgets/Buttons.h"
 #include "../CGameInfo.h"
 #include "../CBitmapHandler.h"
@@ -224,7 +225,7 @@ void CBattleControlPanel::bWaitf()
 	if (owner->spellDestSelectMode) //we are casting a spell
 		return;
 
-	if (owner->activeStack != nullptr)
+	if (owner->stacksController->getActiveStack() != nullptr)
 		owner->giveCommand(EActionType::WAIT);
 }
 
@@ -233,7 +234,7 @@ void CBattleControlPanel::bDefencef()
 	if (owner->spellDestSelectMode) //we are casting a spell
 		return;
 
-	if (owner->activeStack != nullptr)
+	if (owner->stacksController->getActiveStack() != nullptr)
 		owner->giveCommand(EActionType::DEFEND);
 }
 
@@ -276,7 +277,7 @@ void CBattleControlPanel::blockUI(bool on)
 		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
 	}
 
-	bool canWait = owner->activeStack ? !owner->activeStack->waitedThisTurn : false;
+	bool canWait = owner->stacksController->getActiveStack() ? !owner->stacksController->getActiveStack()->waitedThisTurn : false;
 
 	bOptions->block(on);
 	bFlee->block(on || !owner->curInt->cb->battleCanFlee());
@@ -284,7 +285,7 @@ void CBattleControlPanel::blockUI(bool on)
 
 	// block only if during enemy turn and auto-fight is off
 	// otherwise - crash on accessing non-exisiting active stack
-	bAutofight->block(!owner->curInt->isAutoFightOn && !owner->activeStack);
+	bAutofight->block(!owner->curInt->isAutoFightOn && !owner->stacksController->getActiveStack());
 
 	if (owner->tacticsMode && btactEnd && btactNext)
 	{

+ 24 - 22
client/battle/CBattleFieldController.cpp

@@ -12,6 +12,7 @@
 #include "CBattleInterface.h"
 #include "CBattleInterfaceClasses.h"
 #include "CBattleSiegeController.h"
+#include "CBattleStacksController.h"
 #include "CBattleObstacleController.h"
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -125,8 +126,9 @@ void CBattleFieldController::showBackgroundImageWithHexes(SDL_Surface *to)
 	blitAt(backgroundWithHexes, owner->pos.x, owner->pos.y, to);
 }
 
-void CBattleFieldController::redrawBackgroundWithHexes(const CStack *activeStack)
+void CBattleFieldController::redrawBackgroundWithHexes()
 {
+	const CStack *activeStack = owner->stacksController->getActiveStack();
 	attackableHexes.clear();
 	if (activeStack)
 		occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
@@ -173,16 +175,16 @@ void CBattleFieldController::showHighlightedHex(SDL_Surface *to, BattleHex hex,
 void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
 {
 	bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on
-	if(owner->activeStack && settings["battle"]["stackRange"].Bool())
+	if(owner->stacksController->getActiveStack() && settings["battle"]["stackRange"].Bool())
 	{
-		std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->activeStack, currentlyHoveredHex, attackingHex);
+		std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->stacksController->getActiveStack(), currentlyHoveredHex, attackingHex);
 		for(BattleHex hex : set)
 			if(hex != currentlyHoveredHex)
 				showHighlightedHex(to, hex, false);
 
 		// display the movement shadow of the stack at b (i.e. stack under mouse)
 		const CStack * const shere = owner->curInt->cb->battleGetStackByPos(currentlyHoveredHex, false);
-		if(shere && shere != owner->activeStack && shere->alive())
+		if(shere && shere != owner->stacksController->getActiveStack() && shere->alive())
 		{
 			std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
 			for(BattleHex hex : v)
@@ -223,10 +225,10 @@ void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
 					spell = SpellID(owner->spellToCast->actionSubtype).toSpell();
 					caster = owner->getActiveHero();
 				}
-				else if(owner->creatureSpellToCast >= 0 && owner->stackCanCastSpell && owner->creatureCasting)//stack casts spell
+				else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE && owner->creatureCasting)//stack casts spell
 				{
-					spell = SpellID(owner->creatureSpellToCast).toSpell();
-					caster = owner->activeStack;
+					spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
+					caster = owner->stacksController->getActiveStack();
 					mode = spells::Mode::CREATURE_ACTIVE;
 				}
 
@@ -297,7 +299,7 @@ void CBattleFieldController::setBattleCursor(BattleHex myNumber)
 	sectorCursor.push_back(12);
 	sectorCursor.push_back(7);
 
-	const bool doubleWide = owner->activeStack->doubleWide();
+	const bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
 	bool aboveAttackable = true, belowAttackable = true;
 
 	// Exclude directions which cannot be attacked from.
@@ -458,12 +460,12 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 	{
 	case 12: //from bottom right
 		{
-			bool doubleWide = owner->activeStack->doubleWide();
+			bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
-				(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
+				(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
 			if(vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if (vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;
@@ -480,7 +482,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
 			if (vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if(vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;
@@ -494,9 +496,9 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 		}
 	case 8: //from left
 		{
-			if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::DEFENDER)
+			if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::DEFENDER)
 			{
-				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack);
+				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
 				if (vstd::contains(acc, myNumber))
 					return myNumber - 1;
 				else
@@ -513,7 +515,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
 			if(vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if(vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;
@@ -527,12 +529,12 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 		}
 	case 10: //from top right
 		{
-			bool doubleWide = owner->activeStack->doubleWide();
+			bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
 			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
-				(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
+				(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
 			if(vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if(vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;
@@ -546,9 +548,9 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 		}
 	case 11: //from right
 		{
-			if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::ATTACKER)
+			if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
-				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack);
+				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
 				if(vstd::contains(acc, myNumber))
 					return myNumber + 1;
 				else
@@ -565,7 +567,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
 			if(vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if(vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;
@@ -582,7 +584,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
 			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
 			if (vstd::contains(occupyableHexes, destHex))
 				return destHex;
-			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
 			{
 				if(vstd::contains(occupyableHexes, destHex+1))
 					return destHex+1;

+ 1 - 1
client/battle/CBattleFieldController.h

@@ -47,7 +47,7 @@ public:
 	void showBackgroundImage(SDL_Surface *to);
 	void showBackgroundImageWithHexes(SDL_Surface *to);
 
-	void redrawBackgroundWithHexes(const CStack *activeStack);
+	void redrawBackgroundWithHexes();
 
 	void showHighlightedHexes(SDL_Surface *to);
 	void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder);

File diff suppressed because it is too large
+ 123 - 364
client/battle/CBattleInterface.cpp


+ 18 - 38
client/battle/CBattleInterface.h

@@ -63,6 +63,7 @@ class CBattleSiegeController;
 class CBattleObstacleController;
 class CBattleFieldController;
 class CBattleControlPanel;
+class CBattleStacksController;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -119,33 +120,25 @@ enum class MouseHoveredHexContext
 class CBattleInterface : public WindowBase
 {
 private:
-	SDL_Surface *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral;
-
 	std::shared_ptr<CBattleHero> attackingHero;
 	std::shared_ptr<CBattleHero> defendingHero;
 	std::shared_ptr<CStackQueue> queue;
 	std::shared_ptr<CBattleControlPanel> controlPanel;
 
+	std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
+	std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
+	std::shared_ptr<CPlayerInterface> curInt; //current player interface
+
 	const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
 	const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
-	std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
 
-	std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
 	ui8 animCount;
-	const CStack *activeStack; //number of active stack; nullptr - no one
-	const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
-	const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
-	const CStack *selectedStack; //for Teleport / Sacrifice
-	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 
-	std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
 	bool tacticsMode;
-	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
 	std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
 	const CSpell *sp; //spell pointer for convenience
-	si32 creatureSpellToCast;
 	std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player
 	std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex
 	std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target
@@ -155,9 +148,10 @@ private:
 	bool battleActionsStarted; //used for delaying battle actions until intro sound stops
 	int battleIntroSoundChannel; //required as variable for disabling it via ESC key
 
-	void setActiveStack(const CStack *stack);
-	void setHoveredStack(const CStack *stack);
+	std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
 
+	void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
+	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 	void requestAutofightingAIToTakeAction();
 
 	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
@@ -170,26 +164,15 @@ private:
 	void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
 	void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
 
-	std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
-
-	std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
-	std::shared_ptr<CPlayerInterface> curInt; //current player interface
 	const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
 
-	/** Methods for displaying battle screen */
 	void showInterface(SDL_Surface *to);
 
 	void showBattlefieldObjects(SDL_Surface *to);
 
-	void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
-	void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
-
 	void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
 
 	BattleObjectsByHex sortObjectsByHex();
-	void updateBattleAnimations();
-
-	/** End of battle screen blitting methods */
 
 	void setHeroAnimation(ui8 side, int phase);
 public:
@@ -197,14 +180,17 @@ public:
 	std::unique_ptr<CBattleSiegeController> siegeController;
 	std::unique_ptr<CBattleObstacleController> obstacleController;
 	std::unique_ptr<CBattleFieldController> fieldController;
+	std::unique_ptr<CBattleStacksController> stacksController;
 
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
 
-	std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
-	void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
-	ui32 animIDhelper; //for giving IDs for animations
+	bool myTurn; //if true, interface is active (commands can be ordered)
 
+	bool moveStarted; //if true, the creature that is already moving is going to make its first step
+	int moveSoundHander; // sound handler used when moving a unit
+
+	const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
 
 	CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
 	virtual ~CBattleInterface();
@@ -216,17 +202,10 @@ public:
 	void setAnimSpeed(int set); //speed of animation; range 1..100
 	int getAnimSpeed() const; //speed of animation; range 1..100
 	CPlayerInterface *getCurrentPlayerInterface() const;
-	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
-
-	bool myTurn; //if true, interface is active (commands can be ordered)
-
-	bool moveStarted; //if true, the creature that is already moving is going to make its first step
-	int moveSoundHander; // sound handler used when moving a unit
-
-	const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
 
 	void tacticNextStack(const CStack *current);
 	void tacticPhaseEnd();
+	void waitForAnims();
 
 	//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
 	void activate() override;
@@ -240,11 +219,11 @@ public:
 
 	//call-ins
 	void startAction(const BattleAction* action);
-	void unitAdded(const CStack * stack); //new stack appeared on battlefield
+	void stackReset(const CStack * stack);
+	void stackAdded(const CStack * stack); //new stack appeared on battlefield
 	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
 	void stackActivated(const CStack *stack); //active stack has been changed
 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
-	void waitForAnims();
 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
 	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
 	void newRoundFirst( int round );
@@ -306,4 +285,5 @@ public:
 	friend class CBattleObstacleController;
 	friend class CBattleFieldController;
 	friend class CBattleControlPanel;
+	friend class CBattleStacksController;
 };

+ 4 - 3
client/battle/CBattleInterfaceClasses.cpp

@@ -13,6 +13,7 @@
 #include "CBattleInterface.h"
 #include "CBattleSiegeController.h"
 #include "CBattleFieldController.h"
+#include "CBattleStacksController.h"
 #include "CBattleControlPanel.h"
 
 #include "../CBitmapHandler.h"
@@ -591,7 +592,7 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 
 	if (stack)
 	{
-		if(cbi->creDir[stack->ID])
+		if(cbi->stacksController->facingRight(stack))
 			ret.x += imageShiftX;
 		else
 			ret.x -= imageShiftX;
@@ -601,12 +602,12 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 		{
 			if(stack->side == BattleSide::ATTACKER)
 			{
-				if(cbi->creDir[stack->ID])
+				if(cbi->stacksController->facingRight(stack))
 					ret.x -= 44;
 			}
 			else
 			{
-				if(!cbi->creDir[stack->ID])
+				if(!cbi->stacksController->facingRight(stack))
 					ret.x += 44;
 			}
 		}

+ 2 - 1
client/battle/CBattleObstacleController.cpp

@@ -11,6 +11,7 @@
 #include "CBattleObstacleController.h"
 #include "CBattleInterface.h"
 #include "CBattleFieldController.h"
+#include "CBattleStacksController.h"
 #include "../CPlayerInterface.h"
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
@@ -100,7 +101,7 @@ void CBattleObstacleController::obstaclePlaced(const CObstacleInstance & oi)
 	//we assume here that effect graphics have the same size as the usual obstacle image
 	// -> if we know how to blit obstacle, let's blit the effect in the same place
 	Point whereTo = getObstaclePosition(first, oi);
-	owner->addNewAnim(new CEffectAnimation(owner, animation, whereTo.x, whereTo.y));
+	owner->stacksController->addNewAnim(new CEffectAnimation(owner, animation, whereTo.x, whereTo.y));
 
 	//TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad
 	//CCS->soundh->playSound(sound);

+ 14 - 18
client/battle/CBattleProjectileController.cpp

@@ -16,6 +16,7 @@
 #include "../gui/CAnimation.h"
 #include "CBattleInterface.h"
 #include "CBattleSiegeController.h"
+#include "CBattleStacksController.h"
 #include "CCreatureAnimation.h"
 
 CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest)
@@ -79,6 +80,17 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
 	}
 }
 
+void CBattleProjectileController::fireStackProjectile(const CStack * stack)
+{
+	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
+	{
+		if ( !it->shotDone && it->stackID == stack->ID)
+		{
+			it->shotDone = true;
+			return;
+		}
+	}
+}
 
 void CBattleProjectileController::showProjectiles(SDL_Surface *to)
 {
@@ -89,18 +101,7 @@ void CBattleProjectileController::showProjectiles(SDL_Surface *to)
 	{
 		// Check if projectile is already visible (shooter animation did the shot)
 		if (!it->shotDone)
-		{
-			// frame we're waiting for is reached OR animation has already finished
-			if (owner->creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay ||
-				owner->creAnims[it->stackID]->isShooting() == false)
-			{
-				//at this point projectile should become visible
-				owner->creAnims[it->stackID]->pause(); // pause animation
-				it->shotDone = true;
-			}
-			else
-				continue; // wait...
-		}
+			continue;
 
 		if (idToProjectile.count(it->creID))
 		{
@@ -183,11 +184,7 @@ void CBattleProjectileController::showProjectiles(SDL_Surface *to)
 	}
 
 	for (auto & elem : toBeDeleted)
-	{
-		// resume animation
-		owner->creAnims[elem->stackID]->play();
 		projectiles.erase(elem);
-	}
 }
 
 bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
@@ -231,7 +228,7 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
 	spi.creID = shooter->getCreature()->idNumber;
 	spi.stackID = shooter->ID;
 	// reverse if creature is facing right OR this is non-existing stack that is not tower (war machines)
-	spi.reverse = shooter ? !owner->creDir[shooter->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS;
+	spi.reverse = shooter ? !owner->stacksController->facingRight(shooter) : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS;
 
 	spi.step = 0;
 	spi.frameNum = 0;
@@ -314,6 +311,5 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
 	}
 
 	// Set projectile animation start delay which is specified in frames
-	spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;
 	projectiles.push_back(spi);
 }

+ 1 - 1
client/battle/CBattleProjectileController.h

@@ -38,7 +38,6 @@ struct ProjectileInfo
 	int stackID; //ID of stack
 	int frameNum; //frame to display form projectile animation
 	//bool spin; //if true, frameNum will be increased
-	int animStartDelay; //frame of shooter animation when projectile should appear
 	bool shotDone; // actual shot already done, projectile is flying
 	bool reverse; //if true, projectile will be flipped by vertical asix
 	std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
@@ -58,6 +57,7 @@ public:
 
 	void showProjectiles(SDL_Surface *to);
 	void initStackProjectile(const CStack * stack);
+	void fireStackProjectile(const CStack * stack);
 
 	bool hasActiveProjectile(const CStack * stack);
 

+ 4 - 3
client/battle/CBattleSiegeController.cpp

@@ -21,6 +21,7 @@
 #include "CBattleInterface.h"
 #include "CBattleAnimations.h"
 #include "CBattleInterfaceClasses.h"
+#include "CBattleStacksController.h"
 
 CBattleSiegeController::~CBattleSiegeController()
 {
@@ -315,7 +316,7 @@ void CBattleSiegeController::showPiecesOfWall(SDL_Surface *to, std::vector<int>
 			if (turret)
 			{
 				std::vector<const CStack *> stackList(1, turret);
-				owner->showStacks(to, stackList);
+				owner->stacksController->showStacks(to, stackList);
 				printPartOfWall(to, piece);
 			}
 		}
@@ -343,7 +344,7 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 		const CStack *stack = owner->curInt->cb->battleGetStackByID(ca.attacker);
 		for (auto attackInfo : ca.attackedParts)
 		{
-			owner->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
+			owner->stacksController->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
 		}
 	}
 	else
@@ -353,7 +354,7 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 		{
 			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, owner) + Point(99, 120);
 
-			owner->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y));
+			owner->stacksController->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y));
 		}
 	}
 

+ 601 - 0
client/battle/CBattleStacksController.cpp

@@ -0,0 +1,601 @@
+/*
+ * CBattleStacksController.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CBattleStacksController.h"
+#include "CBattleSiegeController.h"
+#include "CBattleInterfaceClasses.h"
+#include "CBattleInterface.h"
+#include "CBattleFieldController.h"
+#include "CBattleProjectileController.h"
+#include "CBattleControlPanel.h"
+#include "../CBitmapHandler.h"
+#include "../gui/SDL_Extensions.h"
+#include "../gui/CGuiHandler.h"
+#include "../../lib/battle/BattleHex.h"
+#include "../CPlayerInterface.h"
+#include "CCreatureAnimation.h"
+#include "../../lib/CGameState.h"
+#include "../../CCallback.h"
+#include "../../lib/CStack.h"
+#include "../../lib/CondSh.h"
+#include "../CMusicHandler.h"
+#include "../CGameInfo.h"
+
+static void onAnimationFinished(const CStack *stack, std::weak_ptr<CCreatureAnimation> anim)
+{
+	std::shared_ptr<CCreatureAnimation> animation = anim.lock();
+	if(!animation)
+		return;
+
+	if (animation->isIdle())
+	{
+		const CCreature *creature = stack->getCreature();
+
+		if (animation->framesInGroup(CCreatureAnim::MOUSEON) > 0)
+		{
+			if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
+				animation->playOnce(CCreatureAnim::MOUSEON);
+			else
+				animation->setType(CCreatureAnim::HOLDING);
+		}
+		else
+		{
+			animation->setType(CCreatureAnim::HOLDING);
+		}
+	}
+	// always reset callback
+	animation->onAnimationReset += std::bind(&onAnimationFinished, stack, anim);
+}
+
+static void transformPalette(SDL_Surface *surf, double rCor, double gCor, double bCor)
+{
+	SDL_Color *colorsToChange = surf->format->palette->colors;
+	for (int g=0; g<surf->format->palette->ncolors; ++g)
+	{
+		SDL_Color *color = &colorsToChange[g];
+		if (color->b != 132 &&
+			color->g != 231 &&
+			color->r != 255) //it's not yellow border
+		{
+			color->r = static_cast<Uint8>(color->r * rCor);
+			color->g = static_cast<Uint8>(color->g * gCor);
+			color->b = static_cast<Uint8>(color->b * bCor);
+		}
+	}
+}
+
+CBattleStacksController::CBattleStacksController(CBattleInterface * owner):
+	owner(owner)
+{
+	//preparing graphics for displaying amounts of creatures
+	amountNormal = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
+	CSDL_Ext::alphaTransform(amountNormal);
+	transformPalette(amountNormal, 0.59, 0.19, 0.93);
+
+	amountPositive = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
+	CSDL_Ext::alphaTransform(amountPositive);
+	transformPalette(amountPositive, 0.18, 1.00, 0.18);
+
+	amountNegative = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
+	CSDL_Ext::alphaTransform(amountNegative);
+	transformPalette(amountNegative, 1.00, 0.18, 0.18);
+
+	amountEffNeutral = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
+	CSDL_Ext::alphaTransform(amountEffNeutral);
+	transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
+
+	std::vector<const CStack*> stacks = owner->curInt->cb->battleGetAllStacks(true);
+	for(const CStack * s : stacks)
+	{
+		stackAdded(s);
+	}
+}
+
+CBattleStacksController::~CBattleStacksController()
+{
+	SDL_FreeSurface(amountNormal);
+	SDL_FreeSurface(amountNegative);
+	SDL_FreeSurface(amountPositive);
+	SDL_FreeSurface(amountEffNeutral);
+}
+
+void CBattleStacksController::sortObjectsByHex(BattleObjectsByHex & sorted)
+{
+	auto getCurrentPosition = [&](const CStack *stack) -> BattleHex
+	{
+		for (auto & anim : pendingAnims)
+		{
+			// certainly ugly workaround but fixes quite annoying bug
+			// stack position will be updated only *after* movement is finished
+			// before this - stack is always at its initial position. Thus we need to find
+			// its current position. Which can be found only in this class
+			if (CMovementAnimation *move = dynamic_cast<CMovementAnimation*>(anim.first))
+			{
+				if (move->stack == stack)
+					return move->nextHex;
+			}
+		}
+		return stack->getPosition();
+	};
+
+	auto stacks = owner->curInt->cb->battleGetStacksIf([](const CStack *s)
+	{
+		return !s->isTurret();
+	});
+
+	for (auto & stack : stacks)
+	{
+		if (creAnims.find(stack->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
+			continue;
+
+		if (stack->initialPosition < 0) // turret shooters are handled separately
+			continue;
+
+		//FIXME: hack to ignore ghost stacks
+		if ((creAnims[stack->ID]->getType() == CCreatureAnim::DEAD || creAnims[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
+			continue;//ignore
+
+		if (creAnims[stack->ID]->isDead())
+		{
+			sorted.hex[stack->getPosition()].dead.push_back(stack);
+			continue;
+		}
+
+		if (!creAnims[stack->ID]->isMoving())
+		{
+			sorted.hex[stack->getPosition()].alive.push_back(stack);
+			continue;
+		}
+
+		// flying creature - just blit them over everyone else
+		if (stack->hasBonusOfType(Bonus::FLYING))
+		{
+			sorted.afterAll.alive.push_back(stack);
+			continue;
+		}
+
+		sorted.hex[getCurrentPosition(stack)].alive.push_back(stack);
+	}
+}
+
+void CBattleStacksController::stackReset(const CStack * stack)
+{
+	auto iter = creAnims.find(stack->ID);
+
+	if(iter == creAnims.end())
+	{
+		logGlobal->error("Unit %d have no animation", stack->ID);
+		return;
+	}
+
+	auto animation = iter->second;
+
+	if(stack->alive() && animation->isDead())
+		animation->setType(CCreatureAnim::HOLDING);
+
+	if (stack->isClone())
+	{
+		ColorShifterDeepBlue shifter;
+		animation->shiftColor(&shifter);
+	}
+
+	//TODO: handle more cases
+}
+
+void CBattleStacksController::stackAdded(const CStack * stack)
+{
+	creDir[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
+
+	Point coords = CClickableHex::getXYUnitAnim(stack->getPosition(), stack, owner);
+
+	if(stack->initialPosition < 0) //turret
+	{
+		assert(owner->siegeController);
+
+		const CCreature *turretCreature = owner->siegeController->turretCreature();
+
+		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
+		creAnims[stack->ID]->pos.h = 225;
+
+		coords = owner->siegeController->turretCreaturePosition(stack->initialPosition);
+	}
+	else
+	{
+		creAnims[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
+		creAnims[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, creAnims[stack->ID]);
+		creAnims[stack->ID]->pos.h = creAnims[stack->ID]->getHeight();
+	}
+	creAnims[stack->ID]->pos.x = coords.x;
+	creAnims[stack->ID]->pos.y = coords.y;
+	creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth();
+	creAnims[stack->ID]->setType(CCreatureAnim::HOLDING);
+
+	//loading projectiles for units
+	if(stack->isShooter())
+	{
+		owner->projectilesController->initStackProjectile(stack);
+	}
+}
+
+void CBattleStacksController::setActiveStack(const CStack *stack)
+{
+	if (activeStack) // update UI
+		creAnims[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
+
+	activeStack = stack;
+
+	if (activeStack) // update UI
+		creAnims[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
+
+	owner->controlPanel->blockUI(activeStack == nullptr);
+}
+
+void CBattleStacksController::setHoveredStack(const CStack *stack)
+{
+	if ( stack == mouseHoveredStack )
+		 return;
+
+	if (mouseHoveredStack)
+		creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder());
+
+	// stack must be alive and not active (which uses gold border instead)
+	if (stack && stack->alive() && stack != activeStack)
+	{
+		mouseHoveredStack = stack;
+
+		if (mouseHoveredStack)
+		{
+			creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
+			if (creAnims[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
+				creAnims[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON);
+		}
+	}
+	else
+		mouseHoveredStack = nullptr;
+}
+
+void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
+{
+	BattleHex currentActionTarget;
+	if(owner->curInt->curAction)
+	{
+		auto target = owner->curInt->curAction->getTarget(owner->curInt->cb.get());
+		if(!target.empty())
+			currentActionTarget = target.at(0).hexValue;
+	}
+
+	auto isAmountBoxVisible = [&](const CStack *stack) -> bool
+	{
+		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
+			return false;
+
+		if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
+			return false;
+
+		for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
+		{
+			auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim.first);
+			if(hitAnimation && (hitAnimation->stack->ID == stack->ID)) //we process only "current creature" as other creatures will be processed reliably on their own iteration
+				return false;
+		}
+
+		if(owner->curInt->curAction)
+		{
+			if(owner->curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
+			{
+				if(owner->curInt->curAction->actionType == EActionType::WALK || owner->curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
+					return false;
+
+				else if(owner->curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
+					return false;
+			}
+
+			if(owner->curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
+				return false;
+		}
+
+		return true;
+	};
+
+	auto getEffectsPositivness = [&](const std::vector<si32> & activeSpells) -> int
+	{
+		int pos = 0;
+		for (const auto & spellId : activeSpells)
+		{
+			pos += CGI->spellh->objects.at(spellId)->positiveness;
+		}
+		return pos;
+	};
+
+	auto getAmountBoxBackground = [&](int positivness) -> SDL_Surface *
+	{
+		if (positivness > 0)
+			return amountPositive;
+		if (positivness < 0)
+			return amountNegative;
+		return amountEffNeutral;
+	};
+
+	showStacks(to, stacks); // Actual display of all stacks
+
+	for (auto & stack : stacks)
+	{
+		assert(stack);
+		//printing amount
+		if (isAmountBoxVisible(stack))
+		{
+			const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
+			const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
+			const BattleHex nextPos = stack->getPosition() + sideShift;
+			const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
+			const bool moveInside = !edge && !owner->fieldController->stackCountOutsideHex(nextPos);
+			int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
+					   (stack->doubleWide() ? 44 : 0) * sideShift +
+					   (moveInside ? amountNormal->w + 10 : 0) * reverseSideShift;
+			int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
+
+			//blitting amount background box
+			SDL_Surface *amountBG = amountNormal;
+			std::vector<si32> activeSpells = stack->activeSpells();
+			if (!activeSpells.empty())
+				amountBG = getAmountBoxBackground(getEffectsPositivness(activeSpells));
+
+			SDL_Rect temp_rect = genRect(amountBG->h, amountBG->w, creAnims[stack->ID]->pos.x + xAdd, creAnims[stack->ID]->pos.y + yAdd);
+			SDL_BlitSurface(amountBG, nullptr, to, &temp_rect);
+
+			//blitting amount
+			Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->w/2,
+						  creAnims[stack->ID]->pos.y + yAdd + amountNormal->h/2);
+			graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->getCount()), Colors::WHITE, textPos);
+		}
+	}
+}
+
+void CBattleStacksController::showStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
+{
+	for (const CStack *stack : stacks)
+	{
+		creAnims[stack->ID]->nextFrame(to, facingRight(stack)); // do actual blit
+		creAnims[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
+	}
+}
+
+void CBattleStacksController::updateBattleAnimations()
+{
+	//handle animations
+	for (auto & elem : pendingAnims)
+	{
+		if (!elem.first) //this animation should be deleted
+			continue;
+
+		if (!elem.second)
+		{
+			elem.second = elem.first->init();
+		}
+		if (elem.second && elem.first)
+			elem.first->nextFrame();
+	}
+
+	//delete anims
+	int preSize = static_cast<int>(pendingAnims.size());
+	for (auto it = pendingAnims.begin(); it != pendingAnims.end(); ++it)
+	{
+		if (it->first == nullptr)
+		{
+			pendingAnims.erase(it);
+			it = pendingAnims.begin();
+			break;
+		}
+	}
+
+	if (preSize > 0 && pendingAnims.empty())
+	{
+		//anims ended
+		owner->controlPanel->blockUI(activeStack == nullptr);
+
+		owner->animsAreDisplayed.setn(false);
+	}
+}
+
+void CBattleStacksController::addNewAnim(CBattleAnimation *anim)
+{
+	pendingAnims.push_back( std::make_pair(anim, false) );
+	owner->animsAreDisplayed.setn(true);
+}
+
+void CBattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
+{
+	stackToActivate = stack;
+	owner->waitForAnims();
+	if (stackToActivate) //during waiting stack may have gotten activated through show
+		activateStack();
+}
+
+void CBattleStacksController::stackRemoved(uint32_t stackID)
+{
+	if (getActiveStack() != nullptr)
+	{
+		if (getActiveStack()->ID == stackID)
+		{
+			BattleAction *action = new BattleAction();
+			action->side = owner->defendingHeroInstance ? (owner->curInt->playerID == owner->defendingHeroInstance->tempOwner) : false;
+			action->actionType = EActionType::CANCEL;
+			action->stackNumber = owner->stacksController->getActiveStack()->ID;
+			owner->givenCommand.setn(action);
+			setActiveStack(nullptr);
+		}
+	}
+	//todo: ensure that ghost stack animation has fadeout effect
+}
+
+void CBattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
+{
+	for(auto & attackedInfo : attackedInfos)
+	{
+		//if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
+			addNewAnim(new CDefenceAnimation(attackedInfo, owner));
+
+		if(attackedInfo.rebirth)
+		{
+			owner->displayEffect(50, attackedInfo.defender->getPosition()); //TODO: play reverse death animation
+			CCS->soundh->playSound(soundBase::RESURECT);
+		}
+	}
+	owner->waitForAnims();
+
+	for (auto & attackedInfo : attackedInfos)
+	{
+		if (attackedInfo.rebirth)
+			creAnims[attackedInfo.defender->ID]->setType(CCreatureAnim::HOLDING);
+		if (attackedInfo.cloneKilled)
+			stackRemoved(attackedInfo.defender->ID);
+	}
+}
+
+void CBattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
+{
+	addNewAnim(new CMovementAnimation(owner, stack, destHex, distance));
+	owner->waitForAnims();
+}
+
+void CBattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
+{
+	if (shooting)
+	{
+		addNewAnim(new CShootingAnimation(owner, attacker, dest, attacked));
+	}
+	else
+	{
+		addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, attacked));
+	}
+	//waitForAnims();
+}
+
+bool CBattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex)
+{
+	Point begPosition = CClickableHex::getXYUnitAnim(oldPos,stack, owner);
+	Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
+
+	if((begPosition.x > endPosition.x) && facingRight(stack))
+		return true;
+	else if((begPosition.x < endPosition.x) && !facingRight(stack))
+		return true;
+
+	return false;
+}
+
+
+void CBattleStacksController::endAction(const BattleAction* action)
+{
+	//check if we should reverse stacks
+	//for some strange reason, it's not enough
+	TStacks stacks = owner->curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
+
+	for (const CStack *s : stacks)
+	{
+		bool shouldFaceRight  = s && s->side == BattleSide::ATTACKER;
+
+		if (s && facingRight(s) != shouldFaceRight && s->alive() && creAnims[s->ID]->isIdle())
+		{
+			addNewAnim(new CReverseAnimation(owner, s, s->getPosition(), false));
+		}
+	}
+}
+
+void CBattleStacksController::startAction(const BattleAction* action)
+{
+	const CStack *stack = owner->curInt->cb->battleGetStackByID(action->stackNumber);
+	setHoveredStack(nullptr);
+
+	auto actionTarget = action->getTarget(owner->curInt->cb.get());
+
+	if(action->actionType == EActionType::WALK
+		|| (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition()))
+	{
+		assert(stack);
+		owner->moveStarted = true;
+		if (creAnims[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
+		{
+			pendingAnims.push_back(std::make_pair(new CMovementStartAnimation(owner, stack), false));
+		}
+
+		if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue))
+			pendingAnims.push_back(std::make_pair(new CReverseAnimation(owner, stack, stack->getPosition(), true), false));
+	}
+}
+
+void CBattleStacksController::activateStack()
+{
+	if ( !pendingAnims.empty())
+		return;
+
+	if ( !stackToActivate)
+		return;
+
+	owner->trySetActivePlayer(stackToActivate->owner);
+
+	setActiveStack(stackToActivate);
+	stackToActivate = nullptr;
+
+	const CStack * s = owner->stacksController->getActiveStack();
+	if(!s)
+		return;
+
+	//set casting flag to true if creature can use it to not check it every time
+	const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
+	const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
+	if(s->canCast() && (spellcaster || randomSpellcaster))
+	{
+		stackCanCastSpell = true;
+		if(randomSpellcaster)
+			creatureSpellToCast = -1; //spell will be set later on cast
+		else
+			creatureSpellToCast = owner->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
+	{
+		stackCanCastSpell = false;
+		creatureSpellToCast = -1;
+	}
+}
+
+void CBattleStacksController::setSelectedStack(const CStack *stack)
+{
+	selectedStack = stack;
+}
+
+const CStack* CBattleStacksController::getSelectedStack()
+{
+	return selectedStack;
+}
+
+const CStack* CBattleStacksController::getActiveStack()
+{
+	return activeStack;
+}
+
+bool CBattleStacksController::facingRight(const CStack * stack)
+{
+	return creDir[stack->ID];
+}
+
+bool CBattleStacksController::activeStackSpellcaster()
+{
+	return stackCanCastSpell;
+}
+
+SpellID CBattleStacksController::activeStackSpellToCast()
+{
+	if (!stackCanCastSpell)
+		return SpellID::NONE;
+	return SpellID(creatureSpellToCast);
+}

+ 85 - 0
client/battle/CBattleStacksController.h

@@ -0,0 +1,85 @@
+/*
+ * CBattleStacksController.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+struct BattleObjectsByHex;
+struct SDL_Surface;
+struct BattleHex;
+struct StackAttackedInfo;
+struct BattleAction;
+
+class SpellID;
+class CBattleInterface;
+class CBattleAnimation;
+class CCreatureAnimation;
+class CStack;
+class CBattleAnimation;
+
+class CBattleStacksController
+{
+	CBattleInterface * owner;
+
+	SDL_Surface *amountNormal;
+	SDL_Surface *amountNegative;
+	SDL_Surface *amountPositive;
+	SDL_Surface *amountEffNeutral;
+
+	std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
+	std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
+	std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
+
+	const CStack *activeStack; //number of active stack; nullptr - no one
+	const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
+	const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
+	const CStack *selectedStack; //for Teleport / Sacrifice
+
+	bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
+	si32 creatureSpellToCast;
+
+	ui32 animIDhelper; //for giving IDs for animations
+public:
+	CBattleStacksController(CBattleInterface * owner);
+	~CBattleStacksController();
+
+	void sortObjectsByHex(BattleObjectsByHex & sorted);
+	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
+	bool facingRight(const CStack * stack);
+
+	void stackReset(const CStack * stack);
+	void stackAdded(const CStack * stack); //new stack appeared on battlefield
+	void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
+	void stackActivated(const CStack *stack); //active stack has been changed
+	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
+	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
+	void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
+
+	void startAction(const BattleAction* action);
+	void endAction(const BattleAction* action);
+
+	bool activeStackSpellcaster();
+	SpellID activeStackSpellToCast();
+
+	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
+
+	void setActiveStack(const CStack *stack);
+	void setHoveredStack(const CStack *stack);
+	void setSelectedStack(const CStack *stack);
+
+	void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
+	void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
+
+	void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
+	void updateBattleAnimations();
+
+	const CStack* getActiveStack();
+	const CStack* getSelectedStack();
+
+	friend class CBattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
+};

Some files were not shown because too many files changed in this diff