1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-14 02:33:51 +02:00

Merge pull request #1729 from IvanSavenko/battle_animation_fixes

Fixes for battle UI regressions
This commit is contained in:
Ivan Savenko 2023-03-21 14:32:45 +02:00 committed by GitHub
commit 9dceed4f56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 225 additions and 221 deletions

View File

@ -81,6 +81,8 @@
"vcmi.battleOptions.animationsSpeed6.help": "Sets animation speed to instantaneous", "vcmi.battleOptions.animationsSpeed6.help": "Sets animation speed to instantaneous",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\n Skip short music that plays at beginning of each battle before action starts. Can also be skipped by pressing ESC key.", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\n Skip short music that plays at beginning of each battle before action starts. Can also be skipped by pressing ESC key.",
"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to skip battle intro",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Show Available Creatures",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\n Shows creatures available to purchase instead of their growth in town summary (bottom-left corner).", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Show Available Creatures}\n\n Shows creatures available to purchase instead of their growth in town summary (bottom-left corner).",

View File

@ -82,6 +82,8 @@
"vcmi.battleOptions.animationsSpeed6.help": "Встановити миттєву швидкість анімації", "vcmi.battleOptions.animationsSpeed6.help": "Встановити миттєву швидкість анімації",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Пропускати вступну музику", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Пропускати вступну музику",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.",
"vcmi.battleWindow.pressKeyToSkipIntro" : "Натисніть будь-яку клавішу, щоб розпочати бій",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Показувати доступних істот", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Показувати доступних істот",
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Показувати доступних істот}\n\n Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Показувати доступних істот}\n\n Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).",

View File

@ -677,6 +677,13 @@ PossiblePlayerBattleAction BattleActionsController::selectAction(BattleHex targe
void BattleActionsController::onHexHovered(BattleHex hoveredHex) void BattleActionsController::onHexHovered(BattleHex hoveredHex)
{ {
if (owner.openingPlaying())
{
currentConsoleMsg = VLC->generaltexth->translate("vcmi.battleWindow.pressKeyToSkipIntro");
GH.statusbar->write(currentConsoleMsg);
return;
}
if (owner.stacksController->getActiveStack() == nullptr) if (owner.stacksController->getActiveStack() == nullptr)
return; return;

View File

@ -306,10 +306,8 @@ void MeleeAttackAnimation::nextFrame()
size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(getGroup()); size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(getGroup());
if ( currentFrame * 2 >= totalFrames ) if ( currentFrame * 2 >= totalFrames )
{ owner.executeAnimationStage(EAnimationEvents::HIT);
if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
owner.setAnimationCondition(EAnimationEvents::HIT, true);
}
AttackAnimation::nextFrame(); AttackAnimation::nextFrame();
} }
@ -356,9 +354,9 @@ bool MovementAnimation::init()
myAnim->setType(ECreatureAnimType::MOVING); myAnim->setType(ECreatureAnimType::MOVING);
} }
if (owner.moveSoundHander == -1) if (moveSoundHander == -1)
{ {
owner.moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1); moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1);
} }
Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack); Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
@ -418,11 +416,8 @@ MovementAnimation::~MovementAnimation()
{ {
assert(stack); assert(stack);
if(owner.moveSoundHander != -1) if(moveSoundHander != -1)
{ CCS->soundh->stopSound(moveSoundHander);
CCS->soundh->stopSound(owner.moveSoundHander);
owner.moveSoundHander = -1;
}
} }
MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance) MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector<BattleHex> _destTiles, int _distance)
@ -432,6 +427,7 @@ MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stac
begX(0), begY(0), begX(0), begY(0),
distanceX(0), distanceY(0), distanceX(0), distanceY(0),
progressPerSecond(0.0), progressPerSecond(0.0),
moveSoundHander(-1),
progress(0.0) progress(0.0)
{ {
logAnim->debug("Created MovementAnimation for %s", stack->getName()); logAnim->debug("Created MovementAnimation for %s", stack->getName());
@ -709,12 +705,17 @@ void RangedAttackAnimation::nextFrame()
if (projectileEmitted) if (projectileEmitted)
{ {
if (!owner.projectilesController->hasActiveProjectile(attackingStack, false)) if (!owner.projectilesController->hasActiveProjectile(attackingStack, false))
{ owner.executeAnimationStage(EAnimationEvents::HIT);
if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
owner.setAnimationCondition(EAnimationEvents::HIT, true);
}
} }
bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
if (!projectileEmitted || stackHasProjectile)
stackAnimation(attackingStack)->playUntil(getAttackClimaxFrame());
else
stackAnimation(attackingStack)->playUntil(static_cast<size_t>(-1));
AttackAnimation::nextFrame(); AttackAnimation::nextFrame();
if (!projectileEmitted) if (!projectileEmitted)
@ -1052,7 +1053,6 @@ bool HeroCastAnimation::init()
hero->setPhase(EHeroAnimType::CAST_SPELL); hero->setPhase(EHeroAnimType::CAST_SPELL);
hero->onPhaseFinished([&](){ hero->onPhaseFinished([&](){
assert(owner.getAnimationCondition(EAnimationEvents::HIT) == true);
delete this; delete this;
}); });
@ -1093,8 +1093,7 @@ void HeroCastAnimation::emitProjectile()
void HeroCastAnimation::emitAnimationEvent() void HeroCastAnimation::emitAnimationEvent()
{ {
if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) owner.executeAnimationStage(EAnimationEvents::HIT);
owner.setAnimationCondition(EAnimationEvents::HIT, true);
} }
void HeroCastAnimation::nextFrame() void HeroCastAnimation::nextFrame()

View File

@ -141,6 +141,8 @@ protected:
class MovementAnimation : public StackMoveAnimation class MovementAnimation : public StackMoveAnimation
{ {
private: private:
int moveSoundHander; // sound handler used when moving a unit
std::vector<BattleHex> destTiles; //full path, includes already passed hexes std::vector<BattleHex> destTiles; //full path, includes already passed hexes
ui32 curentMoveIndex; // index of nextHex in destTiles ui32 curentMoveIndex; // index of nextHex in destTiles

View File

@ -30,14 +30,22 @@ enum class EBattleEffect
INVALID = -1, INVALID = -1,
}; };
enum class EAnimationEvents { enum class EAnimationEvents
OPENING = 0, // battle opening sound is playing {
ACTION = 1, // there are any ongoing animations // any action
MOVEMENT = 2, // stacks are moving or turning around ROTATE, // stacks rotate before action
BEFORE_HIT = 3, // effects played before all attack/defence/hit animations
ATTACK = 4, // attack and defence animations are playing // movement action
HIT = 5, // hit & death animations are playing MOVE_START, // stack starts movement
AFTER_HIT = 6, // after all hit & death animations are over MOVEMENT, // movement animation loop starts
MOVE_END, // stack end movement
// attack/spellcast action
BEFORE_HIT, // attack and defence effects play, e.g. luck/death blow
ATTACK, // attack and defence animations are playing
HIT, // hit & death animations are playing
AFTER_HIT, // post-attack effect, e.g. phoenix rebirth
COUNT COUNT
}; };

View File

@ -56,7 +56,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, std::string so
void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte) void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID); const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID);
if(!stack) if(!stack)
@ -90,12 +90,12 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
default: default:
return; return;
} }
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.waitForAnimations();
} }
void BattleEffectsController::startAction(const BattleAction* action) void BattleEffectsController::startAction(const BattleAction* action)
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber); const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
@ -110,7 +110,7 @@ void BattleEffectsController::startAction(const BattleAction* action)
break; break;
} }
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.waitForAnimations();
} }
void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer) void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer)

View File

@ -54,11 +54,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
, attackerInt(att) , attackerInt(att)
, defenderInt(defen) , defenderInt(defen)
, curInt(att) , curInt(att)
, moveSoundHander(-1)
{ {
for ( auto & event : animationEvents)
event.setn(false);
if(spectatorInt) if(spectatorInt)
{ {
curInt = spectatorInt; curInt = spectatorInt;
@ -96,7 +92,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
obstacleController.reset(new BattleObstacleController(*this)); obstacleController.reset(new BattleObstacleController(*this));
adventureInt->onAudioPaused(); adventureInt->onAudioPaused();
setAnimationCondition(EAnimationEvents::OPENING, true); ongoingAnimationsState.set(true);
GH.pushInt(windowObject); GH.pushInt(windowObject);
windowObject->blockUI(true); windowObject->blockUI(true);
@ -107,12 +103,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
void BattleInterface::playIntroSoundAndUnlockInterface() void BattleInterface::playIntroSoundAndUnlockInterface()
{ {
if(settings["gameTweaks"]["skipBattleIntroMusic"].Bool())
{
onIntroSoundPlayed();
return;
}
auto onIntroPlayed = [this]() auto onIntroPlayed = [this]()
{ {
if(LOCPLINT->battleInt) if(LOCPLINT->battleInt)
@ -124,19 +114,22 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds); battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
if (battleIntroSoundChannel != -1) if (battleIntroSoundChannel != -1)
{
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed); CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
if (settings["gameTweaks"]["skipBattleIntroMusic"].Bool())
openingEnd();
}
else else
onIntroSoundPlayed(); onIntroSoundPlayed();
} }
void BattleInterface::onIntroSoundPlayed() void BattleInterface::onIntroSoundPlayed()
{ {
setAnimationCondition(EAnimationEvents::OPENING, false); if (openingPlaying())
openingEnd();
CCS->musich->playMusicFromSet("battle", true, true); CCS->musich->playMusicFromSet("battle", true, true);
if(tacticsMode)
tacticNextStack(nullptr);
activateStack();
battleIntroSoundChannel = -1;
} }
BattleInterface::~BattleInterface() BattleInterface::~BattleInterface()
@ -147,10 +140,7 @@ BattleInterface::~BattleInterface()
if (adventureInt) if (adventureInt)
adventureInt->onAudioResumed(); adventureInt->onAudioResumed();
// may happen if user decided to close game while in battle onAnimationsFinished();
if (getAnimationCondition(EAnimationEvents::ACTION) == true)
logGlobal->error("Shutting down BattleInterface during animation playback!");
setAnimationCondition(EAnimationEvents::ACTION, false);
} }
void BattleInterface::redrawBattlefield() void BattleInterface::redrawBattlefield()
@ -217,7 +207,7 @@ void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
void BattleInterface::newRoundFirst( int round ) void BattleInterface::newRoundFirst( int round )
{ {
waitForAnimationCondition(EAnimationEvents::OPENING, false); waitForAnimations();
} }
void BattleInterface::newRound(int number) void BattleInterface::newRound(int number)
@ -299,8 +289,7 @@ void BattleInterface::gateStateChanged(const EGateState state)
void BattleInterface::battleFinished(const BattleResult& br) void BattleInterface::battleFinished(const BattleResult& br)
{ {
assert(getAnimationCondition(EAnimationEvents::ACTION) == false); checkForAnimations();
waitForAnimationCondition(EAnimationEvents::ACTION, false);
stacksController->setActiveStack(nullptr); stacksController->setActiveStack(nullptr);
CCS->curh->set(Cursor::Map::POINTER); CCS->curh->set(Cursor::Map::POINTER);
@ -337,7 +326,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
EAnimationEvents::HIT: EAnimationEvents::HIT:
EAnimationEvents::BEFORE_HIT;//FIXME: recheck whether this should be on projectile spawning EAnimationEvents::BEFORE_HIT;//FIXME: recheck whether this should be on projectile spawning
executeOnAnimationCondition(group, true, [=]() { addToAnimationStage(group, [=]() {
CCS->soundh->playSound(castSoundPath); CCS->soundh->playSound(castSoundPath);
}); });
} }
@ -348,7 +337,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
if(casterStack != nullptr ) if(casterStack != nullptr )
{ {
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]()
{ {
stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell)); stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
displaySpellCast(spell, casterStack->getPosition()); displaySpellCast(spell, casterStack->getPosition());
@ -359,14 +348,14 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
auto hero = sc->side ? defendingHero : attackingHero; auto hero = sc->side ? defendingHero : attackingHero;
assert(hero); assert(hero);
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]()
{ {
stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell)); stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
}); });
} }
} }
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ addToAnimationStage(EAnimationEvents::HIT, [=](){
displaySpellHit(spell, targetedTile); displaySpellHit(spell, targetedTile);
}); });
@ -377,7 +366,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
assert(stack); assert(stack);
if(stack) if(stack)
{ {
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ addToAnimationStage(EAnimationEvents::HIT, [=](){
displaySpellEffect(spell, stack->getPosition()); displaySpellEffect(spell, stack->getPosition());
}); });
} }
@ -387,14 +376,14 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
{ {
auto stack = curInt->cb->battleGetStackByID(elem, false); auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack); assert(stack);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ addToAnimationStage(EAnimationEvents::HIT, [=](){
effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition()); effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition());
}); });
} }
if (!sc->resistedCres.empty()) if (!sc->resistedCres.empty())
{ {
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ addToAnimationStage(EAnimationEvents::HIT, [=](){
CCS->soundh->playSound("MAGICRES"); CCS->soundh->playSound("MAGICRES");
}); });
} }
@ -403,7 +392,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
{ {
auto stack = curInt->cb->battleGetStackByID(elem, false); auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack); assert(stack);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ addToAnimationStage(EAnimationEvents::HIT, [=](){
effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition()); effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition());
}); });
} }
@ -415,7 +404,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
Point rightHero = Point(755, 30); Point rightHero = Point(755, 30);
bool side = sc->side; bool side = sc->side;
executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero)); stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero)); stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
}); });
@ -535,6 +524,24 @@ void BattleInterface::activateStack()
GH.fakeMouseMove(); GH.fakeMouseMove();
} }
bool BattleInterface::openingPlaying()
{
return battleIntroSoundChannel != -1;
}
void BattleInterface::openingEnd()
{
assert(openingPlaying());
if (!openingPlaying())
return;
onAnimationsFinished();
if(tacticsMode)
tacticNextStack(nullptr);
activateStack();
battleIntroSoundChannel = -1;
}
bool BattleInterface::makingTurn() const bool BattleInterface::makingTurn() const
{ {
return stacksController->getActiveStack() != nullptr; return stacksController->getActiveStack() != nullptr;
@ -611,8 +618,7 @@ void BattleInterface::tacticNextStack(const CStack * current)
current = stacksController->getActiveStack(); current = stacksController->getActiveStack();
//no switching stacks when the current one is moving //no switching stacks when the current one is moving
assert(getAnimationCondition(EAnimationEvents::ACTION) == false); checkForAnimations();
waitForAnimationCondition(EAnimationEvents::ACTION, false);
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE); TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
vstd::erase_if (stacksOfMine, &immobile); vstd::erase_if (stacksOfMine, &immobile);
@ -698,18 +704,24 @@ void BattleInterface::castThisSpell(SpellID spellID)
actionsController->castThisSpell(spellID); actionsController->castThisSpell(spellID);
} }
void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state) void BattleInterface::executeStagedAnimations()
{ {
logAnim->debug("setAnimationCondition: %d -> %s", static_cast<int>(event), state ? "ON" : "OFF"); EAnimationEvents earliestStage = EAnimationEvents::COUNT;
size_t index = static_cast<size_t>(event); for(const auto & event : awaitingEvents)
animationEvents[index].setn(state); earliestStage = std::min(earliestStage, event.event);
if(earliestStage != EAnimationEvents::COUNT)
executeAnimationStage(earliestStage);
}
void BattleInterface::executeAnimationStage(EAnimationEvents event)
{
decltype(awaitingEvents) executingEvents; decltype(awaitingEvents) executingEvents;
for (auto it = awaitingEvents.begin(); it != awaitingEvents.end();) for(auto it = awaitingEvents.begin(); it != awaitingEvents.end();)
{ {
if (it->event == event && it->eventState == state) if(it->event == event)
{ {
executingEvents.push_back(*it); executingEvents.push_back(*it);
it = awaitingEvents.erase(it); it = awaitingEvents.erase(it);
@ -717,27 +729,43 @@ void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state)
else else
++it; ++it;
} }
for(const auto & event : executingEvents)
for (auto const & event : executingEvents)
event.action(); event.action();
} }
bool BattleInterface::getAnimationCondition( EAnimationEvents event) void BattleInterface::onAnimationsStarted()
{ {
size_t index = static_cast<size_t>(event); ongoingAnimationsState.setn(true);
return animationEvents[index].get();
} }
void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state) void BattleInterface::onAnimationsFinished()
{
ongoingAnimationsState.setn(false);
}
void BattleInterface::waitForAnimations()
{ {
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim); auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
size_t index = static_cast<size_t>(event); ongoingAnimationsState.waitUntil(false);
animationEvents[index].waitUntil(state);
} }
void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action) bool BattleInterface::hasAnimations()
{ {
awaitingEvents.push_back({action, event, state}); return ongoingAnimationsState.get();
}
void BattleInterface::checkForAnimations()
{
assert(!hasAnimations());
if(hasAnimations())
logGlobal->error("Unexpected animations state: expected all animations to be over, but some are still ongoing!");
waitForAnimations();
}
void BattleInterface::addToAnimationStage(EAnimationEvents event, const AwaitingAnimationAction & action)
{
awaitingEvents.push_back({action, event});
} }
void BattleInterface::setBattleQueueVisibility(bool visible) void BattleInterface::setBattleQueueVisibility(bool visible)

View File

@ -94,11 +94,10 @@ class BattleInterface
struct AwaitingAnimationEvents { struct AwaitingAnimationEvents {
AwaitingAnimationAction action; AwaitingAnimationAction action;
EAnimationEvents event; EAnimationEvents event;
bool eventState;
}; };
/// Conditional variables that are set depending on ongoing animations on the battlefield /// Conditional variables that are set depending on ongoing animations on the battlefield
std::array< CondSh<bool>, static_cast<size_t>(EAnimationEvents::COUNT)> animationEvents; CondSh<bool> ongoingAnimationsState;
/// List of events that are waiting to be triggered /// List of events that are waiting to be triggered
std::vector<AwaitingAnimationEvents> awaitingEvents; std::vector<AwaitingAnimationEvents> awaitingEvents;
@ -112,6 +111,9 @@ class BattleInterface
/// defender interface, not null if attacker is human in our vcmiclient /// defender interface, not null if attacker is human in our vcmiclient
std::shared_ptr<CPlayerInterface> defenderInt; std::shared_ptr<CPlayerInterface> defenderInt;
/// ID of channel on which battle opening sound is playing, or -1 if none
int battleIntroSoundChannel;
void playIntroSoundAndUnlockInterface(); void playIntroSoundAndUnlockInterface();
void onIntroSoundPlayed(); void onIntroSoundPlayed();
public: public:
@ -119,9 +121,6 @@ public:
const CCreatureSet *army1; const CCreatureSet *army1;
const CCreatureSet *army2; const CCreatureSet *army2;
/// ID of channel on which battle opening sound is playing, or -1 if none
int battleIntroSoundChannel;
std::shared_ptr<BattleWindow> windowObject; std::shared_ptr<BattleWindow> windowObject;
std::shared_ptr<BattleConsole> console; std::shared_ptr<BattleConsole> console;
@ -146,9 +145,10 @@ public:
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
bool makingTurn() const; bool openingPlaying();
void openingEnd();
int moveSoundHander; // sound handler used when moving a unit bool makingTurn() const;
BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr); BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
~BattleInterface(); ~BattleInterface();
@ -178,17 +178,14 @@ public:
void setBattleQueueVisibility(bool visible); void setBattleQueueVisibility(bool visible);
/// sets condition to targeted state and executes any awaiting actions void executeStagedAnimations();
void setAnimationCondition( EAnimationEvents event, bool state); void executeAnimationStage( EAnimationEvents event);
void onAnimationsStarted();
/// returns current state of condition void onAnimationsFinished();
bool getAnimationCondition( EAnimationEvents event); void waitForAnimations();
bool hasAnimations();
/// locks execution until selected condition reached targeted state void checkForAnimations();
void waitForAnimationCondition( EAnimationEvents event, bool state); void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action);
/// adds action that will be executed one selected condition reached targeted state
void executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action);
//call-ins //call-ins
void startAction(const BattleAction* action); void startAction(const BattleAction* action);

View File

@ -100,7 +100,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
owner.stacksController->addNewAnim(new EffectAnimation(owner, spellObstacle->appearAnimation, whereTo, oi->pos)); owner.stacksController->addNewAnim(new EffectAnimation(owner, spellObstacle->appearAnimation, whereTo, oi->pos));
//so when multiple obstacles are added, they show up one after another //so when multiple obstacles are added, they show up one after another
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.waitForAnimations();
loadObstacleImage(*spellObstacle); loadObstacleImage(*spellObstacle);
} }

View File

@ -330,7 +330,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
if (ca.attacker != -1) if (ca.attacker != -1)
{ {
@ -352,8 +352,7 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions)); owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));
} }
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.waitForAnimations();
owner.setAnimationCondition(EAnimationEvents::HIT, false);
for (auto attackInfo : ca.attackedParts) for (auto attackInfo : ca.attackedParts)
{ {

View File

@ -160,7 +160,7 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
void BattleStacksController::stackReset(const CStack * stack) void BattleStacksController::stackReset(const CStack * stack)
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
//reset orientation? //reset orientation?
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; //stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
@ -177,7 +177,7 @@ void BattleStacksController::stackReset(const CStack * stack)
if(stack->alive() && animation->isDeadOrDying()) if(stack->alive() && animation->isDeadOrDying())
{ {
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() owner.addToAnimationStage(EAnimationEvents::HIT, [=]()
{ {
addNewAnim(new ResurrectionAnimation(owner, stack)); addNewAnim(new ResurrectionAnimation(owner, stack));
}); });
@ -221,7 +221,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant)
auto shifterFade = ColorFilter::genAlphaShifter(0); auto shifterFade = ColorFilter::genAlphaShifter(0);
setStackColorFilter(shifterFade, stack, nullptr, true); setStackColorFilter(shifterFade, stack, nullptr, true);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() owner.addToAnimationStage(EAnimationEvents::HIT, [=]()
{ {
addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr)); addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr));
if (stack->isClone()) if (stack->isClone())
@ -329,13 +329,6 @@ void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect); fullFilter = ColorFilter::genCombined(fullFilter, filter.effect);
} }
bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
if (stackHasProjectile)
stackAnimation[stack->ID]->pause();
else
stackAnimation[stack->ID]->play();
stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000); stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
} }
@ -372,15 +365,19 @@ void BattleStacksController::updateBattleAnimations()
vstd::erase(currentAnimations, nullptr); vstd::erase(currentAnimations, nullptr);
if (hadAnimations && currentAnimations.empty()) if (hadAnimations && currentAnimations.empty())
owner.setAnimationCondition(EAnimationEvents::ACTION, false); {
owner.executeStagedAnimations();
if (currentAnimations.empty())
owner.onAnimationsFinished();
}
initializeBattleAnimations(); initializeBattleAnimations();
} }
void BattleStacksController::addNewAnim(BattleAnimation *anim) void BattleStacksController::addNewAnim(BattleAnimation *anim)
{ {
owner.onAnimationsStarted();
currentAnimations.push_back(anim); currentAnimations.push_back(anim);
owner.setAnimationCondition(EAnimationEvents::ACTION, true);
} }
void BattleStacksController::stackRemoved(uint32_t stackID) void BattleStacksController::stackRemoved(uint32_t stackID)
@ -398,7 +395,7 @@ void BattleStacksController::stackRemoved(uint32_t stackID)
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos) void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ owner.addToAnimationStage(EAnimationEvents::HIT, [=](){
// remove any potentially erased petrification effect // remove any potentially erased petrification effect
removeExpiredColorFilters(); removeExpiredColorFilters();
}); });
@ -423,7 +420,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
// if (needsReverse && !attackedInfo.defender->isFrozen()) // if (needsReverse && !attackedInfo.defender->isFrozen())
if (needsReverse && stackAnimation[attackedInfo.defender->ID]->getType() != ECreatureAnimType::FROZEN) if (needsReverse && stackAnimation[attackedInfo.defender->ID]->getType() != ECreatureAnimType::FROZEN)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [=]()
{ {
addNewAnim(new ReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition())); addNewAnim(new ReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition()));
}); });
@ -437,7 +434,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT; EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT;
owner.executeOnAnimationCondition(usedEvent, true, [=]() owner.addToAnimationStage(usedEvent, [=]()
{ {
if (useDeathAnim) if (useDeathAnim)
addNewAnim(new DeathAnimation(owner, attackedInfo.defender, attackedInfo.indirectAttack)); addNewAnim(new DeathAnimation(owner, attackedInfo.defender, attackedInfo.indirectAttack));
@ -465,7 +462,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
{ {
if (attackedInfo.rebirth) if (attackedInfo.rebirth)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition()); owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition());
addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender)); addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender));
}); });
@ -473,25 +470,26 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
if (attackedInfo.killed && attackedInfo.defender->summoned) if (attackedInfo.killed && attackedInfo.defender->summoned)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr)); addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr));
stackRemoved(attackedInfo.defender->ID); stackRemoved(attackedInfo.defender->ID);
}); });
} }
} }
executeAttackAnimations(); owner.executeStagedAnimations();
owner.waitForAnimations();
} }
void BattleStacksController::stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance) void BattleStacksController::stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{ {
assert(destHex.size() > 0); assert(destHex.size() > 0);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ owner.addToAnimationStage(EAnimationEvents::HIT, [=](){
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeOut", nullptr) ); addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeOut", nullptr) );
}); });
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack)); stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) ); addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) );
}); });
@ -502,42 +500,36 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector<Ba
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance) void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{ {
assert(destHex.size() > 0); assert(destHex.size() > 0);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
bool stackTeleports = stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1));
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
auto enqueMoveEnd = [&](){
addNewAnim(new MovementEndAnimation(owner, stack, destHex.back()));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, [&](){
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
});
};
auto enqueMove = [&](){
if (!stackTeleports)
{
addNewAnim(new MovementAnimation(owner, stack, destHex, distance));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveEnd);
}
else
enqueMoveEnd();
};
auto enqueMoveStart = [&](){
addNewAnim(new MovementStartAnimation(owner, stack));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMove);
};
if(shouldRotate(stack, stack->getPosition(), destHex[0])) if(shouldRotate(stack, stack->getPosition(), destHex[0]))
{ {
addNewAnim(new ReverseAnimation(owner, stack, stack->getPosition())); owner.addToAnimationStage(EAnimationEvents::ROTATE, [&]()
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveStart); {
addNewAnim(new ReverseAnimation(owner, stack, stack->getPosition()));
});
} }
else
enqueMoveStart();
owner.waitForAnimationCondition(EAnimationEvents::MOVEMENT, false); owner.addToAnimationStage(EAnimationEvents::MOVE_START, [&]()
{
addNewAnim(new MovementStartAnimation(owner, stack));
});
if (!stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
{
owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [&]()
{
addNewAnim(new MovementAnimation(owner, stack, destHex, distance));
});
}
owner.addToAnimationStage(EAnimationEvents::MOVE_END, [&]()
{
addNewAnim(new MovementEndAnimation(owner, stack, destHex.back()));
});
owner.executeStagedAnimations();
owner.waitForAnimations();
} }
bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, const CStack * defender) bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, const CStack * defender)
@ -554,7 +546,7 @@ bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, co
void BattleStacksController::stackAttacking( const StackAttackInfo & info ) void BattleStacksController::stackAttacking( const StackAttackInfo & info )
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
auto attacker = info.attacker; auto attacker = info.attacker;
auto defender = info.defender; auto defender = info.defender;
@ -574,7 +566,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if (needsReverse) if (needsReverse)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [=]()
{ {
addNewAnim(new ReverseAnimation(owner, attacker, attacker->getPosition())); addNewAnim(new ReverseAnimation(owner, attacker, attacker->getPosition()));
}); });
@ -582,7 +574,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if(info.lucky) if(info.lucky)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(-45)); owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition()); owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition());
}); });
@ -590,7 +582,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if(info.unlucky) if(info.unlucky)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(-44)); owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition()); owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition());
}); });
@ -598,20 +590,20 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if(info.deathBlow) if(info.deathBlow)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(365)); owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition()); owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition());
}); });
for(auto elem : info.secondaryDefender) for(auto elem : info.secondaryDefender)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition()); owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition());
}); });
} }
} }
owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]() owner.addToAnimationStage(EAnimationEvents::ATTACK, [=]()
{ {
if (info.indirectAttack) if (info.indirectAttack)
{ {
@ -625,7 +617,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if (info.spellEffect != SpellID::NONE) if (info.spellEffect != SpellID::NONE)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() owner.addToAnimationStage(EAnimationEvents::HIT, [=]()
{ {
owner.displaySpellHit(spellEffect.toSpell(), tile); owner.displaySpellHit(spellEffect.toSpell(), tile);
}); });
@ -633,7 +625,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
if (info.lifeDrain) if (info.lifeDrain)
{ {
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]() owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]()
{ {
owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition()); owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition());
}); });
@ -642,32 +634,6 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
//return, animation playback will be handled by stacksAreAttacked //return, animation playback will be handled by stacksAreAttacked
} }
void BattleStacksController::executeAttackAnimations()
{
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, false);
// Note that HIT event can also be emitted by attack animation
owner.setAnimationCondition(EAnimationEvents::HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::HIT, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, false);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const
{ {
Point begPosition = getStackPositionAtHex(oldPos,stack); Point begPosition = getStackPositionAtHex(oldPos,stack);
@ -683,7 +649,7 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex
void BattleStacksController::endAction(const BattleAction* action) void BattleStacksController::endAction(const BattleAction* action)
{ {
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); owner.checkForAnimations();
//check if we should reverse stacks //check if we should reverse stacks
TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY); TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
@ -697,14 +663,8 @@ void BattleStacksController::endAction(const BattleAction* action)
addNewAnim(new ReverseAnimation(owner, s, s->getPosition())); addNewAnim(new ReverseAnimation(owner, s, s->getPosition()));
} }
} }
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.executeStagedAnimations();
owner.waitForAnimations();
//Ensure that all animation flags were reset
assert(owner.getAnimationCondition(EAnimationEvents::OPENING) == false);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
assert(owner.getAnimationCondition(EAnimationEvents::MOVEMENT) == false);
assert(owner.getAnimationCondition(EAnimationEvents::ATTACK) == false);
assert(owner.getAnimationCondition(EAnimationEvents::HIT) == false);
owner.windowObject->blockUI(activeStack == nullptr); owner.windowObject->blockUI(activeStack == nullptr);
removeExpiredColorFilters(); removeExpiredColorFilters();
@ -718,7 +678,8 @@ void BattleStacksController::startAction(const BattleAction* action)
void BattleStacksController::stackActivated(const CStack *stack) void BattleStacksController::stackActivated(const CStack *stack)
{ {
stackToActivate = stack; stackToActivate = stack;
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); owner.waitForAnimations();
logAnim->debug("Activating next stack");
owner.activateStack(); owner.activateStack();
} }
@ -850,7 +811,6 @@ void BattleStacksController::updateHoveredStacks()
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder()); stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen()) if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON); stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON);
} }
mouseHoveredStacks = newStacks; mouseHoveredStacks = newStacks;
@ -862,7 +822,7 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
if (!activeStack) if (!activeStack)
return {}; return {};
if(owner.getAnimationCondition(EAnimationEvents::ACTION) == true) if(owner.hasAnimations())
return {}; return {};
auto hoveredQueueUnitId = owner.windowObject->getQueueHoveredUnitId(); auto hoveredQueueUnitId = owner.windowObject->getQueueHoveredUnitId();

View File

@ -88,7 +88,6 @@ class BattleStacksController
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack); std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
void executeAttackAnimations();
void removeExpiredColorFilters(); void removeExpiredColorFilters();
void initializeBattleAnimations(); void initializeBattleAnimations();

View File

@ -179,6 +179,12 @@ void BattleWindow::deactivate()
void BattleWindow::keyPressed(const SDL_Keycode & key) void BattleWindow::keyPressed(const SDL_Keycode & key)
{ {
if (owner.openingPlaying())
{
owner.openingEnd();
return;
}
if(key == SDLK_q) if(key == SDLK_q)
{ {
toggleQueueVisibility(); toggleQueueVisibility();
@ -189,10 +195,7 @@ void BattleWindow::keyPressed(const SDL_Keycode & key)
} }
else if(key == SDLK_ESCAPE) else if(key == SDLK_ESCAPE)
{ {
if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true) owner.actionsController->endCastingSpell();
CCS->soundh->stopSound(owner.battleIntroSoundChannel);
else
owner.actionsController->endCastingSpell();
} }
} }

View File

@ -183,7 +183,7 @@ void CreatureAnimation::setType(ECreatureAnimType type)
currentFrame = 0; currentFrame = 0;
once = false; once = false;
play(); speed = speedController(this, type);
} }
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller) CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
@ -191,6 +191,7 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
speed(0.1f), speed(0.1f),
shadowAlpha(128), shadowAlpha(128),
currentFrame(0), currentFrame(0),
animationEnd(-1),
elapsedTime(0), elapsedTime(0),
type(ECreatureAnimType::HOLDING), type(ECreatureAnimType::HOLDING),
border(CSDL_Ext::makeColor(0, 0, 0, 0)), border(CSDL_Ext::makeColor(0, 0, 0, 0)),
@ -248,7 +249,7 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
reverse->verticalFlip(); reverse->verticalFlip();
play(); speed = speedController(this, type);
} }
void CreatureAnimation::endAnimation() void CreatureAnimation::endAnimation()
@ -263,6 +264,9 @@ bool CreatureAnimation::incrementFrame(float timePassed)
{ {
elapsedTime += timePassed; elapsedTime += timePassed;
currentFrame += timePassed * speed; currentFrame += timePassed * speed;
if (animationEnd >= 0)
currentFrame = std::min(currentFrame, animationEnd);
const auto framesNumber = framesInGroup(type); const auto framesNumber = framesInGroup(type);
if(framesNumber <= 0) if(framesNumber <= 0)
@ -375,6 +379,11 @@ void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter,
} }
} }
void CreatureAnimation::playUntil(size_t frameIndex)
{
animationEnd = frameIndex;
}
int CreatureAnimation::framesInGroup(ECreatureAnimType group) const int CreatureAnimation::framesInGroup(ECreatureAnimType group) const
{ {
return static_cast<int>(forward->size(size_t(group))); return static_cast<int>(forward->size(size_t(group)));
@ -421,14 +430,3 @@ bool CreatureAnimation::isShooting() const
|| getType() == ECreatureAnimType::SHOOT_FRONT || getType() == ECreatureAnimType::SHOOT_FRONT
|| getType() == ECreatureAnimType::SHOOT_DOWN; || getType() == ECreatureAnimType::SHOOT_DOWN;
} }
void CreatureAnimation::pause()
{
speed = 0;
}
void CreatureAnimation::play()
{
//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
speed = speedController(this, type);
}

View File

@ -88,6 +88,7 @@ private:
/// currently displayed frame. Float to allow H3-style animations where frames /// currently displayed frame. Float to allow H3-style animations where frames
/// don't display for integer number of frames /// don't display for integer number of frames
float currentFrame; float currentFrame;
float animationEnd;
/// cumulative, real-time duration of animation. Used for effects like selection border /// cumulative, real-time duration of animation. Used for effects like selection border
float elapsedTime; float elapsedTime;
@ -146,8 +147,7 @@ public:
/// returns number of frames in selected animation type /// returns number of frames in selected animation type
int framesInGroup(ECreatureAnimType group) const; int framesInGroup(ECreatureAnimType group) const;
void pause(); void playUntil(size_t frameIndex);
void play();
/// helpers to classify current type of animation /// helpers to classify current type of animation
bool isDead() const; bool isDead() const;