1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Introduced animation phases for beter ordering of visuals in battles

This commit is contained in:
Ivan Savenko 2022-12-10 00:25:11 +02:00
parent c79634b6a7
commit 0020d76d1d
12 changed files with 246 additions and 178 deletions

View File

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

View File

@ -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<BattleHex> hex, int effects):

View File

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

View File

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

View File

@ -62,6 +62,8 @@ void BattleEffectsController::displayCustomEffects(const std::vector<CustomEffec
void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
const CStack * stack = owner.curInt->cb->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)

View File

@ -42,19 +42,30 @@
#include "../../lib/NetPacks.h"
#include "../../lib/UnlockGuard.h"
CondSh<bool> BattleInterface::animsAreDisplayed(false);
CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
const CGHeroInstance *hero1, const CGHeroInstance *hero2,
const SDL_Rect & myRect,
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
attackerInt(att), defenderInt(defen), curInt(att),
myTurn(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
std::shared_ptr<CPlayerInterface> att,
std::shared_ptr<CPlayerInterface> defen,
std::shared_ptr<CPlayerInterface> 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<int>(event), state ? "ON" : "OFF");
size_t index = static_cast<size_t>(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<size_t>(event);
return animationEvents[index].get();
}
void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state)
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
size_t index = static_cast<size_t>(event);
animationEvents[index].waitUntil(state);
}
void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action)
{
awaitingEvents.push_back({action, event, state});
}

View File

@ -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 <typename T> 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<void()>;
struct AwaitingAnimationEvents {
AwaitingAnimationAction action;
EAnimationEvents event;
bool eventState;
};
/// Conditional variables that are set depending on ongoing animations on the battlefield
std::array< CondSh<bool>, static_cast<size_t>(EAnimationEvents::COUNT)> animationEvents;
/// List of events that are waiting to be triggered
std::vector<AwaitingAnimationEvents> 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<BattleActionsController> actionsController;
std::unique_ptr<BattleEffectsController> effectsController;
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
static CondSh<BattleAction *> 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;

View File

@ -107,7 +107,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
owner.stacksController->addNewAnim(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);

View File

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

View File

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

View File

@ -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<StackAttackedInfo> 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<StackAttackedInfo> at
if (attackedInfo.cloneKilled)
stackRemoved(attackedInfo.defender->ID);
}
owner.waitForAnims();
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
@ -443,23 +461,27 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
assert(destHex.size() > 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)

View File

@ -18,6 +18,8 @@ template <typename T> struct CondSh
boost::condition_variable cond;
boost::mutex mx;
CondSh() : data(T()) {}
CondSh(T t) : data(t) {}
// set data