Ver Fonte

Spell animation will now wait for hero animation before playing

Ivan Savenko há 2 anos atrás
pai
commit
b2f5a87a0f

+ 69 - 22
client/battle/BattleAnimationClasses.cpp

@@ -11,6 +11,7 @@
 #include "BattleAnimationClasses.h"
 
 #include "BattleInterface.h"
+#include "BattleInterfaceClasses.h"
 #include "BattleProjectileController.h"
 #include "BattleSiegeController.h"
 #include "BattleFieldController.h"
@@ -1055,35 +1056,81 @@ PointEffectAnimation::~PointEffectAnimation()
 	assert(soundFinished);
 }
 
-void WaitingProjectileAnimation::nextFrame()
+HeroCastAnimation::HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell):
+	BattleAnimation(owner),
+	projectileEmitted(false),
+	hero(hero),
+	target(defender),
+	tile(dest),
+	spell(spell)
 {
-	// initialization conditions fulfilled, delay is over
-	if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
-		owner.setAnimationCondition(EAnimationEvents::HIT, true);
+}
+
+bool HeroCastAnimation::init()
+{
+	hero->setPhase(EHeroAnimType::CAST_SPELL);
 
-	delete this;
+	hero->onPhaseFinished([&](){
+		assert(owner.getAnimationCondition(EAnimationEvents::HIT) == true);
+		delete this;
+	});
+
+	initializeProjectile();
+
+	return true;
 }
 
-WaitingProjectileAnimation::WaitingProjectileAnimation(BattleInterface & owner_, const CStack * shooter):
-	BattleAnimation(owner_),
-	shooter(shooter)
-{}
+void HeroCastAnimation::initializeProjectile()
+{
+	//spell has no projectile to play, ignore this step
+	if (spell->animationInfo.projectile.empty())
+		return;
+
+	Point srccoord = hero->pos.center();
+	Point destcoord = owner.stacksController->getStackPositionAtHex(tile, target); //position attacked by projectile
 
-bool WaitingProjectileAnimation::init()
+	destcoord += Point(222, 265); // FIXME: what are these constants?
+	owner.projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
+}
+
+void HeroCastAnimation::emitProjectile()
 {
-	for(auto & elem : pendingAnimations())
-	{
-		auto * attackAnim = dynamic_cast<RangedAttackAnimation *>(elem);
+	if (projectileEmitted)
+		return;
 
-		if( attackAnim && shooter && attackAnim->stack->ID == shooter->ID && !attackAnim->isInitialized() )
-		{
-			// there is ongoing ranged attack that involves our stack, but projectile was not created yet
-			return false;
-		}
-	}
+	//spell has no projectile to play, skip this step and immediately play hit animations
+	if (spell->animationInfo.projectile.empty())
+		emitAnimationEvent();
+	else
+		owner.projectilesController->emitStackProjectile( nullptr );
 
-	if(owner.projectilesController->hasActiveProjectile(shooter))
-		return false;
+	projectileEmitted = true;
+}
 
-	return true;
+void HeroCastAnimation::emitAnimationEvent()
+{
+	if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
+		owner.setAnimationCondition(EAnimationEvents::HIT, true);
+}
+
+void HeroCastAnimation::nextFrame()
+{
+	float frame = hero->getFrame();
+
+	if (frame < 4.0f)
+		return;
+
+	if (!projectileEmitted)
+	{
+		emitProjectile();
+		hero->pause();
+		return;
+	}
+
+	if (!owner.projectilesController->hasActiveProjectile(nullptr))
+	{
+		emitAnimationEvent();
+		//FIXME: check H3 - it is possible that hero animation should be paused until hit effect is over, not just projectile
+		hero->play();
+	}
 }

+ 12 - 9
client/battle/BattleAnimationClasses.h

@@ -20,6 +20,7 @@ class CSpell;
 
 VCMI_LIB_NAMESPACE_END
 
+class BattleHero;
 class CAnimation;
 class BattleInterface;
 class CreatureAnimation;
@@ -328,18 +329,20 @@ public:
 	void nextFrame() override;
 };
 
-class HeroAnimation : public BattleAnimation
+class HeroCastAnimation : public BattleAnimation
 {
-public:
-	HeroAnimation(BattleInterface * owner_, const CStack * shooter);
-};
+	std::shared_ptr<BattleHero> hero;
+	const CStack * target;
+	const CSpell * spell;
+	BattleHex tile;
+	bool projectileEmitted;
+
+	void initializeProjectile();
+	void emitProjectile();
+	void emitAnimationEvent();
 
-/// Class that waits till projectile of certain shooter hits a target
-class WaitingProjectileAnimation : public BattleAnimation
-{
-	const CStack * shooter;
 public:
-	WaitingProjectileAnimation(BattleInterface & owner, const CStack * shooter);
+	HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell);
 
 	void nextFrame() override;
 	bool init() override;

+ 4 - 14
client/battle/BattleInterface.cpp

@@ -510,21 +510,14 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 			});
 		}
 		else
-		if (targetedTile.isValid() && !spell->animationInfo.projectile.empty())
+		if (targetedTile.isValid())
 		{
-			// this is spell cast by hero with valid destination & valid projectile -> play animation
+			auto hero = sc->side ? defendingHero : attackingHero;
+			assert(hero);
 
-			const CStack * target = curInt->cb->battleGetStackByPos(targetedTile);
-			Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos;	//hero position
-			Point destcoord = stacksController->getStackPositionAtHex(targetedTile, target); //position attacked by projectile
-			destcoord += Point(250, 240); // FIXME: what are these constants?
-
-			//FIXME: should be replaced with new hero cast animation type
 			executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
 			{
-				projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
-				projectilesController->emitStackProjectile( nullptr );
-				stacksController->addNewAnim(new WaitingProjectileAnimation(*this, nullptr));
+				stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
 			});
 		}
 	}
@@ -770,10 +763,7 @@ void BattleInterface::startAction(const BattleAction* action)
 	redraw(); // redraw after deactivation, including proper handling of hovered hexes
 
 	if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
-	{
-		setHeroAnimation(action->side, EHeroAnimType::CAST_SPELL);
 		return;
-	}
 
 	if (!stack)
 	{

+ 20 - 13
client/battle/BattleInterfaceClasses.cpp

@@ -176,9 +176,8 @@ void BattleHero::render(Canvas & canvas)
 	canvas.draw(flagFrame, flagPosition);
 	canvas.draw(heroFrame, heroPosition);
 
-	//FIXME: un-hardcode speed
-	flagCurrentFrame += 0.25f;
-	currentFrame += 0.25f;
+	flagCurrentFrame += currentSpeed;
+	currentFrame += currentSpeed;
 
 	if(flagCurrentFrame >= flagAnimation->size(0))
 		flagCurrentFrame -= flagAnimation->size(0);
@@ -186,14 +185,21 @@ void BattleHero::render(Canvas & canvas)
 	if(currentFrame >= animation->size(groupIndex))
 	{
 		currentFrame -= animation->size(groupIndex);
-		if (phaseFinishedCallback)
-		{
-			phaseFinishedCallback();
-			phaseFinishedCallback = std::function<void()>();
-		}
+		switchToNextPhase();
 	}
 }
 
+void BattleHero::pause()
+{
+	currentSpeed = 0.f;
+}
+
+void BattleHero::play()
+{
+	//FIXME: un-hardcode speed
+	currentSpeed = 0.25f;
+}
+
 float BattleHero::getFrame() const
 {
 	return currentFrame;
@@ -266,11 +272,10 @@ void BattleHero::switchToNextPhase()
 {
 	phase = nextPhase;
 	currentFrame = 0.f;
-	if (phaseFinishedCallback)
-	{
-		phaseFinishedCallback();
-		phaseFinishedCallback = std::function<void()>();
-	}
+
+	auto copy = phaseFinishedCallback;
+	phaseFinishedCallback.clear();
+	copy();
 }
 
 BattleHero::BattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const BattleInterface & owner):
@@ -279,6 +284,7 @@ BattleHero::BattleHero(const std::string & animationPath, bool flipG, PlayerColo
 	owner(owner),
 	phase(EHeroAnimType::HOLDING),
 	nextPhase(EHeroAnimType::HOLDING),
+	currentSpeed(0.f),
 	currentFrame(0.f),
 	flagCurrentFrame(0.f)
 {
@@ -304,6 +310,7 @@ BattleHero::BattleHero(const std::string & animationPath, bool flipG, PlayerColo
 	addUsedEvents(LCLICK | RCLICK | HOVER);
 
 	switchToNextPhase();
+	play();
 }
 
 HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)

+ 7 - 1
client/battle/BattleInterfaceClasses.h

@@ -11,6 +11,7 @@
 
 #include "BattleConstants.h"
 #include "../gui/CIntObject.h"
+#include "../../lib/FunctionList.h"
 #include "../../lib/battle/BattleHex.h"
 #include "../windows/CWindowObject.h"
 
@@ -80,7 +81,8 @@ class BattleHero : public CIntObject
 {
 	bool flip; //false if it's attacking hero, true otherwise
 
-	std::function<void()> phaseFinishedCallback;
+	CFunctionList<void()> phaseFinishedCallback;
+
 	std::shared_ptr<CAnimation> animation;
 	std::shared_ptr<CAnimation> flagAnimation;
 
@@ -90,6 +92,7 @@ class BattleHero : public CIntObject
 	EHeroAnimType phase; //stage of animation
 	EHeroAnimType nextPhase; //stage of animation to be set after current phase is fully displayed
 
+	float currentSpeed;
 	float currentFrame; //frame of animation
 	float flagCurrentFrame;
 
@@ -102,6 +105,9 @@ public:
 	float getFrame() const;
 	void onPhaseFinished(const std::function<void()> &);
 
+	void pause();
+	void play();
+
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override; //call-in
 	void clickRight(tribool down, bool previousState) override; //call-in