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:
parent
0020d76d1d
commit
60a00b450e
@ -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
|
||||
{
|
||||
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)
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->effectsController->displayEffect(EBattleEffect::DEATH_BLOW, attacked->getPosition());
|
||||
}
|
||||
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()))
|
||||
if(!elem.isSecondary())
|
||||
{
|
||||
logNetwork->error("Invalid current action: no destination.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ba->shot())
|
||||
{
|
||||
for(auto & elem : ba->bsa)
|
||||
{
|
||||
if(!elem.isSecondary()) //display projectile only for primary target
|
||||
{
|
||||
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
|
||||
battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(info.defender == nullptr);
|
||||
info.defender = cb->battleGetStackByID(elem.stackAttacked);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto attackTarget = actionTarget.at(1).hexValue;
|
||||
|
||||
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);
|
||||
info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked));
|
||||
}
|
||||
}
|
||||
assert(info.defender != nullptr);
|
||||
assert(info.attacker != nullptr);
|
||||
|
||||
//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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
{
|
||||
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 )
|
||||
{
|
||||
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
|
||||
{
|
||||
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
|
||||
displaySpellCast(spellID, casterStack->getPosition());
|
||||
|
||||
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell));
|
||||
});
|
||||
}
|
||||
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?
|
||||
|
||||
//FIXME: should be replaced with new hero cast animation type
|
||||
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
|
||||
{
|
||||
projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
|
||||
projectilesController->emitStackProjectile( nullptr );
|
||||
|
||||
// wait fo projectile to end
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
@ -162,8 +165,11 @@ void BattleStacksController::stackReset(const CStack * stack)
|
||||
auto animation = iter->second;
|
||||
|
||||
if(stack->alive() && animation->isDeadOrDying())
|
||||
{
|
||||
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,14 +416,13 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
|
||||
facingRight(attackedInfo.attacker));
|
||||
|
||||
if (needsReverse)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -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.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
|
||||
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition());
|
||||
addNewAnim(new CResurrectionAnimation(owner, attackedInfo.defender));
|
||||
});
|
||||
}
|
||||
|
||||
if (attackedInfo.cloneKilled)
|
||||
{
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user