diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index 8f241bf1a..0903a347d 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -81,6 +81,8 @@ "vcmi.battleOptions.animationsSpeed6.help": "Sets animation speed to instantaneous", "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.battleWindow.pressKeyToSkipIntro" : "Press any key to skip battle intro", "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).", diff --git a/Mods/vcmi/config/vcmi/ukrainian.json b/Mods/vcmi/config/vcmi/ukrainian.json index d889ef609..13d028381 100644 --- a/Mods/vcmi/config/vcmi/ukrainian.json +++ b/Mods/vcmi/config/vcmi/ukrainian.json @@ -82,6 +82,8 @@ "vcmi.battleOptions.animationsSpeed6.help": "Встановити миттєву швидкість анімації", "vcmi.battleOptions.skipBattleIntroMusic.hover": "Пропускати вступну музику", "vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.", + + "vcmi.battleWindow.pressKeyToSkipIntro" : "Натисніть будь-яку клавішу, щоб розпочати бій", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Показувати доступних істот", "vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Показувати доступних істот}\n\n Показує істот, яких можна придбати, замість їхнього приросту у зведенні по місту (нижній лівий кут).", diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index ddbeb4b27..4995a78c9 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -677,6 +677,13 @@ PossiblePlayerBattleAction BattleActionsController::selectAction(BattleHex targe 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) return; diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index fac752acc..30f1eb2b7 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -306,10 +306,8 @@ void MeleeAttackAnimation::nextFrame() size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(getGroup()); if ( currentFrame * 2 >= totalFrames ) - { - if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) - owner.setAnimationCondition(EAnimationEvents::HIT, true); - } + owner.executeAnimationStage(EAnimationEvents::HIT); + AttackAnimation::nextFrame(); } @@ -356,9 +354,9 @@ bool MovementAnimation::init() 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); @@ -418,11 +416,8 @@ MovementAnimation::~MovementAnimation() { assert(stack); - if(owner.moveSoundHander != -1) - { - CCS->soundh->stopSound(owner.moveSoundHander); - owner.moveSoundHander = -1; - } + if(moveSoundHander != -1) + CCS->soundh->stopSound(moveSoundHander); } MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector _destTiles, int _distance) @@ -432,6 +427,7 @@ MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stac begX(0), begY(0), distanceX(0), distanceY(0), progressPerSecond(0.0), + moveSoundHander(-1), progress(0.0) { logAnim->debug("Created MovementAnimation for %s", stack->getName()); @@ -709,12 +705,17 @@ void RangedAttackAnimation::nextFrame() if (projectileEmitted) { if (!owner.projectilesController->hasActiveProjectile(attackingStack, false)) - { - if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) - owner.setAnimationCondition(EAnimationEvents::HIT, true); - } + owner.executeAnimationStage(EAnimationEvents::HIT); + } + bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true); + + if (!projectileEmitted || stackHasProjectile) + stackAnimation(attackingStack)->playUntil(getAttackClimaxFrame()); + else + stackAnimation(attackingStack)->playUntil(static_cast(-1)); + AttackAnimation::nextFrame(); if (!projectileEmitted) @@ -1052,7 +1053,6 @@ bool HeroCastAnimation::init() hero->setPhase(EHeroAnimType::CAST_SPELL); hero->onPhaseFinished([&](){ - assert(owner.getAnimationCondition(EAnimationEvents::HIT) == true); delete this; }); @@ -1093,8 +1093,7 @@ void HeroCastAnimation::emitProjectile() void HeroCastAnimation::emitAnimationEvent() { - if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) - owner.setAnimationCondition(EAnimationEvents::HIT, true); + owner.executeAnimationStage(EAnimationEvents::HIT); } void HeroCastAnimation::nextFrame() diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index 89309ffe9..230f15709 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -141,6 +141,8 @@ protected: class MovementAnimation : public StackMoveAnimation { private: + int moveSoundHander; // sound handler used when moving a unit + std::vector destTiles; //full path, includes already passed hexes ui32 curentMoveIndex; // index of nextHex in destTiles diff --git a/client/battle/BattleConstants.h b/client/battle/BattleConstants.h index b815132a1..fb6bb4baa 100644 --- a/client/battle/BattleConstants.h +++ b/client/battle/BattleConstants.h @@ -30,14 +30,22 @@ enum class EBattleEffect INVALID = -1, }; -enum class EAnimationEvents { - OPENING = 0, // battle opening sound is playing - ACTION = 1, // there are any ongoing animations - MOVEMENT = 2, // stacks are moving or turning around - BEFORE_HIT = 3, // effects played before all attack/defence/hit animations - ATTACK = 4, // attack and defence animations are playing - HIT = 5, // hit & death animations are playing - AFTER_HIT = 6, // after all hit & death animations are over +enum class EAnimationEvents +{ + // any action + ROTATE, // stacks rotate before action + + // movement action + MOVE_START, // stack starts movement + 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 }; diff --git a/client/battle/BattleEffectsController.cpp b/client/battle/BattleEffectsController.cpp index 85d750ceb..28a83a72b 100644 --- a/client/battle/BattleEffectsController.cpp +++ b/client/battle/BattleEffectsController.cpp @@ -56,7 +56,7 @@ void BattleEffectsController::displayEffect(EBattleEffect effect, std::string so void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID); if(!stack) @@ -90,12 +90,12 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt default: return; } - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.waitForAnimations(); } void BattleEffectsController::startAction(const BattleAction* action) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber); @@ -110,7 +110,7 @@ void BattleEffectsController::startAction(const BattleAction* action) break; } - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.waitForAnimations(); } void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer) diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index db7daded1..ac63b6ee8 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -54,11 +54,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet * , attackerInt(att) , defenderInt(defen) , curInt(att) - , moveSoundHander(-1) { - for ( auto & event : animationEvents) - event.setn(false); - if(spectatorInt) { curInt = spectatorInt; @@ -96,7 +92,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet * obstacleController.reset(new BattleObstacleController(*this)); adventureInt->onAudioPaused(); - setAnimationCondition(EAnimationEvents::OPENING, true); + ongoingAnimationsState.set(true); GH.pushInt(windowObject); windowObject->blockUI(true); @@ -107,12 +103,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet * void BattleInterface::playIntroSoundAndUnlockInterface() { - if(settings["gameTweaks"]["skipBattleIntroMusic"].Bool()) - { - onIntroSoundPlayed(); - return; - } - auto onIntroPlayed = [this]() { if(LOCPLINT->battleInt) @@ -124,19 +114,22 @@ void BattleInterface::playIntroSoundAndUnlockInterface() battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds); if (battleIntroSoundChannel != -1) + { CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed); + + if (settings["gameTweaks"]["skipBattleIntroMusic"].Bool()) + openingEnd(); + } else onIntroSoundPlayed(); } void BattleInterface::onIntroSoundPlayed() { - setAnimationCondition(EAnimationEvents::OPENING, false); + if (openingPlaying()) + openingEnd(); + CCS->musich->playMusicFromSet("battle", true, true); - if(tacticsMode) - tacticNextStack(nullptr); - activateStack(); - battleIntroSoundChannel = -1; } BattleInterface::~BattleInterface() @@ -147,10 +140,7 @@ BattleInterface::~BattleInterface() if (adventureInt) adventureInt->onAudioResumed(); - // may happen if user decided to close game while in battle - if (getAnimationCondition(EAnimationEvents::ACTION) == true) - logGlobal->error("Shutting down BattleInterface during animation playback!"); - setAnimationCondition(EAnimationEvents::ACTION, false); + onAnimationsFinished(); } void BattleInterface::redrawBattlefield() @@ -217,7 +207,7 @@ void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo ) void BattleInterface::newRoundFirst( int round ) { - waitForAnimationCondition(EAnimationEvents::OPENING, false); + waitForAnimations(); } void BattleInterface::newRound(int number) @@ -299,8 +289,7 @@ void BattleInterface::gateStateChanged(const EGateState state) void BattleInterface::battleFinished(const BattleResult& br) { - assert(getAnimationCondition(EAnimationEvents::ACTION) == false); - waitForAnimationCondition(EAnimationEvents::ACTION, false); + checkForAnimations(); stacksController->setActiveStack(nullptr); CCS->curh->set(Cursor::Map::POINTER); @@ -337,7 +326,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) EAnimationEvents::HIT: EAnimationEvents::BEFORE_HIT;//FIXME: recheck whether this should be on projectile spawning - executeOnAnimationCondition(group, true, [=]() { + addToAnimationStage(group, [=]() { CCS->soundh->playSound(castSoundPath); }); } @@ -348,7 +337,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) if(casterStack != nullptr ) { - executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() + addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() { stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell)); displaySpellCast(spell, casterStack->getPosition()); @@ -359,14 +348,14 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) auto hero = sc->side ? defendingHero : attackingHero; assert(hero); - executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() + addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() { stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell)); }); } } - executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + addToAnimationStage(EAnimationEvents::HIT, [=](){ displaySpellHit(spell, targetedTile); }); @@ -377,7 +366,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) assert(stack); if(stack) { - executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + addToAnimationStage(EAnimationEvents::HIT, [=](){ displaySpellEffect(spell, stack->getPosition()); }); } @@ -387,14 +376,14 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) { auto stack = curInt->cb->battleGetStackByID(elem, false); assert(stack); - executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + addToAnimationStage(EAnimationEvents::HIT, [=](){ effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition()); }); } if (!sc->resistedCres.empty()) { - executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + addToAnimationStage(EAnimationEvents::HIT, [=](){ CCS->soundh->playSound("MAGICRES"); }); } @@ -403,7 +392,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) { auto stack = curInt->cb->battleGetStackByID(elem, false); assert(stack); - executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + addToAnimationStage(EAnimationEvents::HIT, [=](){ effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition()); }); } @@ -415,7 +404,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) Point rightHero = Point(755, 30); 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_B.DEF" : "SP07_A.DEF", rightHero)); }); @@ -535,6 +524,24 @@ void BattleInterface::activateStack() 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 { return stacksController->getActiveStack() != nullptr; @@ -611,8 +618,7 @@ void BattleInterface::tacticNextStack(const CStack * current) current = stacksController->getActiveStack(); //no switching stacks when the current one is moving - assert(getAnimationCondition(EAnimationEvents::ACTION) == false); - waitForAnimationCondition(EAnimationEvents::ACTION, false); + checkForAnimations(); TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE); vstd::erase_if (stacksOfMine, &immobile); @@ -698,18 +704,24 @@ void BattleInterface::castThisSpell(SpellID spellID) actionsController->castThisSpell(spellID); } -void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state) +void BattleInterface::executeStagedAnimations() { - logAnim->debug("setAnimationCondition: %d -> %s", static_cast(event), state ? "ON" : "OFF"); + EAnimationEvents earliestStage = EAnimationEvents::COUNT; - size_t index = static_cast(event); - animationEvents[index].setn(state); + for(const auto & event : awaitingEvents) + earliestStage = std::min(earliestStage, event.event); + if(earliestStage != EAnimationEvents::COUNT) + executeAnimationStage(earliestStage); +} + +void BattleInterface::executeAnimationStage(EAnimationEvents event) +{ 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); it = awaitingEvents.erase(it); @@ -717,27 +729,43 @@ void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state) else ++it; } - - for (auto const & event : executingEvents) + for(const auto & event : executingEvents) event.action(); } -bool BattleInterface::getAnimationCondition( EAnimationEvents event) +void BattleInterface::onAnimationsStarted() { - size_t index = static_cast(event); - return animationEvents[index].get(); + ongoingAnimationsState.setn(true); } -void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state) +void BattleInterface::onAnimationsFinished() +{ + ongoingAnimationsState.setn(false); +} + +void BattleInterface::waitForAnimations() { auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim); - size_t index = static_cast(event); - animationEvents[index].waitUntil(state); + ongoingAnimationsState.waitUntil(false); } -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) diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index fbcaa8b6a..e2d4db1ff 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -94,11 +94,10 @@ class BattleInterface struct AwaitingAnimationEvents { AwaitingAnimationAction action; EAnimationEvents event; - bool eventState; }; /// Conditional variables that are set depending on ongoing animations on the battlefield - std::array< CondSh, static_cast(EAnimationEvents::COUNT)> animationEvents; + CondSh ongoingAnimationsState; /// List of events that are waiting to be triggered std::vector awaitingEvents; @@ -112,6 +111,9 @@ class BattleInterface /// defender interface, not null if attacker is human in our vcmiclient std::shared_ptr defenderInt; + /// ID of channel on which battle opening sound is playing, or -1 if none + int battleIntroSoundChannel; + void playIntroSoundAndUnlockInterface(); void onIntroSoundPlayed(); public: @@ -119,9 +121,6 @@ public: const CCreatureSet *army1; const CCreatureSet *army2; - /// ID of channel on which battle opening sound is playing, or -1 if none - int battleIntroSoundChannel; - std::shared_ptr windowObject; std::shared_ptr console; @@ -146,9 +145,10 @@ public: static CondSh 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 att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); ~BattleInterface(); @@ -178,17 +178,14 @@ public: void setBattleQueueVisibility(bool visible); - /// sets condition to targeted state and executes any awaiting actions - void setAnimationCondition( EAnimationEvents event, bool state); - - /// returns current state of condition - bool getAnimationCondition( EAnimationEvents event); - - /// locks execution until selected condition reached targeted state - void waitForAnimationCondition( EAnimationEvents event, bool state); - - /// adds action that will be executed one selected condition reached targeted state - void executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action); + void executeStagedAnimations(); + void executeAnimationStage( EAnimationEvents event); + void onAnimationsStarted(); + void onAnimationsFinished(); + void waitForAnimations(); + bool hasAnimations(); + void checkForAnimations(); + void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); //call-ins void startAction(const BattleAction* action); diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index 5e3eb2cd8..ffa5c0429 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -100,7 +100,7 @@ void BattleObstacleController::obstaclePlaced(const std::vectoraddNewAnim(new EffectAnimation(owner, spellObstacle->appearAnimation, whereTo, oi->pos)); //so when multiple obstacles are added, they show up one after another - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.waitForAnimations(); loadObstacleImage(*spellObstacle); } diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index ed106cc87..7493af942 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -330,7 +330,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); if (ca.attacker != -1) { @@ -352,8 +352,7 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions)); } - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); - owner.setAnimationCondition(EAnimationEvents::HIT, false); + owner.waitForAnimations(); for (auto attackInfo : ca.attackedParts) { diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 0caada6ed..561c1874c 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -160,7 +160,7 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer) void BattleStacksController::stackReset(const CStack * stack) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); //reset orientation? //stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; @@ -177,7 +177,7 @@ void BattleStacksController::stackReset(const CStack * stack) if(stack->alive() && animation->isDeadOrDying()) { - owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() + owner.addToAnimationStage(EAnimationEvents::HIT, [=]() { addNewAnim(new ResurrectionAnimation(owner, stack)); }); @@ -221,7 +221,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant) auto shifterFade = ColorFilter::genAlphaShifter(0); setStackColorFilter(shifterFade, stack, nullptr, true); - owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() + owner.addToAnimationStage(EAnimationEvents::HIT, [=]() { addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr)); if (stack->isClone()) @@ -329,13 +329,6 @@ void BattleStacksController::showStack(Canvas & canvas, const CStack * stack) 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]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000); } @@ -372,15 +365,19 @@ void BattleStacksController::updateBattleAnimations() vstd::erase(currentAnimations, nullptr); if (hadAnimations && currentAnimations.empty()) - owner.setAnimationCondition(EAnimationEvents::ACTION, false); + { + owner.executeStagedAnimations(); + if (currentAnimations.empty()) + owner.onAnimationsFinished(); + } initializeBattleAnimations(); } void BattleStacksController::addNewAnim(BattleAnimation *anim) { + owner.onAnimationsStarted(); currentAnimations.push_back(anim); - owner.setAnimationCondition(EAnimationEvents::ACTION, true); } void BattleStacksController::stackRemoved(uint32_t stackID) @@ -398,7 +395,7 @@ void BattleStacksController::stackRemoved(uint32_t stackID) void BattleStacksController::stacksAreAttacked(std::vector attackedInfos) { - owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){ + owner.addToAnimationStage(EAnimationEvents::HIT, [=](){ // remove any potentially erased petrification effect removeExpiredColorFilters(); }); @@ -423,7 +420,7 @@ void BattleStacksController::stacksAreAttacked(std::vector at // if (needsReverse && !attackedInfo.defender->isFrozen()) 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())); }); @@ -437,7 +434,7 @@ void BattleStacksController::stacksAreAttacked(std::vector at EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT; - owner.executeOnAnimationCondition(usedEvent, true, [=]() + owner.addToAnimationStage(usedEvent, [=]() { if (useDeathAnim) addNewAnim(new DeathAnimation(owner, attackedInfo.defender, attackedInfo.indirectAttack)); @@ -465,7 +462,7 @@ void BattleStacksController::stacksAreAttacked(std::vector at { if (attackedInfo.rebirth) { - owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ + owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){ owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition()); addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender)); }); @@ -473,25 +470,26 @@ void BattleStacksController::stacksAreAttacked(std::vector at 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)); stackRemoved(attackedInfo.defender->ID); }); } } - executeAttackAnimations(); + owner.executeStagedAnimations(); + owner.waitForAnimations(); } void BattleStacksController::stackTeleported(const CStack *stack, std::vector destHex, int distance) { 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) ); }); - owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){ + owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){ stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack)); addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) ); }); @@ -502,42 +500,36 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector destHex, int distance) { assert(destHex.size() > 0); - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); - - 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); - }; + owner.checkForAnimations(); if(shouldRotate(stack, stack->getPosition(), destHex[0])) { - addNewAnim(new ReverseAnimation(owner, stack, stack->getPosition())); - owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveStart); + owner.addToAnimationStage(EAnimationEvents::ROTATE, [&]() + { + 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) @@ -554,7 +546,7 @@ bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, co void BattleStacksController::stackAttacking( const StackAttackInfo & info ) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); auto attacker = info.attacker; auto defender = info.defender; @@ -574,7 +566,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if (needsReverse) { - owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() + owner.addToAnimationStage(EAnimationEvents::MOVEMENT, [=]() { addNewAnim(new ReverseAnimation(owner, attacker, attacker->getPosition())); }); @@ -582,7 +574,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if(info.lucky) { - owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { + owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() { owner.appendBattleLog(info.attacker->formatGeneralMessage(-45)); owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition()); }); @@ -590,7 +582,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if(info.unlucky) { - owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { + owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() { owner.appendBattleLog(info.attacker->formatGeneralMessage(-44)); owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition()); }); @@ -598,20 +590,20 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if(info.deathBlow) { - owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() { + owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() { owner.appendBattleLog(info.attacker->formatGeneralMessage(365)); owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition()); }); 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.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]() + owner.addToAnimationStage(EAnimationEvents::ATTACK, [=]() { if (info.indirectAttack) { @@ -625,7 +617,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if (info.spellEffect != SpellID::NONE) { - owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]() + owner.addToAnimationStage(EAnimationEvents::HIT, [=]() { owner.displaySpellHit(spellEffect.toSpell(), tile); }); @@ -633,7 +625,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info ) if (info.lifeDrain) { - owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]() + owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]() { 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 } -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 { Point begPosition = getStackPositionAtHex(oldPos,stack); @@ -683,7 +649,7 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex void BattleStacksController::endAction(const BattleAction* action) { - assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.checkForAnimations(); //check if we should reverse stacks 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())); } } - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); - - //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.executeStagedAnimations(); + owner.waitForAnimations(); owner.windowObject->blockUI(activeStack == nullptr); removeExpiredColorFilters(); @@ -718,7 +678,8 @@ void BattleStacksController::startAction(const BattleAction* action) void BattleStacksController::stackActivated(const CStack *stack) { stackToActivate = stack; - owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.waitForAnimations(); + logAnim->debug("Activating next stack"); owner.activateStack(); } @@ -850,7 +811,6 @@ void BattleStacksController::updateHoveredStacks() stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder()); if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen()) stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON); - } mouseHoveredStacks = newStacks; @@ -862,7 +822,7 @@ std::vector BattleStacksController::selectHoveredStacks() if (!activeStack) return {}; - if(owner.getAnimationCondition(EAnimationEvents::ACTION) == true) + if(owner.hasAnimations()) return {}; auto hoveredQueueUnitId = owner.windowObject->getQueueHoveredUnitId(); diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index d199d43e4..3cce90334 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -88,7 +88,6 @@ class BattleStacksController std::shared_ptr getStackAmountBox(const CStack * stack); - void executeAttackAnimations(); void removeExpiredColorFilters(); void initializeBattleAnimations(); diff --git a/client/battle/BattleWindow.cpp b/client/battle/BattleWindow.cpp index dd71da022..865a410d0 100644 --- a/client/battle/BattleWindow.cpp +++ b/client/battle/BattleWindow.cpp @@ -179,6 +179,12 @@ void BattleWindow::deactivate() void BattleWindow::keyPressed(const SDL_Keycode & key) { + if (owner.openingPlaying()) + { + owner.openingEnd(); + return; + } + if(key == SDLK_q) { toggleQueueVisibility(); @@ -189,10 +195,7 @@ void BattleWindow::keyPressed(const SDL_Keycode & key) } else if(key == SDLK_ESCAPE) { - if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true) - CCS->soundh->stopSound(owner.battleIntroSoundChannel); - else - owner.actionsController->endCastingSpell(); + owner.actionsController->endCastingSpell(); } } diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index 4ae613df5..e355b7d5b 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -183,7 +183,7 @@ void CreatureAnimation::setType(ECreatureAnimType type) currentFrame = 0; once = false; - play(); + speed = speedController(this, type); } CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller) @@ -191,6 +191,7 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController speed(0.1f), shadowAlpha(128), currentFrame(0), + animationEnd(-1), elapsedTime(0), type(ECreatureAnimType::HOLDING), border(CSDL_Ext::makeColor(0, 0, 0, 0)), @@ -248,7 +249,7 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController reverse->verticalFlip(); - play(); + speed = speedController(this, type); } void CreatureAnimation::endAnimation() @@ -263,6 +264,9 @@ bool CreatureAnimation::incrementFrame(float timePassed) { elapsedTime += timePassed; currentFrame += timePassed * speed; + if (animationEnd >= 0) + currentFrame = std::min(currentFrame, animationEnd); + const auto framesNumber = framesInGroup(type); 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 { return static_cast(forward->size(size_t(group))); @@ -421,14 +430,3 @@ bool CreatureAnimation::isShooting() const || getType() == ECreatureAnimType::SHOOT_FRONT || getType() == ECreatureAnimType::SHOOT_DOWN; } - -void CreatureAnimation::pause() -{ - speed = 0; -} - -void CreatureAnimation::play() -{ - //logAnim->trace("Play %s group %d at %d:%d", name, static_cast(getType()), pos.x, pos.y); - speed = speedController(this, type); -} diff --git a/client/battle/CreatureAnimation.h b/client/battle/CreatureAnimation.h index 12eebef0d..610b397fd 100644 --- a/client/battle/CreatureAnimation.h +++ b/client/battle/CreatureAnimation.h @@ -88,6 +88,7 @@ private: /// currently displayed frame. Float to allow H3-style animations where frames /// don't display for integer number of frames float currentFrame; + float animationEnd; /// cumulative, real-time duration of animation. Used for effects like selection border float elapsedTime; @@ -146,8 +147,7 @@ public: /// returns number of frames in selected animation type int framesInGroup(ECreatureAnimType group) const; - void pause(); - void play(); + void playUntil(size_t frameIndex); /// helpers to classify current type of animation bool isDead() const;