diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 22d883425..b71aa3b2c 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1017,7 +1017,7 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) battleInt->effectsController->displayCustomEffects(ba->customEffects); - battleInt->waitForAnims(); + battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false); auto actionTarget = curAction->getTarget(cb.get()); @@ -1035,6 +1035,7 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) { const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked); battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true); + break; } } } @@ -1042,8 +1043,6 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) { auto attackTarget = actionTarget.at(1).hexValue; - //TODO: use information from BattleAttack but not curAction - int shift = 0; if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0) { @@ -1058,17 +1057,15 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba) if(!ba->bsa.empty()) { - const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked); - battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false); + const CStack * defender = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked); + battleInt->stackAttacking(attacker, BattleHex(attackTarget + shift), defender, false); } } - //battleInt->waitForAnims(); //FIXME: freeze + //battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false); // FIXME: freeze if(ba->spellLike()) { - //TODO: use information from BattleAttack but not curAction - auto destination = actionTarget.at(0).hexValue; //display hit animation SpellID spellID = ba->spellID; diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index eaf469245..bed9065fa 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -152,36 +152,18 @@ CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInte : CBattleStackAnimation(owner, _attackedInfo.defender), attacker(_attackedInfo.attacker), rangedAttack(_attackedInfo.indirectAttack), - killed(_attackedInfo.killed), - timeToWait(0) + killed(_attackedInfo.killed) { logAnim->debug("Created defence anim for %s", _attackedInfo.defender->getName()); } bool CDefenceAnimation::init() { - if(rangedAttack && attacker != nullptr && owner.projectilesController->hasActiveProjectile(attacker)) //delay hit animation - { - return false; - } + logAnim->info("CDefenceAnimation::init: stack %d", stack->ID); - // synchronize animation with attacker, unless defending or attacked by shooter: - // wait for 1/2 of attack animation - if (!rangedAttack && getMyAnimType() != ECreatureAnimType::DEFENCE) - { - float frameLength = AnimationControls::getCreatureAnimationSpeed( - stack->getCreature(), stackAnimation(stack).get(), getMyAnimType()); - - timeToWait = myAnim->framesInGroup(getMyAnimType()) * frameLength / 2; - - //FIXME: perhaps this should be pause instead? - myAnim->setType(ECreatureAnimType::HOLDING); - } - else - { - timeToWait = 0; - startAnimation(); - } + CCS->soundh->playSound(getMySound()); + myAnim->setType(getMyAnimType()); + myAnim->onAnimationReset += [&](){ delete this; }; return true; //initialized successfuly } @@ -212,25 +194,6 @@ ECreatureAnimType::Type CDefenceAnimation::getMyAnimType() return ECreatureAnimType::HITTED; } -void CDefenceAnimation::startAnimation() -{ - CCS->soundh->playSound(getMySound()); - myAnim->setType(getMyAnimType()); - myAnim->onAnimationReset += [&](){ delete this; }; -} - -void CDefenceAnimation::nextFrame() -{ - if (timeToWait > 0) - { - timeToWait -= float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000; - if (timeToWait <= 0) - startAnimation(); - } - - CBattleAnimation::nextFrame(); -} - CDefenceAnimation::~CDefenceAnimation() { if(killed) @@ -274,19 +237,7 @@ bool CMeleeAttackAnimation::init() return false; } - bool toReverse = owner.getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), stackFacingRight(stack), attackedStack->doubleWide(), stackFacingRight(attackedStack)); - - if(toReverse) - { - owner.stacksController->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn)); - return false; - } - - // opponent must face attacker ( = different directions) before he can be attacked - if(attackingStack && attackedStack && - stackFacingRight(attackingStack) == stackFacingRight(attackedStack)) - return false; - + logAnim->info("CMeleeAttackAnimation::init: stack %d -> stack %d", stack->ID, attackedStack->ID); //reversed shooting = false; @@ -354,6 +305,20 @@ bool CMeleeAttackAnimation::init() return true; } +void CMeleeAttackAnimation::nextFrame() +{ + size_t currentFrame = stackAnimation(attackingStack)->getCurrentFrame(); + size_t totalFrames = stackAnimation(attackingStack)->framesInGroup(group); + + if ( currentFrame * 2 >= totalFrames ) + { + if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) + owner.setAnimationCondition(EAnimationEvents::HIT, true); + } + + CAttackAnimation::nextFrame(); +} + CMeleeAttackAnimation::CMeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked) : CAttackAnimation(owner, attacker, _dest, _attacked) { @@ -386,6 +351,8 @@ bool CMovementAnimation::init() return false; } + logAnim->info("CMovementAnimation::init: stack %d moves %d -> %d", stack->ID, oldPos, currentHex); + //reverse unit if necessary if(owner.stacksController->shouldRotate(stack, oldPos, currentHex)) { @@ -500,6 +467,8 @@ bool CMovementEndAnimation::init() return false; } + logAnim->info("CMovementEndAnimation::init: stack %d", stack->ID); + CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving)); if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END)) @@ -508,6 +477,7 @@ bool CMovementEndAnimation::init() return false; } + myAnim->setType(ECreatureAnimType::MOVE_END); myAnim->onAnimationReset += [&](){ delete this; }; @@ -539,6 +509,7 @@ bool CMovementStartAnimation::init() return false; } + logAnim->info("CMovementStartAnimation::init: stack %d", stack->ID); CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving)); if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START)) @@ -566,9 +537,10 @@ bool CReverseAnimation::init() return false; //there is no such creature } + logAnim->info("CReverseAnimation::init: stack %d", stack->ID); if(myAnim->framesInGroup(ECreatureAnimType::TURN_L)) { - myAnim->setType(ECreatureAnimType::TURN_L); + myAnim->playOnce(ECreatureAnimType::TURN_L); myAnim->onAnimationReset += std::bind(&CReverseAnimation::setupSecondPart, this); } else @@ -578,12 +550,6 @@ bool CReverseAnimation::init() return true; } -CReverseAnimation::~CReverseAnimation() -{ - if( stack && stack->alive() )//don't do that if stack is dead - myAnim->setType(ECreatureAnimType::HOLDING); -} - void CBattleStackAnimation::rotateStack(BattleHex hex) { setStackFacingRight(stack, !stackFacingRight(stack)); @@ -603,7 +569,7 @@ void CReverseAnimation::setupSecondPart() if(myAnim->framesInGroup(ECreatureAnimType::TURN_R)) { - myAnim->setType(ECreatureAnimType::TURN_R); + myAnim->playOnce(ECreatureAnimType::TURN_R); myAnim->onAnimationReset += [&](){ delete this; }; } else @@ -618,6 +584,7 @@ bool CResurrectionAnimation::init() return false; } + logAnim->info("CResurrectionAnimation::init: stack %d", stack->ID); myAnim->playOnce(ECreatureAnimType::RESURRECTION); myAnim->onAnimationReset += [&](){ delete this; }; @@ -630,16 +597,10 @@ CResurrectionAnimation::CResurrectionAnimation(BattleInterface & owner, const CS } -CResurrectionAnimation::~CResurrectionAnimation() -{ - -} - CRangedAttackAnimation::CRangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender) : CAttackAnimation(owner, attacker, dest_, defender), projectileEmitted(false) { - logAnim->info("Ranged attack animation created"); } bool CRangedAttackAnimation::init() @@ -655,7 +616,8 @@ bool CRangedAttackAnimation::init() return false; } - logAnim->info("Ranged attack animation initialized"); + logAnim->info("CRangedAttackAnimation::init: stack %d", stack->ID); + setAnimationGroup(); initializeProjectile(); shooting = true; @@ -726,18 +688,17 @@ void CRangedAttackAnimation::nextFrame() if (owner.projectilesController->hasActiveProjectile(attackingStack)) stackAnimation(attackingStack)->pause(); else + { stackAnimation(attackingStack)->play(); + if(owner.getAnimationCondition(EAnimationEvents::HIT) == false) + owner.setAnimationCondition(EAnimationEvents::HIT, true); + } } CAttackAnimation::nextFrame(); if (!projectileEmitted) { - logAnim->info("Ranged attack executing, %d / %d / %d", - stackAnimation(attackingStack)->getCurrentFrame(), - getAttackClimaxFrame(), - stackAnimation(attackingStack)->framesInGroup(group)); - // emit projectile once animation playback reached "climax" frame if ( stackAnimation(attackingStack)->getCurrentFrame() >= getAttackClimaxFrame() ) { @@ -750,7 +711,6 @@ void CRangedAttackAnimation::nextFrame() CRangedAttackAnimation::~CRangedAttackAnimation() { - logAnim->info("Ranged attack animation is over"); //FIXME: this assert triggers under some unclear, rare conditions. Possibly - if game window is inactive and/or in foreground/minimized? assert(!owner.projectilesController->hasActiveProjectile(attackingStack)); assert(projectileEmitted); @@ -830,7 +790,6 @@ void CCatapultAnimation::createProjectile(const Point & from, const Point & dest owner.projectilesController->createCatapultProjectile(attackingStack, from, dest); } - CCastAnimation::CCastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell) : CRangedAttackAnimation(owner, attacker, dest_, defender), spell(spell) @@ -908,6 +867,7 @@ CPointEffectAnimation::CPointEffectAnimation(BattleInterface & owner, soundBase: soundFinished(false), effectFinished(false) { + logAnim->info("CPointEffectAnimation::init: effect %s", animationName); } CPointEffectAnimation::CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector hex, int effects): diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index 4f7bc0358..d22c507d0 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -96,16 +96,12 @@ class CDefenceAnimation : public CBattleStackAnimation ECreatureAnimType::Type getMyAnimType(); std::string getMySound(); - void startAnimation(); - const CStack * attacker; //attacking stack bool rangedAttack; //if true, stack has been attacked by shooting bool killed; //if true, stack has been killed - float timeToWait; // for how long this animation should be paused public: bool init() override; - void nextFrame() override; CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInterface & owner); ~CDefenceAnimation(); @@ -128,6 +124,7 @@ class CMeleeAttackAnimation : public CAttackAnimation { public: bool init() override; + void nextFrame() override; CMeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked); }; @@ -187,13 +184,11 @@ public: /// Class responsible for animation of stack chaning direction (left <-> right) class CReverseAnimation : public CStackMoveAnimation { + void setupSecondPart(); public: bool init() override; - void setupSecondPart(); - CReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest); - ~CReverseAnimation(); }; /// Resurrects stack from dead state @@ -202,8 +197,7 @@ class CResurrectionAnimation : public CBattleStackAnimation public: bool init() override; - CResurrectionAnimation(BattleInterface & owner, const CStack * stack); - ~CResurrectionAnimation(); + CResurrectionAnimation(BattleInterface & owner, const CStack * _stack); }; class CRangedAttackAnimation : public CAttackAnimation diff --git a/client/battle/BattleConstants.h b/client/battle/BattleConstants.h index 3a565e8e7..b169042cd 100644 --- a/client/battle/BattleConstants.h +++ b/client/battle/BattleConstants.h @@ -9,6 +9,16 @@ */ #pragma once +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 + ATTACK = 3, // attack and defense animations are playing + HIT = 4, // hit & death animations are playing + PROJECTILES = 5, // there are any flying projectiles + COUNT +}; + namespace EBattleEffect { enum EBattleEffect @@ -38,7 +48,7 @@ enum Type IDLE = 1, // idling movement that happens from time to time DEFEAT = 2, // played when army loses stack or on friendly fire VICTORY = 3, // when enemy stack killed or huge damage is dealt - CAST_SPELL = 4 // spellcasting + CAST_SPELL = 4 // spellcasting }; } @@ -46,28 +56,28 @@ namespace ECreatureAnimType { enum Type // list of creature animations, numbers were taken from def files { - MOVING =0, - MOUSEON =1, - HOLDING =2, - HITTED =3, - DEFENCE =4, - DEATH =5, - DEATH_RANGED =6, - TURN_L =7, - TURN_R =8, - //TURN_L2 =9, //unused - identical to TURN_L - //TURN_R2 =10,//unused - identical to TURN_R - ATTACK_UP =11, - ATTACK_FRONT =12, - ATTACK_DOWN =13, - SHOOT_UP =14, - SHOOT_FRONT =15, - SHOOT_DOWN =16, - CAST_UP =17, - CAST_FRONT =18, - CAST_DOWN =19, - MOVE_START =20, - MOVE_END =21, + MOVING = 0, + MOUSEON = 1, + HOLDING = 2, // base idling animation + HITTED = 3, // base animation for when stack is taking damage + DEFENCE = 4, // alternative animation for defending in melee if stack spent its action on defending + DEATH = 5, + DEATH_RANGED = 6, // alternative animation for when stack is killed by ranged attack + TURN_L = 7, + TURN_R = 8, + //TURN_L2 = 9, //unused - identical to TURN_L + //TURN_R2 = 10, //unused - identical to TURN_R + ATTACK_UP = 11, + ATTACK_FRONT = 12, + ATTACK_DOWN = 13, + SHOOT_UP = 14, + SHOOT_FRONT = 15, + SHOOT_DOWN = 16, + CAST_UP = 17, + CAST_FRONT = 18, + CAST_DOWN = 19, + MOVE_START = 20, // small animation to be played before MOVING + MOVE_END = 21, // small animation to be played after MOVING DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here diff --git a/client/battle/BattleEffectsController.cpp b/client/battle/BattleEffectsController.cpp index 3493ddc14..bf93f5f91 100644 --- a/client/battle/BattleEffectsController.cpp +++ b/client/battle/BattleEffectsController.cpp @@ -62,6 +62,8 @@ void BattleEffectsController::displayCustomEffects(const std::vectorcb->battleGetStackByID(bte.stackID); if(!stack) { @@ -95,11 +97,13 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt default: return; } - //waitForAnims(); //fixme: freezes game :? + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleEffectsController::startAction(const BattleAction* action) { + assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber); switch(action->actionType) @@ -121,6 +125,8 @@ void BattleEffectsController::startAction(const BattleAction* action) displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, actionTarget.at(0).hexValue); break; } + + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer) diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index cfeb7a84e..a8a40c955 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -42,19 +42,30 @@ #include "../../lib/NetPacks.h" #include "../../lib/UnlockGuard.h" -CondSh BattleInterface::animsAreDisplayed(false); CondSh BattleInterface::givenCommand(nullptr); BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, - std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt) - : attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0), - attackerInt(att), defenderInt(defen), curInt(att), - myTurn(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false) + std::shared_ptr att, + std::shared_ptr defen, + std::shared_ptr spectatorInt) + : attackingHeroInstance(hero1) + , defendingHeroInstance(hero2) + , animCount(0) + , attackerInt(att) + , defenderInt(defen) + , curInt(att) + , myTurn(false) + , moveSoundHander(-1) + , bresult(nullptr) + , battleActionsStarted(false) { OBJ_CONSTRUCTION; + for ( auto & event : animationEvents) + event.setn(false); + if(spectatorInt) { curInt = spectatorInt; @@ -65,7 +76,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet * curInt = defenderInt; } - animsAreDisplayed.setn(false); pos = myRect; strongInterest = true; givenCommand.setn(nullptr); @@ -204,7 +214,9 @@ BattleInterface::~BattleInterface() const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType); CCS->musich->playMusicFromSet("terrain", terrain.name, true, false); } - animsAreDisplayed.setn(false); + assert(getAnimationCondition(EAnimationEvents::ACTION) == false); + + setAnimationCondition(EAnimationEvents::ACTION, false); } void BattleInterface::setPrintCellBorders(bool set) @@ -367,7 +379,7 @@ void BattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, co void BattleInterface::newRoundFirst( int round ) { - waitForAnims(); + waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleInterface::newRound(int number) @@ -455,10 +467,7 @@ void BattleInterface::gateStateChanged(const EGateState state) void BattleInterface::battleFinished(const BattleResult& br) { bresult = &br; - { - auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim); - animsAreDisplayed.waitUntil(false); - } + waitForAnimationCondition(EAnimationEvents::ACTION, false); stacksController->setActiveStack(nullptr); displayBattleFinished(); } @@ -519,7 +528,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) } } - waitForAnims(); //wait for projectile animation + waitForAnimationCondition(EAnimationEvents::ACTION, false);//wait for projectile animation displaySpellHit(spellID, sc->tile); @@ -539,7 +548,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition()); } - waitForAnims(); + waitForAnimationCondition(EAnimationEvents::ACTION, false); //mana absorption if (sc->manaGained > 0) { @@ -548,7 +557,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc) stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero)); stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero)); } - waitForAnims(); + waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse) @@ -776,12 +785,6 @@ void BattleInterface::startAction(const BattleAction* action) effectsController->startAction(action); } -void BattleInterface::waitForAnims() -{ - auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim); - animsAreDisplayed.waitWhileTrue(); -} - void BattleInterface::tacticPhaseEnd() { stacksController->setActiveStack(nullptr); @@ -800,7 +803,7 @@ void BattleInterface::tacticNextStack(const CStack * current) current = stacksController->getActiveStack(); //no switching stacks when the current one is moving - waitForAnims(); + waitForAnimationCondition(EAnimationEvents::ACTION, false); TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE); vstd::erase_if (stacksOfMine, &immobile); @@ -959,3 +962,40 @@ void BattleInterface::castThisSpell(SpellID spellID) { actionsController->castThisSpell(spellID); } + +void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state) +{ + logAnim->info("setAnimationCondition: %d -> %s", static_cast(event), state ? "ON" : "OFF"); + + size_t index = static_cast(event); + animationEvents[index].setn(state); + + for (auto it = awaitingEvents.begin(); it != awaitingEvents.end();) + { + if (it->event == event && it->eventState == state) + { + it->action(); + it = awaitingEvents.erase(it); + } + else + ++it; + } +} + +bool BattleInterface::getAnimationCondition( EAnimationEvents event) +{ + size_t index = static_cast(event); + return animationEvents[index].get(); +} + +void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state) +{ + auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim); + size_t index = static_cast(event); + animationEvents[index].waitUntil(state); +} + +void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action) +{ + awaitingEvents.push_back({action, event, state}); +} diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 8428d8cf7..f6c5e5835 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -12,6 +12,7 @@ #include "BattleConstants.h" #include "../gui/CIntObject.h" #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation +#include "../../lib/CondSh.h" VCMI_LIB_NAMESPACE_BEGIN @@ -21,7 +22,6 @@ class CStack; struct BattleResult; struct BattleSpellCast; struct CObstacleInstance; -template struct CondSh; struct SetStackEffect; class BattleAction; class CGTownInstance; @@ -95,6 +95,20 @@ private: bool battleActionsStarted; //used for delaying battle actions until intro sound stops int battleIntroSoundChannel; //required as variable for disabling it via ESC key + using AwaitingAnimationAction = std::function; + + 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; + + /// List of events that are waiting to be triggered + std::vector awaitingEvents; + void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all void requestAutofightingAIToTakeAction(); @@ -116,7 +130,6 @@ public: std::unique_ptr actionsController; std::unique_ptr effectsController; - static CondSh animsAreDisplayed; //for waiting with the end of battle for end of anims static CondSh givenCommand; //data != nullptr if we have i.e. moved current unit bool myTurn; //if true, interface is active (commands can be ordered) @@ -136,7 +149,18 @@ public: void tacticNextStack(const CStack *current); void tacticPhaseEnd(); - void waitForAnims(); + + /// 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); //napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem void activate() override; diff --git a/client/battle/BattleObstacleController.cpp b/client/battle/BattleObstacleController.cpp index 292ee393b..0b8c657f1 100644 --- a/client/battle/BattleObstacleController.cpp +++ b/client/battle/BattleObstacleController.cpp @@ -107,7 +107,7 @@ void BattleObstacleController::obstaclePlaced(const std::vectoraddNewAnim(new CPointEffectAnimation(owner, soundBase::invalid, spellObstacle->appearAnimation, whereTo, oi->pos, CPointEffectAnimation::WAIT_FOR_SOUND)); //so when multiple obstacles are added, they show up one after another - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin()); loadObstacleImage(*spellObstacle); diff --git a/client/battle/BattleProjectileController.cpp b/client/battle/BattleProjectileController.cpp index 6932a9a06..86c70d4d8 100644 --- a/client/battle/BattleProjectileController.cpp +++ b/client/battle/BattleProjectileController.cpp @@ -48,7 +48,6 @@ static double calculateCatapultParabolaY(const Point & from, const Point & dest, void ProjectileMissile::show(Canvas & canvas) { - logAnim->info("Projectile rendering, %d / %d", step, steps); size_t group = reverse ? 1 : 0; auto image = animation->getImage(frameNum, group, true); diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 436cec9f2..fea3436b3 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -347,7 +347,8 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca) owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", positions)); } - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.setAnimationCondition(EAnimationEvents::HIT, false); for (auto attackInfo : ca.attackedParts) { diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 3df6b3e37..335965649 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -148,8 +148,8 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer) void BattleStacksController::stackReset(const CStack * stack) { //FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end - assert(!owner.animsAreDisplayed.get()); - owner.waitForAnims(); + assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); auto iter = stackAnimation.find(stack->ID); @@ -173,7 +173,7 @@ void BattleStacksController::stackReset(const CStack * stack) animation->shiftColor(&shifterClone); } - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleStacksController::stackAdded(const CStack * stack) @@ -355,6 +355,7 @@ void BattleStacksController::updateBattleAnimations() elem->tryInitialize(); } + bool hadAnimations = !currentAnimations.empty(); vstd::erase(currentAnimations, nullptr); @@ -362,20 +363,20 @@ void BattleStacksController::updateBattleAnimations() { //anims ended owner.controlPanel->blockUI(activeStack == nullptr); - owner.animsAreDisplayed.setn(false); + owner.setAnimationCondition(EAnimationEvents::ACTION, false); } } void BattleStacksController::addNewAnim(CBattleAnimation *anim) { currentAnimations.push_back(anim); - owner.animsAreDisplayed.setn(true); + owner.setAnimationCondition(EAnimationEvents::ACTION, true); } void BattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities { stackToActivate = stack; - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); if (stackToActivate) //during waiting stack may have gotten activated through show owner.activateStack(); } @@ -412,17 +413,34 @@ void BattleStacksController::stacksAreAttacked(std::vector at addNewAnim(new CReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition())); } + // raise flag that movement phase started, starting any queued movements + owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true); + + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false); + for(auto & attackedInfo : attackedInfos) { - addNewAnim(new CDefenceAnimation(attackedInfo, owner)); + bool useDefenceAnim = attackedInfo.defender->defendingAnim && !attackedInfo.indirectAttack && !attackedInfo.killed; - if (attackedInfo.battleEffect != EBattleEffect::INVALID) - owner.effectsController->displayEffect(EBattleEffect::EBattleEffect(attackedInfo.battleEffect), attackedInfo.defender->getPosition()); + EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT; - if (attackedInfo.spellEffect != SpellID::NONE) - owner.displaySpellEffect(attackedInfo.spellEffect, attackedInfo.defender->getPosition()); + owner.executeOnAnimationCondition(usedEvent, true, [=]() + { + addNewAnim(new CDefenceAnimation(attackedInfo, owner)); + + if (attackedInfo.battleEffect != EBattleEffect::INVALID) + owner.effectsController->displayEffect(EBattleEffect::EBattleEffect(attackedInfo.battleEffect), attackedInfo.defender->getPosition()); + + if (attackedInfo.spellEffect != SpellID::NONE) + owner.displaySpellEffect(attackedInfo.spellEffect, attackedInfo.defender->getPosition()); + }); } - owner.waitForAnims(); + + owner.setAnimationCondition(EAnimationEvents::ATTACK, true); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + owner.setAnimationCondition(EAnimationEvents::ATTACK, false); + owner.setAnimationCondition(EAnimationEvents::HIT, false); for (auto & attackedInfo : attackedInfos) { @@ -435,7 +453,7 @@ void BattleStacksController::stacksAreAttacked(std::vector at if (attackedInfo.cloneKilled) stackRemoved(attackedInfo.defender->ID); } - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleStacksController::stackMoved(const CStack *stack, std::vector destHex, int distance) @@ -443,23 +461,27 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector 0); //FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end - assert(!owner.animsAreDisplayed.get()); + assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); if(shouldRotate(stack, stack->getPosition(), destHex[0])) addNewAnim(new CReverseAnimation(owner, stack, destHex[0])); addNewAnim(new CMovementStartAnimation(owner, stack)); - owner.waitForAnims(); + + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); addNewAnim(new CMovementAnimation(owner, stack, destHex, distance)); - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); addNewAnim(new CMovementEndAnimation(owner, stack, destHex.back())); - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); } void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *defender, bool shooting ) { + //FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end + assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + bool needsReverse = owner.curInt->cb->isToReverse( attacker->getPosition(), @@ -469,21 +491,26 @@ void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex d facingRight(defender)); if (needsReverse) - addNewAnim(new CReverseAnimation(owner, attacker, attacker->getPosition())); - - owner.waitForAnims(); - - if (shooting) { - addNewAnim(new CShootingAnimation(owner, attacker, dest, defender)); - } - else - { - addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, defender)); + owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]() + { + addNewAnim(new CReverseAnimation(owner, attacker, attacker->getPosition())); + }); } - // do not wait - waiting will be done at stacksAreAttacked - // waitForAnims(); + owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]() + { + if (shooting) + { + addNewAnim(new CShootingAnimation(owner, attacker, dest, defender)); + } + else + { + addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, defender)); + } + }); + + //waiting will be done in stacksAreAttacked } bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const @@ -503,8 +530,8 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex void BattleStacksController::endAction(const BattleAction* action) { //FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end - assert(!owner.animsAreDisplayed.get()); - owner.waitForAnims(); + assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); //check if we should reverse stacks TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY); @@ -518,7 +545,15 @@ void BattleStacksController::endAction(const BattleAction* action) addNewAnim(new CReverseAnimation(owner, s, s->getPosition())); } } - owner.waitForAnims(); + owner.waitForAnimationCondition(EAnimationEvents::ACTION, false); + + //FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end + 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); + assert(owner.getAnimationCondition(EAnimationEvents::PROJECTILES) == false); } void BattleStacksController::startAction(const BattleAction* action) diff --git a/lib/CondSh.h b/lib/CondSh.h index 165e2a0c1..0120a36fa 100644 --- a/lib/CondSh.h +++ b/lib/CondSh.h @@ -18,6 +18,8 @@ template struct CondSh boost::condition_variable cond; boost::mutex mx; + CondSh() : data(T()) {} + CondSh(T t) : data(t) {} // set data