1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Multiple fixes & improvements to animation ordering

This commit is contained in:
Ivan Savenko 2022-12-11 12:29:11 +02:00
parent 0020d76d1d
commit 60a00b450e
13 changed files with 259 additions and 173 deletions

View File

@ -986,91 +986,36 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
assert(curAction);
const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking);
StackAttackInfo info;
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
info.defender = nullptr;
info.indirectAttack = ba->shot();
info.lucky = ba->lucky();
info.unlucky = ba->unlucky();
info.deathBlow = ba->deathBlow();
info.lifeDrain = ba->lifeDrain();
info.tile = ba->tile;
info.spellEffect = SpellID::NONE;
if(!attacker)
{
logGlobal->error("Attacking stack not found");
return;
}
if (ba->spellLike())
info.spellEffect = ba->spellID;
if(ba->lucky()) //lucky hit
for(auto & elem : ba->bsa)
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-45));
battleInt->effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, attacker->getPosition());
}
if(ba->unlucky()) //unlucky hit
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-44));
battleInt->effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, attacker->getPosition());
}
if(ba->deathBlow())
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(365));
for(auto & elem : ba->bsa)
if(!elem.isSecondary())
{
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->effectsController->displayEffect(EBattleEffect::DEATH_BLOW, attacked->getPosition());
assert(info.defender == nullptr);
info.defender = cb->battleGetStackByID(elem.stackAttacked);
}
CCS->soundh->playSound(soundBase::deathBlow);
}
battleInt->effectsController->displayCustomEffects(ba->customEffects);
battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false);
auto actionTarget = curAction->getTarget(cb.get());
if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot()))
{
logNetwork->error("Invalid current action: no destination.");
return;
}
if(ba->shot())
{
for(auto & elem : ba->bsa)
else
{
if(!elem.isSecondary()) //display projectile only for primary target
{
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
break;
}
info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked));
}
}
else
{
auto attackTarget = actionTarget.at(1).hexValue;
assert(info.defender != nullptr);
assert(info.attacker != nullptr);
int shift = 0;
if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0)
{
int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
if(distp < distm)
shift = 1;
else
shift = -1;
}
if(!ba->bsa.empty())
{
const CStack * defender = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
battleInt->stackAttacking(attacker, BattleHex(attackTarget + shift), defender, false);
}
}
//battleInt->waitForAnimationCondition(EAnimationEvents::ACTION, false); // FIXME: freeze
if(ba->spellLike())
{
auto destination = actionTarget.at(0).hexValue;
//display hit animation
SpellID spellID = ba->spellID;
battleInt->displaySpellHit(spellID, destination);
}
battleInt->stackAttacking(info);
}
void CPlayerInterface::battleGateStateChanged(const EGateState state)

View File

@ -159,7 +159,7 @@ CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInte
bool CDefenceAnimation::init()
{
logAnim->info("CDefenceAnimation::init: stack %d", stack->ID);
logAnim->info("CDefenceAnimation::init: stack %s", stack->getName());
CCS->soundh->playSound(getMySound());
myAnim->setType(getMyAnimType());
@ -231,13 +231,16 @@ void CDummyAnimation::nextFrame()
bool CMeleeAttackAnimation::init()
{
assert(attackingStack);
assert(!myAnim->isDeadOrDying());
if(!attackingStack || myAnim->isDeadOrDying())
{
delete this;
return false;
}
logAnim->info("CMeleeAttackAnimation::init: stack %d -> stack %d", stack->ID, attackedStack->ID);
logAnim->info("CMeleeAttackAnimation::init: stack %s -> stack %s", stack->getName(), attackedStack->getName());
//reversed
shooting = false;
@ -351,7 +354,7 @@ bool CMovementAnimation::init()
return false;
}
logAnim->info("CMovementAnimation::init: stack %d moves %d -> %d", stack->ID, oldPos, currentHex);
logAnim->info("CMovementAnimation::init: stack %s moves %d -> %d", stack->getName(), oldPos, currentHex);
//reverse unit if necessary
if(owner.stacksController->shouldRotate(stack, oldPos, currentHex))
@ -467,7 +470,7 @@ bool CMovementEndAnimation::init()
return false;
}
logAnim->info("CMovementEndAnimation::init: stack %d", stack->ID);
logAnim->info("CMovementEndAnimation::init: stack %s", stack->getName());
CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving));
@ -509,7 +512,7 @@ bool CMovementStartAnimation::init()
return false;
}
logAnim->info("CMovementStartAnimation::init: stack %d", stack->ID);
logAnim->info("CMovementStartAnimation::init: stack %s", stack->getName());
CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving));
if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
@ -531,13 +534,16 @@ CReverseAnimation::CReverseAnimation(BattleInterface & owner, const CStack * sta
bool CReverseAnimation::init()
{
assert(myAnim);
assert(!myAnim->isDeadOrDying());
if(myAnim == nullptr || myAnim->isDeadOrDying())
{
delete this;
return false; //there is no such creature
}
logAnim->info("CReverseAnimation::init: stack %d", stack->ID);
logAnim->info("CReverseAnimation::init: stack %s", stack->getName());
if(myAnim->framesInGroup(ECreatureAnimType::TURN_L))
{
myAnim->playOnce(ECreatureAnimType::TURN_L);
@ -559,6 +565,8 @@ void CBattleStackAnimation::rotateStack(BattleHex hex)
void CReverseAnimation::setupSecondPart()
{
assert(stack);
if(!stack)
{
delete this;
@ -578,13 +586,15 @@ void CReverseAnimation::setupSecondPart()
bool CResurrectionAnimation::init()
{
assert(stack);
if(!stack)
{
delete this;
return false;
}
logAnim->info("CResurrectionAnimation::init: stack %d", stack->ID);
logAnim->info("CResurrectionAnimation::init: stack %s", stack->getName());
myAnim->playOnce(ECreatureAnimType::RESURRECTION);
myAnim->onAnimationReset += [&](){ delete this; };
@ -616,7 +626,7 @@ bool CRangedAttackAnimation::init()
return false;
}
logAnim->info("CRangedAttackAnimation::init: stack %d", stack->ID);
logAnim->info("CRangedAttackAnimation::init: stack %s", stack->getName());
setAnimationGroup();
initializeProjectile();
@ -1067,6 +1077,9 @@ CWaitingAnimation::CWaitingAnimation(BattleInterface & owner):
void CWaitingAnimation::nextFrame()
{
// initialization conditions fulfilled, delay is over
if(owner.getAnimationCondition(EAnimationEvents::HIT) == false)
owner.setAnimationCondition(EAnimationEvents::HIT, true);
delete this;
}

View File

@ -10,12 +10,15 @@
#pragma once
enum class EAnimationEvents {
OPENING = 0, // battle opening sound is playing
OPENING = 0, // TODO 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
BEFORE_MOVE = 2, // TODO effects played before stack can act, e.g. regen or bad morale
MOVEMENT = 3, // stacks are moving or turning around
BEFORE_HIT = 4, // effects played before all attack/defence/hit animations
ATTACK = 5, // attack and defence animations are playing
HIT = 6, // hit & death animations are playing
AFTER_HIT = 7, // after all hit & death animations are over
PROJECTILES = 8, // TODO there are any flying projectiles
COUNT
};
@ -30,7 +33,7 @@ namespace EBattleEffect
BAD_MORALE = 30,
BAD_LUCK = 48,
RESURRECT = 50,
DRAIN_LIFE = 52, // hardcoded constant in CGameHandler
DRAIN_LIFE = 52,
POISON = 67,
DEATH_BLOW = 73,
REGENERATION = 74,

View File

@ -372,9 +372,9 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI
}
}
void BattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
{
stacksController->stackAttacking(attacker, dest, attacked, shooting);
stacksController->stackAttacking(attackInfo);
}
void BattleInterface::newRoundFirst( int round )
@ -490,6 +490,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
{
const SpellID spellID = sc->spellID;
const CSpell * spell = spellID.toSpell();
auto targetedTile = sc->tile;
assert(spell);
if(!spell)
@ -498,7 +499,11 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
const std::string & castSoundPath = spell->getCastSound();
if (!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath);
{
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
CCS->soundh->playSound(castSoundPath);
});
}
if ( sc->activeCast )
{
@ -506,58 +511,74 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
if(casterStack != nullptr )
{
displaySpellCast(spellID, casterStack->getPosition());
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell));
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
{
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
displaySpellCast(spellID, casterStack->getPosition());
});
}
else
if (sc->tile.isValid() && !spell->animationInfo.projectile.empty())
if (targetedTile.isValid() && !spell->animationInfo.projectile.empty())
{
// this is spell cast by hero with valid destination & valid projectile -> play animation
const CStack * target = curInt->cb->battleGetStackByPos(sc->tile);
const CStack * target = curInt->cb->battleGetStackByPos(targetedTile);
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position
Point destcoord = stacksController->getStackPositionAtHex(sc->tile, target); //position attacked by projectile
Point destcoord = stacksController->getStackPositionAtHex(targetedTile, target); //position attacked by projectile
destcoord += Point(250, 240); // FIXME: what are these constants?
projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
projectilesController->emitStackProjectile( nullptr );
// wait fo projectile to end
stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr));
//FIXME: should be replaced with new hero cast animation type
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
{
projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
projectilesController->emitStackProjectile( nullptr );
stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr));
});
}
}
waitForAnimationCondition(EAnimationEvents::ACTION, false);//wait for projectile animation
displaySpellHit(spellID, sc->tile);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
displaySpellHit(spellID, targetedTile);
});
//queuing affect animation
for(auto & elem : sc->affectedCres)
{
auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack);
if(stack)
displaySpellEffect(spellID, stack->getPosition());
{
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
displaySpellEffect(spellID, stack->getPosition());
});
}
}
//queuing additional animation
//queuing additional animation (magic mirror / resistance)
for(auto & elem : sc->customEffects)
{
auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
assert(stack);
if(stack)
effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition());
{
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition());
});
}
}
waitForAnimationCondition(EAnimationEvents::ACTION, false);
//mana absorption
if (sc->manaGained > 0)
{
Point leftHero = Point(15, 30) + pos;
Point rightHero = Point(755, 30) + pos;
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));
bool side = sc->side;
executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
});
}
waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)

View File

@ -72,6 +72,24 @@ struct StackAttackedInfo
bool cloneKilled;
};
struct StackAttackInfo
{
const CStack *attacker;
const CStack *defender;
std::vector< const CStack *> secondaryDefender;
//EBattleEffect::EBattleEffect battleEffect;
SpellID spellEffect;
BattleHex tile;
bool indirectAttack;
bool lucky;
bool unlucky;
bool deathBlow;
bool lifeDrain;
};
/// Big class which handles the overall battle interface actions and it is also responsible for
/// drawing everything correctly.
class BattleInterface : public WindowBase
@ -121,6 +139,9 @@ private:
void showInterface(SDL_Surface * to);
void setHeroAnimation(ui8 side, int phase);
void executeSpellCast(); //called when a hero casts a spell
public:
std::unique_ptr<BattleProjectileController> projectilesController;
std::unique_ptr<BattleSiegeController> siegeController;
@ -182,7 +203,7 @@ public:
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
void newRoundFirst( int round );
void newRound(int number); //caled when round is ended; number is the number of round
void hexLclicked(int whichOne); //hex only call-in

View File

@ -19,11 +19,11 @@ enum class EBattleFieldLayer {
OBSTACLES = 0,
CORPSES = 0,
WALLS = 1,
HEROES = 1,
STACKS = 1, // after corpses, obstacles
BATTLEMENTS = 2, // after stacks
STACK_AMOUNTS = 2, // after stacks, obstacles, corpses
EFFECTS = 3, // after obstacles, battlements
HEROES = 2,
STACKS = 2, // after corpses, obstacles, walls
BATTLEMENTS = 3, // after stacks
STACK_AMOUNTS = 3, // after stacks, obstacles, corpses
EFFECTS = 4, // after obstacles, battlements
};
class BattleRenderer

View File

@ -146,7 +146,7 @@ BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual w
BattleHex::HEX_AFTER_ALL, // BOTTOM_TOWER,
182, // BOTTOM_WALL,
130, // WALL_BELLOW_GATE,
78, // WALL_OVER_GATE,
62, // WALL_OVER_GATE,
12, // UPPER_WALL,
BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,
BattleHex::HEX_BEFORE_ALL, // GATE, // 94
@ -327,6 +327,10 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
{
//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);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
if (ca.attacker != -1)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);
@ -343,7 +347,6 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
for (auto attackInfo : ca.attackedParts)
positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", positions));
}

View File

@ -148,8 +148,11 @@ 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.getAnimationCondition(EAnimationEvents::ACTION) == false);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
//assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
//reset orientation?
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
auto iter = stackAnimation.find(stack->ID);
@ -163,7 +166,10 @@ void BattleStacksController::stackReset(const CStack * stack)
if(stack->alive() && animation->isDeadOrDying())
{
addNewAnim(new CResurrectionAnimation(owner, stack));
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
addNewAnim(new CResurrectionAnimation(owner, stack));
});
}
static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
@ -173,7 +179,7 @@ void BattleStacksController::stackReset(const CStack * stack)
animation->shiftColor(&shifterClone);
}
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
//owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleStacksController::stackAdded(const CStack * stack)
@ -410,15 +416,14 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
facingRight(attackedInfo.attacker));
if (needsReverse)
addNewAnim(new CReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition()));
{
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
{
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)
{
bool useDefenceAnim = attackedInfo.defender->defendingAnim && !attackedInfo.indirectAttack && !attackedInfo.killed;
@ -437,23 +442,24 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
});
}
owner.setAnimationCondition(EAnimationEvents::ATTACK, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, false);
owner.setAnimationCondition(EAnimationEvents::HIT, false);
for (auto & attackedInfo : attackedInfos)
{
if (attackedInfo.rebirth)
{
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition());
addNewAnim(new CResurrectionAnimation(owner, attackedInfo.defender));
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition());
addNewAnim(new CResurrectionAnimation(owner, attackedInfo.defender));
});
}
if (attackedInfo.cloneKilled)
stackRemoved(attackedInfo.defender->ID);
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
stackRemoved(attackedInfo.defender->ID);
});
}
}
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
executeAttackAnimations();
}
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
@ -477,40 +483,113 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *defender, bool shooting )
void BattleStacksController::stackAttacking( const StackAttackInfo & info )
{
//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(),
defender->getPosition(),
facingRight(attacker),
attacker->doubleWide(),
facingRight(defender));
info.attacker->getPosition(),
info.defender->getPosition(),
facingRight(info.attacker),
info.attacker->doubleWide(),
facingRight(info.defender));
if (needsReverse)
{
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
{
addNewAnim(new CReverseAnimation(owner, attacker, attacker->getPosition()));
addNewAnim(new CReverseAnimation(owner, info.attacker, info.attacker->getPosition()));
});
}
if(info.lucky)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-45));
owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, info.attacker->getPosition());
});
}
if(info.unlucky)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-44));
owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, info.attacker->getPosition());
});
}
if(info.deathBlow)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(365));
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, soundBase::deathBlow, info.defender->getPosition());
});
for(auto & elem : info.secondaryDefender)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition());
});
}
}
owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]()
{
if (shooting)
if (info.indirectAttack)
{
addNewAnim(new CShootingAnimation(owner, attacker, dest, defender));
addNewAnim(new CShootingAnimation(owner, info.attacker, info.tile, info.defender));
}
else
{
addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, defender));
addNewAnim(new CMeleeAttackAnimation(owner, info.attacker, info.tile, info.defender));
}
});
//waiting will be done in stacksAreAttacked
if (info.spellEffect)
{
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
owner.displaySpellHit(info.spellEffect, info.tile);
});
}
if (info.lifeDrain)
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]()
{
owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, soundBase::DRAINLIF, info.attacker->getPosition());
});
}
//return, animation playback will be handled by stacksAreAttacked
}
void BattleStacksController::executeAttackAnimations()
{
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, false);
// Note that HIT event can also be emitted by attack animation
owner.setAnimationCondition(EAnimationEvents::HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::HIT, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, false);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const

View File

@ -21,6 +21,7 @@ class SpellID;
VCMI_LIB_NAMESPACE_END
struct StackAttackedInfo;
struct StackAttackInfo;
class Canvas;
class BattleInterface;
@ -77,6 +78,7 @@ class BattleStacksController
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
void executeAttackAnimations();
public:
BattleStacksController(BattleInterface & owner);
@ -89,7 +91,7 @@ public:
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
void startAction(const BattleAction* action);
void endAction(const BattleAction* action);

View File

@ -1647,12 +1647,11 @@ struct BattleAttack : public CPackForClient
std::vector<BattleStackAttacked> bsa;
ui32 stackAttacking;
ui32 flags; //uses Eflags (below)
enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64};
enum EFlags{SHOT = 1, COUNTER = 2, LUCKY = 4, UNLUCKY = 8, BALLISTA_DOUBLE_DMG = 16, DEATH_BLOW = 32, SPELL_LIKE = 64, LIFE_DRAIN = 128};
BattleHex tile;
SpellID spellID; //for SPELL_LIKE
std::vector<CustomEffectInfo> customEffects;
bool shot() const//distance attack - decrease number of shots
{
return flags & SHOT;
@ -1681,13 +1680,17 @@ struct BattleAttack : public CPackForClient
{
return flags & SPELL_LIKE;
}
bool lifeDrain() const
{
return flags & LIFE_DRAIN;
}
template <typename Handler> void serialize(Handler &h, const int version)
{
h & bsa;
h & stackAttacking;
h & flags;
h & tile;
h & spellID;
h & customEffects;
h & attackerChanges;
}
};

View File

@ -145,8 +145,8 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con
{
case spells::Mode::HERO:
{
if(battleCastSpells(side.get()) > 0)
return ESpellCastProblem::CASTS_PER_TURN_LIMIT;
//if(battleCastSpells(side.get()) > 0)
// return ESpellCastProblem::CASTS_PER_TURN_LIMIT;
auto hero = dynamic_cast<const CGHeroInstance *>(caster);

View File

@ -330,8 +330,6 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target)
for(auto & p : effectsToApply)
p.first->apply(server, this, p.second);
// afterCast();
if(sc.activeCast)
{
caster->spendMana(server, spellCost);
@ -342,6 +340,11 @@ void BattleSpellMechanics::cast(ServerCallback * server, const Target & target)
otherHero->spendMana(server, -sc.manaGained);
}
}
// send empty event to client
// temporary(?) workaround to force animations to trigger
StacksInjured fake_event;
server->apply(&fake_event);
}
void BattleSpellMechanics::beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target)

View File

@ -1009,6 +1009,7 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
BattleAttack bat;
BattleLogMessage blm;
bat.stackAttacking = attacker->unitId();
bat.tile = targetHex;
std::shared_ptr<battle::CUnitState> attackerState = attacker->acquireState();
@ -1114,6 +1115,9 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
bat.attackerChanges.changedStacks.push_back(info);
}
if (drainedLife > 0)
bat.flags |= BattleAttack::LIFE_DRAIN;
sendAndApply(&bat);
{
@ -1142,17 +1146,6 @@ void CGameHandler::makeAttack(const CStack * attacker, const CStack * defender,
// drain life effect (as well as log entry) must be applied after the attack
if(drainedLife > 0)
{
BattleAttack bat;
bat.stackAttacking = attacker->unitId();
{
CustomEffectInfo customEffect;
customEffect.sound = soundBase::DRAINLIF;
customEffect.effect = 52;
customEffect.stack = attackerState->unitId();
bat.customEffects.push_back(std::move(customEffect));
}
sendAndApply(&bat);
MetaString text;
attackerState->addText(text, MetaString::GENERAL_TXT, 361);
attackerState->addNameReplacement(text, false);