1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

Spell animation will now wait for hero animation before playing

This commit is contained in:
Ivan Savenko
2022-12-13 18:49:35 +02:00
parent e7206cb759
commit b2f5a87a0f
5 changed files with 113 additions and 60 deletions

View File

@@ -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);
delete this;
}
WaitingProjectileAnimation::WaitingProjectileAnimation(BattleInterface & owner_, const CStack * shooter):
BattleAnimation(owner_),
shooter(shooter)
{}
bool WaitingProjectileAnimation::init()
bool HeroCastAnimation::init()
{
for(auto & elem : pendingAnimations())
{
auto * attackAnim = dynamic_cast<RangedAttackAnimation *>(elem);
hero->setPhase(EHeroAnimType::CAST_SPELL);
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;
}
}
hero->onPhaseFinished([&](){
assert(owner.getAnimationCondition(EAnimationEvents::HIT) == true);
delete this;
});
if(owner.projectilesController->hasActiveProjectile(shooter))
return false;
initializeProjectile();
return true;
}
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
destcoord += Point(222, 265); // FIXME: what are these constants?
owner.projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
}
void HeroCastAnimation::emitProjectile()
{
if (projectileEmitted)
return;
//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 );
projectileEmitted = 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();
}
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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