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:
parent
c79634b6a7
commit
0020d76d1d
@ -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;
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user