Browse Source

Petrify will freeze stack animations

Ivan Savenko 2 years ago
parent
commit
733f21f8dc

+ 95 - 149
client/battle/BattleAnimationClasses.cpp

@@ -93,36 +93,68 @@ BattleStackAnimation::BattleStackAnimation(BattleInterface & owner, const CStack
 	assert(myAnim);
 	assert(myAnim);
 }
 }
 
 
-ECreatureAnimType::Type AttackAnimation::findValidGroup( const std::vector<ECreatureAnimType::Type> candidates ) const
+StackActionAnimation::StackActionAnimation(BattleInterface & owner, const CStack * stack)
+	: BattleStackAnimation(owner, stack)
+	, nextGroup(ECreatureAnimType::HOLDING)
+	, currGroup(ECreatureAnimType::HOLDING)
 {
 {
-	for ( auto group : candidates)
-	{
-		if(myAnim->framesInGroup(group) > 0)
-			return group;
-	}
+}
 
 
-	assert(0);
-	return ECreatureAnimType::HOLDING;
+ECreatureAnimType::Type StackActionAnimation::getGroup() const
+{
+	return currGroup;
+}
+
+void StackActionAnimation::setNextGroup( ECreatureAnimType::Type group )
+{
+	nextGroup = group;
 }
 }
 
 
-void AttackAnimation::nextFrame()
+void StackActionAnimation::setGroup( ECreatureAnimType::Type group )
 {
 {
-	if(myAnim->getType() != group)
+	currGroup = group;
+}
+
+void StackActionAnimation::setSound( std::string sound )
+{
+	this->sound = sound;
+}
+
+bool StackActionAnimation::init()
+{
+	if (!sound.empty())
+		CCS->soundh->playSound(sound);
+
+	if (myAnim->framesInGroup(currGroup) > 0)
 	{
 	{
-		myAnim->setType(group);
+		myAnim->playOnce(currGroup);
 		myAnim->onAnimationReset += [&](){ delete this; };
 		myAnim->onAnimationReset += [&](){ delete this; };
 	}
 	}
+	else
+		delete this;
 
 
-	if(!soundPlayed)
-	{
-		playSound();
-		soundPlayed = true;
-	}
+	return true;
 }
 }
 
 
-AttackAnimation::~AttackAnimation()
+StackActionAnimation::~StackActionAnimation()
 {
 {
-	myAnim->setType(ECreatureAnimType::HOLDING);
+	if (stack->isFrozen())
+		myAnim->setType(ECreatureAnimType::HOLDING);
+	else
+		myAnim->setType(nextGroup);
+
+}
+
+ECreatureAnimType::Type AttackAnimation::findValidGroup( const std::vector<ECreatureAnimType::Type> candidates ) const
+{
+	for ( auto group : candidates)
+	{
+		if(myAnim->framesInGroup(group) > 0)
+			return group;
+	}
+
+	assert(0);
+	return ECreatureAnimType::HOLDING;
 }
 }
 
 
 const CCreature * AttackAnimation::getCreature() const
 const CCreature * AttackAnimation::getCreature() const
@@ -135,9 +167,7 @@ const CCreature * AttackAnimation::getCreature() const
 
 
 
 
 AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender)
 AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender)
-	: BattleStackAnimation(owner, attacker),
-	  group(ECreatureAnimType::SHOOT_FRONT),
-	  soundPlayed(false),
+	: StackActionAnimation(owner, attacker),
 	  dest(_dest),
 	  dest(_dest),
 	  defendingStack(defender),
 	  defendingStack(defender),
 	  attackingStack(attacker)
 	  attackingStack(attacker)
@@ -146,61 +176,34 @@ AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker
 	attackingStackPosBeforeReturn = attackingStack->getPosition();
 	attackingStackPosBeforeReturn = attackingStack->getPosition();
 }
 }
 
 
-bool HittedAnimation::init()
-{
-	CCS->soundh->playSound(battle_sound(stack->getCreature(), wince));
-	myAnim->playOnce(ECreatureAnimType::HITTED);
-	myAnim->onAnimationReset += [&](){ delete this; };
-	return true;
-}
-
 HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
 HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
-	: BattleStackAnimation(owner, stack)
+	: StackActionAnimation(owner, stack)
 {
 {
+	setGroup(ECreatureAnimType::HITTED);
+	setSound(battle_sound(stack->getCreature(), wince));
 }
 }
 
 
 DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack)
 DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack)
-	: BattleStackAnimation(owner, stack)
+	: StackActionAnimation(owner, stack)
 {
 {
+	setGroup(ECreatureAnimType::DEFENCE);
+	setSound(battle_sound(stack->getCreature(), defend));
 }
 }
 
 
-bool DefenceAnimation::init()
+DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
+	StackActionAnimation(owner, stack)
 {
 {
-	CCS->soundh->playSound(battle_sound(stack->getCreature(), defend));
-	myAnim->playOnce(ECreatureAnimType::DEFENCE);
-	myAnim->onAnimationReset += [&](){ delete this; };
+	setSound(battle_sound(stack->getCreature(), killed));
 
 
-	return true; //initialized successfuly
-}
-
-ECreatureAnimType::Type DeathAnimation::getMyAnimType()
-{
-	if(rangedAttack && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
-		return ECreatureAnimType::DEATH_RANGED;
+	if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
+		setGroup(ECreatureAnimType::DEATH_RANGED);
 	else
 	else
-		return ECreatureAnimType::DEATH;
-}
+		setGroup(ECreatureAnimType::DEATH);
 
 
-bool DeathAnimation::init()
-{
-	CCS->soundh->playSound(battle_sound(stack->getCreature(), killed));
-	myAnim->playOnce(getMyAnimType());
-	myAnim->onAnimationReset += [&](){ delete this; };
-	return true;
-}
-
-DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
-	BattleStackAnimation(owner, stack),
-	rangedAttack(ranged)
-{
-}
-
-DeathAnimation::~DeathAnimation()
-{
-	if(rangedAttack && myAnim->framesInGroup(ECreatureAnimType::DEAD_RANGED) > 0)
-		myAnim->setType(ECreatureAnimType::DEAD_RANGED);
+	if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEAD_RANGED) > 0)
+		setNextGroup(ECreatureAnimType::DEAD_RANGED);
 	else
 	else
-		myAnim->setType(ECreatureAnimType::DEAD);
+		setNextGroup(ECreatureAnimType::DEAD);
 }
 }
 
 
 DummyAnimation::DummyAnimation(BattleInterface & owner, int howManyFrames)
 DummyAnimation::DummyAnimation(BattleInterface & owner, int howManyFrames)
@@ -223,7 +226,7 @@ void DummyAnimation::nextFrame()
 		delete this;
 		delete this;
 }
 }
 
 
-ECreatureAnimType::Type MeleeAttackAnimation::getUpwardsGroup() const
+ECreatureAnimType::Type MeleeAttackAnimation::getUpwardsGroup(bool multiAttack) const
 {
 {
 	if (!multiAttack)
 	if (!multiAttack)
 		return ECreatureAnimType::ATTACK_UP;
 		return ECreatureAnimType::ATTACK_UP;
@@ -235,7 +238,7 @@ ECreatureAnimType::Type MeleeAttackAnimation::getUpwardsGroup() const
 	});
 	});
 }
 }
 
 
-ECreatureAnimType::Type MeleeAttackAnimation::getForwardGroup() const
+ECreatureAnimType::Type MeleeAttackAnimation::getForwardGroup(bool multiAttack) const
 {
 {
 	if (!multiAttack)
 	if (!multiAttack)
 		return ECreatureAnimType::ATTACK_FRONT;
 		return ECreatureAnimType::ATTACK_FRONT;
@@ -247,7 +250,7 @@ ECreatureAnimType::Type MeleeAttackAnimation::getForwardGroup() const
 	});
 	});
 }
 }
 
 
-ECreatureAnimType::Type MeleeAttackAnimation::getDownwardsGroup() const
+ECreatureAnimType::Type MeleeAttackAnimation::getDownwardsGroup(bool multiAttack) const
 {
 {
 	if (!multiAttack)
 	if (!multiAttack)
 		return ECreatureAnimType::ATTACK_DOWN;
 		return ECreatureAnimType::ATTACK_DOWN;
@@ -259,27 +262,16 @@ ECreatureAnimType::Type MeleeAttackAnimation::getDownwardsGroup() const
 	});
 	});
 }
 }
 
 
-bool MeleeAttackAnimation::init()
+ECreatureAnimType::Type MeleeAttackAnimation::selectGroup(bool multiAttack)
 {
 {
-	assert(attackingStack);
-	assert(!myAnim->isDeadOrDying());
-
-	if(!attackingStack || myAnim->isDeadOrDying())
-	{
-		delete this;
-		return false;
-	}
-
-	logAnim->info("CMeleeAttackAnimation::init: stack %s -> stack %s", stack->getName(), defendingStack->getName());
-
 	const ECreatureAnimType::Type mutPosToGroup[] =
 	const ECreatureAnimType::Type mutPosToGroup[] =
 	{
 	{
-		getUpwardsGroup(),
-		getUpwardsGroup(),
-		getForwardGroup(),
-		getDownwardsGroup(),
-		getDownwardsGroup(),
-		getForwardGroup()
+		getUpwardsGroup  (multiAttack),
+		getUpwardsGroup  (multiAttack),
+		getForwardGroup  (multiAttack),
+		getDownwardsGroup(multiAttack),
+		getDownwardsGroup(multiAttack),
+		getForwardGroup  (multiAttack)
 	};
 	};
 
 
 	int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
 	int revShiftattacker = (attackingStack->side == BattleSide::ATTACKER ? -1 : 1);
@@ -300,14 +292,13 @@ bool MeleeAttackAnimation::init()
 
 
 	assert(mutPos >= 0 && mutPos <=5);
 	assert(mutPos >= 0 && mutPos <=5);
 
 
-	group = mutPosToGroup[mutPos];
-	return true;
+	return mutPosToGroup[mutPos];
 }
 }
 
 
 void MeleeAttackAnimation::nextFrame()
 void MeleeAttackAnimation::nextFrame()
 {
 {
 	size_t currentFrame = stackAnimation(attackingStack)->getCurrentFrame();
 	size_t currentFrame = stackAnimation(attackingStack)->getCurrentFrame();
-	size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(group);
+	size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(getGroup());
 
 
 	if ( currentFrame * 2 >= totalFrames )
 	if ( currentFrame * 2 >= totalFrames )
 	{
 	{
@@ -317,23 +308,18 @@ void MeleeAttackAnimation::nextFrame()
 	AttackAnimation::nextFrame();
 	AttackAnimation::nextFrame();
 }
 }
 
 
-void MeleeAttackAnimation::playSound()
-{
-	CCS->soundh->playSound(battle_sound(getCreature(), attack));
-}
-
 MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack)
 MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack)
-	: AttackAnimation(owner, attacker, _dest, _attacked),
-	  multiAttack(multiAttack)
+	: AttackAnimation(owner, attacker, _dest, _attacked)
 {
 {
 	logAnim->debug("Created melee attack anim for %s", attacker->getName());
 	logAnim->debug("Created melee attack anim for %s", attacker->getName());
+	setSound(battle_sound(getCreature(), attack));
+	setGroup(selectGroup(multiAttack));
 }
 }
 
 
 StackMoveAnimation::StackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex):
 StackMoveAnimation::StackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex):
 	BattleStackAnimation(owner, _stack),
 	BattleStackAnimation(owner, _stack),
 	currentHex(_currentHex)
 	currentHex(_currentHex)
 {
 {
-
 }
 }
 
 
 bool MovementAnimation::init()
 bool MovementAnimation::init()
@@ -585,32 +571,10 @@ void ReverseAnimation::setupSecondPart()
 		delete this;
 		delete this;
 }
 }
 
 
-bool ResurrectionAnimation::init()
-{
-	assert(stack);
-
-	if(!stack)
-	{
-		delete this;
-		return false;
-	}
-
-	logAnim->info("CResurrectionAnimation::init: stack %s", stack->getName());
-	myAnim->playOnce(ECreatureAnimType::RESURRECTION);
-	myAnim->onAnimationReset += [&](){ delete this; };
-
-	return true;
-}
-
 ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CStack * _stack):
 ResurrectionAnimation::ResurrectionAnimation(BattleInterface & owner, const CStack * _stack):
-	BattleStackAnimation(owner, _stack)
+	StackActionAnimation(owner, _stack)
 {
 {
-
-}
-
-bool ColorTransformAnimation::init()
-{
-	return true;
+	setGroup(ECreatureAnimType::RESURRECTION);
 }
 }
 
 
 void ColorTransformAnimation::nextFrame()
 void ColorTransformAnimation::nextFrame()
@@ -651,7 +615,7 @@ void ColorTransformAnimation::nextFrame()
 }
 }
 
 
 ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell):
 ColorTransformAnimation::ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell):
-	BattleStackAnimation(owner, _stack),
+	StackActionAnimation(owner, _stack),
 	spell(spell),
 	spell(spell),
 	totalProgress(0.f)
 	totalProgress(0.f)
 {
 {
@@ -716,31 +680,15 @@ RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CSt
 	: AttackAnimation(owner_, attacker, dest_, defender),
 	: AttackAnimation(owner_, attacker, dest_, defender),
 	  projectileEmitted(false)
 	  projectileEmitted(false)
 {
 {
-}
-
-void RangedAttackAnimation::playSound()
-{
-	CCS->soundh->playSound(battle_sound(getCreature(), shoot));
+	setSound(battle_sound(getCreature(), shoot));
 }
 }
 
 
 bool RangedAttackAnimation::init()
 bool RangedAttackAnimation::init()
 {
 {
-	assert(attackingStack);
-	assert(!myAnim->isDeadOrDying());
-
-	if(!attackingStack || myAnim->isDeadOrDying())
-	{
-		//FIXME: how is this possible?
-		logAnim->warn("Shooting animation has not started yet but attacker is dead! Aborting...");
-		delete this;
-		return false;
-	}
-
-	logAnim->info("CRangedAttackAnimation::init: stack %s", stack->getName());
-
 	setAnimationGroup();
 	setAnimationGroup();
 	initializeProjectile();
 	initializeProjectile();
-	return true;
+
+	return AttackAnimation::init();
 }
 }
 
 
 void RangedAttackAnimation::setAnimationGroup()
 void RangedAttackAnimation::setAnimationGroup()
@@ -755,11 +703,11 @@ void RangedAttackAnimation::setAnimationGroup()
 
 
 	// Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
 	// Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
 	if (projectileAngle > straightAngle)
 	if (projectileAngle > straightAngle)
-		group = getUpwardsGroup();
+		setGroup(getUpwardsGroup());
 	else if (projectileAngle < -straightAngle)
 	else if (projectileAngle < -straightAngle)
-		group = getDownwardsGroup();
+		setGroup(getDownwardsGroup());
 	else
 	else
-		group = getForwardGroup();
+		setGroup(getForwardGroup());
 }
 }
 
 
 void RangedAttackAnimation::initializeProjectile()
 void RangedAttackAnimation::initializeProjectile()
@@ -769,17 +717,17 @@ void RangedAttackAnimation::initializeProjectile()
 	Point shotOrigin = stackAnimation(attackingStack)->pos.topLeft() + Point(222, 265);
 	Point shotOrigin = stackAnimation(attackingStack)->pos.topLeft() + Point(222, 265);
 	int multiplier = stackFacingRight(attackingStack) ? 1 : -1;
 	int multiplier = stackFacingRight(attackingStack) ? 1 : -1;
 
 
-	if (group == getUpwardsGroup())
+	if (getGroup() == getUpwardsGroup())
 	{
 	{
 		shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
 		shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
 		shotOrigin.y += shooterInfo->animation.upperRightMissleOffsetY;
 		shotOrigin.y += shooterInfo->animation.upperRightMissleOffsetY;
 	}
 	}
-	else if (group == getDownwardsGroup())
+	else if (getGroup() == getDownwardsGroup())
 	{
 	{
 		shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
 		shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
 		shotOrigin.y += shooterInfo->animation.lowerRightMissleOffsetY;
 		shotOrigin.y += shooterInfo->animation.lowerRightMissleOffsetY;
 	}
 	}
-	else if (group == getForwardGroup())
+	else if (getGroup() == getForwardGroup())
 	{
 	{
 		shotOrigin.x += ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
 		shotOrigin.x += ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
 		shotOrigin.y += shooterInfo->animation.rightMissleOffsetY;
 		shotOrigin.y += shooterInfo->animation.rightMissleOffsetY;
@@ -953,11 +901,9 @@ void CastAnimation::createProjectile(const Point & from, const Point & dest) con
 uint32_t CastAnimation::getAttackClimaxFrame() const
 uint32_t CastAnimation::getAttackClimaxFrame() const
 {
 {
 	//TODO: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks
 	//TODO: allow defining this parameter in config file, separately from attackClimaxFrame of missile attacks
-	uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(group);
+	uint32_t maxFrames = stackAnimation(attackingStack)->framesInGroup(getGroup());
 
 
-	if (maxFrames > 2)
-		return maxFrames - 2;
-	return 0;
+	return maxFrames / 2;
 }
 }
 
 
 PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, int effects):
 PointEffectAnimation::PointEffectAnimation(BattleInterface & owner, std::string soundName, std::string animationName, int effects):

+ 64 - 64
client/battle/BattleAnimationClasses.h

@@ -63,86 +63,73 @@ public:
 	const CStack * stack; //id of stack whose animation it is
 	const CStack * stack; //id of stack whose animation it is
 
 
 	BattleStackAnimation(BattleInterface & owner, const CStack * _stack);
 	BattleStackAnimation(BattleInterface & owner, const CStack * _stack);
-
 	void rotateStack(BattleHex hex);
 	void rotateStack(BattleHex hex);
 };
 };
 
 
-/// This class is responsible for managing the battle attack animation
-class AttackAnimation : public BattleStackAnimation
+class StackActionAnimation : public BattleStackAnimation
 {
 {
-	bool soundPlayed;
-protected:
-	BattleHex dest; //attacked hex
-	ECreatureAnimType::Type group;
-	const CStack *defendingStack;
-	const CStack *attackingStack;
-	int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
+	ECreatureAnimType::Type nextGroup;
+	ECreatureAnimType::Type currGroup;
+	std::string sound;
+public:
+	void setNextGroup( ECreatureAnimType::Type group );
+	void setGroup( ECreatureAnimType::Type group );
+	void setSound( std::string sound );
 
 
-	const CCreature * getCreature() const;
-	ECreatureAnimType::Type findValidGroup( const std::vector<ECreatureAnimType::Type> candidates ) const;
+	ECreatureAnimType::Type getGroup() const;
 
 
-public:
-	virtual void playSound() = 0;
+	StackActionAnimation(BattleInterface & owner, const CStack * _stack);
+	~StackActionAnimation();
 
 
-	void nextFrame() override;
-	AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
-	~AttackAnimation();
+	bool init() override;
 };
 };
 
 
 /// Animation of a defending unit
 /// Animation of a defending unit
-class DefenceAnimation : public BattleStackAnimation
+class DefenceAnimation : public StackActionAnimation
 {
 {
 public:
 public:
-	bool init() override;
 	DefenceAnimation(BattleInterface & owner, const CStack * stack);
 	DefenceAnimation(BattleInterface & owner, const CStack * stack);
 };
 };
 
 
 /// Animation of a hit unit
 /// Animation of a hit unit
-class HittedAnimation : public BattleStackAnimation
+class HittedAnimation : public StackActionAnimation
 {
 {
 public:
 public:
 	HittedAnimation(BattleInterface & owner, const CStack * stack);
 	HittedAnimation(BattleInterface & owner, const CStack * stack);
-	bool init() override;
 };
 };
 
 
 /// Animation of a dying unit
 /// Animation of a dying unit
-class DeathAnimation : public BattleStackAnimation
+class DeathAnimation : public StackActionAnimation
 {
 {
-	bool rangedAttack;
-	ECreatureAnimType::Type getMyAnimType();
 public:
 public:
-	bool init() override;
 	DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged);
 	DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged);
-	~DeathAnimation();
 };
 };
 
 
-class DummyAnimation : public BattleAnimation
+/// Resurrects stack from dead state
+class ResurrectionAnimation : public StackActionAnimation
 {
 {
-private:
-	int counter;
-	int howMany;
 public:
 public:
-	bool init() override;
-	void nextFrame() override;
-
-	DummyAnimation(BattleInterface & owner, int howManyFrames);
+	ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
 };
 };
 
 
-/// Hand-to-hand attack
-class MeleeAttackAnimation : public AttackAnimation
+class ColorTransformAnimation : public StackActionAnimation
 {
 {
-	bool multiAttack;
+	std::vector<ColorFilter> steps;
+	std::vector<float> timePoints;
+	const CSpell * spell;
+
+	float totalProgress;
 
 
-	ECreatureAnimType::Type getUpwardsGroup() const;
-	ECreatureAnimType::Type getForwardGroup() const;
-	ECreatureAnimType::Type getDownwardsGroup() const;
+	void nextFrame() override;
 
 
+	ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
 public:
 public:
-	bool init() override;
-	void nextFrame() override;
-	void playSound() override;
 
 
-	MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack);
+	static ColorTransformAnimation * petrifyAnimation  (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
+	static ColorTransformAnimation * cloneAnimation    (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
+	static ColorTransformAnimation * bloodlustAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
+	static ColorTransformAnimation * fadeInAnimation   (BattleInterface & owner, const CStack * _stack);
+	static ColorTransformAnimation * fadeOutAnimation  (BattleInterface & owner, const CStack * _stack);
 };
 };
 
 
 /// Base class for all animations that play during stack movement
 /// Base class for all animations that play during stack movement
@@ -207,36 +194,38 @@ public:
 	ReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest);
 	ReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest);
 };
 };
 
 
-/// Resurrects stack from dead state
-class ResurrectionAnimation : public BattleStackAnimation
+/// This class is responsible for managing the battle attack animation
+class AttackAnimation : public StackActionAnimation
 {
 {
-public:
-	bool init() override;
+protected:
+	BattleHex dest; //attacked hex
+	const CStack *defendingStack;
+	const CStack *attackingStack;
+	int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
 
 
-	ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
+	const CCreature * getCreature() const;
+	ECreatureAnimType::Type findValidGroup( const std::vector<ECreatureAnimType::Type> candidates ) const;
+
+public:
+	AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
 };
 };
 
 
-class ColorTransformAnimation : public BattleStackAnimation
+/// Hand-to-hand attack
+class MeleeAttackAnimation : public AttackAnimation
 {
 {
-	std::vector<ColorFilter> steps;
-	std::vector<float> timePoints;
-	const CSpell * spell;
+	ECreatureAnimType::Type getUpwardsGroup(bool multiAttack) const;
+	ECreatureAnimType::Type getForwardGroup(bool multiAttack) const;
+	ECreatureAnimType::Type getDownwardsGroup(bool multiAttack) const;
 
 
-	float totalProgress;
+	ECreatureAnimType::Type selectGroup(bool multiAttack);
 
 
-	bool init() override;
-	void nextFrame() override;
-
-	ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
 public:
 public:
+	MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack);
 
 
-	static ColorTransformAnimation * petrifyAnimation  (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
-	static ColorTransformAnimation * cloneAnimation    (BattleInterface & owner, const CStack * _stack, const CSpell * spell);
-	static ColorTransformAnimation * bloodlustAnimation(BattleInterface & owner, const CStack * _stack, const CSpell * spell);
-	static ColorTransformAnimation * fadeInAnimation   (BattleInterface & owner, const CStack * _stack);
-	static ColorTransformAnimation * fadeOutAnimation  (BattleInterface & owner, const CStack * _stack);
+	void nextFrame() override;
 };
 };
 
 
+
 class RangedAttackAnimation : public AttackAnimation
 class RangedAttackAnimation : public AttackAnimation
 {
 {
 	void setAnimationGroup();
 	void setAnimationGroup();
@@ -260,7 +249,6 @@ public:
 
 
 	bool init() override;
 	bool init() override;
 	void nextFrame() override;
 	void nextFrame() override;
-	void playSound() override;
 };
 };
 
 
 /// Shooting attack
 /// Shooting attack
@@ -307,6 +295,18 @@ public:
 	CastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
 	CastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
 };
 };
 
 
+class DummyAnimation : public BattleAnimation
+{
+private:
+	int counter;
+	int howMany;
+public:
+	bool init() override;
+	void nextFrame() override;
+
+	DummyAnimation(BattleInterface & owner, int howManyFrames);
+};
+
 /// Class that plays effect at one or more positions along with (single) sound effect
 /// Class that plays effect at one or more positions along with (single) sound effect
 class PointEffectAnimation : public BattleAnimation
 class PointEffectAnimation : public BattleAnimation
 {
 {

+ 3 - 0
client/battle/BattleConstants.h

@@ -56,6 +56,8 @@ namespace ECreatureAnimType
 {
 {
 enum Type // list of creature animations, numbers were taken from def files
 enum Type // list of creature animations, numbers were taken from def files
 {
 {
+	INVALID         = -1,
+
 	MOVING          = 0,
 	MOVING          = 0,
 	MOUSEON         = 1,
 	MOUSEON         = 1,
 	HOLDING         = 2,  // base idling animation
 	HOLDING         = 2,  // base idling animation
@@ -82,6 +84,7 @@ enum Type // list of creature animations, numbers were taken from def files
 	DEAD            = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
 	DEAD            = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
 	DEAD_RANGED     = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
 	DEAD_RANGED     = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
 	RESURRECTION    = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copiend here
 	RESURRECTION    = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copiend here
+	FROZEN          = 25, // new group, used when stack animation is paused (e.g. petrified). If empty - consist of first frame from HOLDING animation
 
 
 	CAST_UP            = 30,
 	CAST_UP            = 30,
 	CAST_FRONT         = 31,
 	CAST_FRONT         = 31,

+ 7 - 3
client/battle/BattleStacksController.cpp

@@ -40,8 +40,14 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnima
 	if(!animation)
 	if(!animation)
 		return;
 		return;
 
 
+	if (!stack->isFrozen() && animation->getType() == ECreatureAnimType::FROZEN)
+		animation->setType(ECreatureAnimType::HOLDING);
+
 	if (animation->isIdle())
 	if (animation->isIdle())
 	{
 	{
+		if (stack->isFrozen())
+			animation->setType(ECreatureAnimType::FROZEN);
+
 		const CCreature *creature = stack->getCreature();
 		const CCreature *creature = stack->getCreature();
 
 
 		if (animation->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
 		if (animation->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
@@ -249,7 +255,7 @@ void BattleStacksController::setHoveredStack(const CStack *stack)
 	{
 	{
 		mouseHoveredStack = stack;
 		mouseHoveredStack = stack;
 
 
-		if (mouseHoveredStack)
+		if (mouseHoveredStack && !mouseHoveredStack->isFrozen())
 		{
 		{
 			stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
 			stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
 			if (stackAnimation[mouseHoveredStack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
 			if (stackAnimation[mouseHoveredStack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
@@ -361,8 +367,6 @@ void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
 	}
 	}
 
 
 	bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
 	bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
-	//bool stackPetrified = stack->hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE));
-	//bool stackFrozen = stackHasProjectile || stackPetrified;
 
 
 	if (stackHasProjectile)
 	if (stackHasProjectile)
 		stackAnimation[stack->ID]->pause();
 		stackAnimation[stack->ID]->pause();

+ 6 - 0
client/battle/CreatureAnimation.cpp

@@ -190,6 +190,12 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
 		reverse->duplicateImage(ECreatureAnimType::DEATH_RANGED, reverse->size(ECreatureAnimType::DEATH_RANGED)-1, ECreatureAnimType::DEAD_RANGED);
 		reverse->duplicateImage(ECreatureAnimType::DEATH_RANGED, reverse->size(ECreatureAnimType::DEATH_RANGED)-1, ECreatureAnimType::DEAD_RANGED);
 	}
 	}
 
 
+	if(forward->size(ECreatureAnimType::FROZEN) == 0)
+	{
+		forward->duplicateImage(ECreatureAnimType::HOLDING, 0, ECreatureAnimType::FROZEN);
+		reverse->duplicateImage(ECreatureAnimType::HOLDING, 0, ECreatureAnimType::FROZEN);
+	}
+
 	if(forward->size(ECreatureAnimType::RESURRECTION) == 0)
 	if(forward->size(ECreatureAnimType::RESURRECTION) == 0)
 	{
 	{
 		for (size_t i = 0; i < forward->size(ECreatureAnimType::DEATH); ++i)
 		for (size_t i = 0; i < forward->size(ECreatureAnimType::DEATH); ++i)

+ 5 - 0
lib/battle/CUnitState.cpp

@@ -547,6 +547,11 @@ bool CUnitState::isGhost() const
 	return ghost;
 	return ghost;
 }
 }
 
 
+bool CUnitState::isFrozen() const
+{
+	return hasBonus(Selector::source(Bonus::SPELL_EFFECT, SpellID::STONE_GAZE));
+}
+
 bool CUnitState::isValidTarget(bool allowDead) const
 bool CUnitState::isValidTarget(bool allowDead) const
 {
 {
 	return (alive() || (allowDead && isDead())) && getPosition().isValid() && !isTurret();
 	return (alive() || (allowDead && isDead())) && getPosition().isValid() && !isTurret();

+ 1 - 0
lib/battle/CUnitState.h

@@ -195,6 +195,7 @@ public:
 	bool ableToRetaliate() const override;
 	bool ableToRetaliate() const override;
 	bool alive() const override;
 	bool alive() const override;
 	bool isGhost() const override;
 	bool isGhost() const override;
+	bool isFrozen() const override;
 	bool isValidTarget(bool allowDead = false) const override;
 	bool isValidTarget(bool allowDead = false) const override;
 
 
 	bool isClone() const override;
 	bool isClone() const override;

+ 1 - 0
lib/battle/Unit.h

@@ -43,6 +43,7 @@ public:
 	virtual bool ableToRetaliate() const = 0;
 	virtual bool ableToRetaliate() const = 0;
 	virtual bool alive() const = 0;
 	virtual bool alive() const = 0;
 	virtual bool isGhost() const = 0;
 	virtual bool isGhost() const = 0;
+	virtual bool isFrozen() const = 0;
 
 
 	bool isDead() const;
 	bool isDead() const;
 	bool isTurret() const;
 	bool isTurret() const;