mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-24 08:32:34 +02:00
Refactoring of spell animations, multiple fixes for spell visuals
This commit is contained in:
parent
92ca0d9877
commit
5094fab4d9
@ -756,21 +756,24 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
|
||||
std::vector<std::shared_ptr<const CObstacleInstance>> newObstacles;
|
||||
|
||||
for(auto & change : obstacles)
|
||||
{
|
||||
if(change.operation == BattleChanges::EOperation::ADD)
|
||||
{
|
||||
auto instance = cb->battleGetObstacleByID(change.id);
|
||||
if(instance)
|
||||
battleInt->obstaclePlaced(*instance);
|
||||
newObstacles.push_back(instance);
|
||||
else
|
||||
logNetwork->error("Invalid obstacle instance %d", change.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
battleInt->fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
}
|
||||
|
||||
if (!newObstacles.empty())
|
||||
battleInt->obstaclePlaced(newObstacles);
|
||||
|
||||
battleInt->fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
|
||||
|
@ -93,18 +93,18 @@ void CBattleAnimation::setStackFacingRight(const CStack * stack, bool facingRigh
|
||||
bool CBattleAnimation::checkInitialConditions()
|
||||
{
|
||||
int lowestMoveID = ID;
|
||||
CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
|
||||
CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this);
|
||||
auto * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
|
||||
auto * thSen = dynamic_cast<CPointEffectAnimation *>(this);
|
||||
|
||||
for(auto & elem : pendingAnimations())
|
||||
{
|
||||
CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem);
|
||||
auto * sen = dynamic_cast<CPointEffectAnimation *>(elem);
|
||||
|
||||
// all effect animations can play concurrently with each other
|
||||
if(sen && thSen && sen != thSen)
|
||||
continue;
|
||||
|
||||
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(elem);
|
||||
auto * revAnim = dynamic_cast<CReverseAnimation *>(elem);
|
||||
|
||||
// if there is high-priority reverse animation affecting our stack then this animation will wait
|
||||
if(revAnim && thAnim && revAnim && revAnim->stack->ID == thAnim->stack->ID && revAnim->priority)
|
||||
@ -206,15 +206,15 @@ bool CDefenceAnimation::init()
|
||||
for(auto & elem : pendingAnimations())
|
||||
{
|
||||
|
||||
CDefenceAnimation * defAnim = dynamic_cast<CDefenceAnimation *>(elem);
|
||||
auto * defAnim = dynamic_cast<CDefenceAnimation *>(elem);
|
||||
if(defAnim && defAnim->stack->ID != stack->ID)
|
||||
continue;
|
||||
|
||||
CAttackAnimation * attAnim = dynamic_cast<CAttackAnimation *>(elem);
|
||||
auto * attAnim = dynamic_cast<CAttackAnimation *>(elem);
|
||||
if(attAnim && attAnim->stack->ID != stack->ID)
|
||||
continue;
|
||||
|
||||
CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem);
|
||||
auto * sen = dynamic_cast<CPointEffectAnimation *>(elem);
|
||||
if (sen && attacker == nullptr)
|
||||
return false;
|
||||
|
||||
@ -699,22 +699,13 @@ void CReverseAnimation::setupSecondPart()
|
||||
}
|
||||
|
||||
CRangedAttackAnimation::CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
|
||||
: CAttackAnimation(owner_, attacker, dest_, defender)
|
||||
: CAttackAnimation(owner_, attacker, dest_, defender),
|
||||
projectileEmitted(false)
|
||||
{
|
||||
|
||||
logAnim->info("Ranged attack animation created");
|
||||
}
|
||||
|
||||
|
||||
CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool _catapult, int _catapultDmg)
|
||||
: CRangedAttackAnimation(_owner, attacker, _dest, _attacked),
|
||||
catapultDamage(_catapultDmg),
|
||||
projectileEmitted(false),
|
||||
explosionEmitted(false)
|
||||
{
|
||||
logAnim->debug("Created shooting anim for %s", stack->getName());
|
||||
}
|
||||
|
||||
bool CShootingAnimation::init()
|
||||
bool CRangedAttackAnimation::init()
|
||||
{
|
||||
if( !CAttackAnimation::checkInitialConditions() )
|
||||
return false;
|
||||
@ -737,13 +728,14 @@ bool CShootingAnimation::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
logAnim->info("Ranged attack animation initialized");
|
||||
setAnimationGroup();
|
||||
initializeProjectile();
|
||||
shooting = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CShootingAnimation::setAnimationGroup()
|
||||
void CRangedAttackAnimation::setAnimationGroup()
|
||||
{
|
||||
Point shooterPos = stackAnimation(attackingStack)->pos.topLeft();
|
||||
Point shotTarget = owner->stacksController->getStackPositionAtHex(dest, attackedStack) + Point(225, 225);
|
||||
@ -755,14 +747,14 @@ void CShootingAnimation::setAnimationGroup()
|
||||
|
||||
// Calculate projectile start position. Offsets are read out of the CRANIM.TXT.
|
||||
if (projectileAngle > straightAngle)
|
||||
group = CCreatureAnim::SHOOT_UP;
|
||||
group = getUpwardsGroup();
|
||||
else if (projectileAngle < -straightAngle)
|
||||
group = CCreatureAnim::SHOOT_DOWN;
|
||||
group = getDownwardsGroup();
|
||||
else
|
||||
group = CCreatureAnim::SHOOT_FRONT;
|
||||
group = getForwardGroup();
|
||||
}
|
||||
|
||||
void CShootingAnimation::initializeProjectile()
|
||||
void CRangedAttackAnimation::initializeProjectile()
|
||||
{
|
||||
const CCreature *shooterInfo = getCreature();
|
||||
Point shotTarget = owner->stacksController->getStackPositionAtHex(dest, attackedStack) + Point(225, 225);
|
||||
@ -789,38 +781,36 @@ void CShootingAnimation::initializeProjectile()
|
||||
assert(0);
|
||||
}
|
||||
|
||||
owner->projectilesController->createProjectile(attackingStack, attackedStack, shotOrigin, shotTarget);
|
||||
createProjectile(shotOrigin, shotTarget);
|
||||
}
|
||||
|
||||
void CShootingAnimation::emitProjectile()
|
||||
void CRangedAttackAnimation::emitProjectile()
|
||||
{
|
||||
logAnim->info("Ranged attack projectile emitted");
|
||||
owner->projectilesController->emitStackProjectile(attackingStack);
|
||||
projectileEmitted = true;
|
||||
}
|
||||
|
||||
void CShootingAnimation::nextFrame()
|
||||
void CRangedAttackAnimation::nextFrame()
|
||||
{
|
||||
for(auto & it : pendingAnimations())
|
||||
{
|
||||
CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it);
|
||||
CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it);
|
||||
if( (anim && anim->stack->ID == stack->ID) || (anim2 && anim2->stack->ID == stack->ID && anim2->priority ) )
|
||||
{
|
||||
assert(0); // FIXME: our stack started to move even though we are playing shooting animation? How?
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// animation should be paused if there is an active projectile
|
||||
if (projectileEmitted)
|
||||
{
|
||||
if (owner->projectilesController->hasActiveProjectile(attackingStack))
|
||||
{
|
||||
stackAnimation(attackingStack)->pause();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
stackAnimation(attackingStack)->play();
|
||||
emitExplosion();
|
||||
}
|
||||
}
|
||||
|
||||
CAttackAnimation::nextFrame();
|
||||
@ -831,41 +821,25 @@ void CShootingAnimation::nextFrame()
|
||||
|
||||
assert(stackAnimation(attackingStack)->isShooting());
|
||||
|
||||
logAnim->info("Ranged attack executing, %d / %d / %d",
|
||||
stackAnimation(attackingStack)->getCurrentFrame(),
|
||||
shooterInfo->animation.attackClimaxFrame,
|
||||
stackAnimation(attackingStack)->framesInGroup(group));
|
||||
|
||||
// emit projectile once animation playback reached "climax" frame
|
||||
if ( stackAnimation(attackingStack)->getCurrentFrame() >= shooterInfo->animation.attackClimaxFrame )
|
||||
{
|
||||
emitProjectile();
|
||||
stackAnimation(attackingStack)->pause();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CShootingAnimation::emitExplosion()
|
||||
{
|
||||
if (attackedStack)
|
||||
return;
|
||||
|
||||
if (explosionEmitted)
|
||||
return;
|
||||
|
||||
explosionEmitted = true;
|
||||
|
||||
Point shotTarget = owner->stacksController->getStackPositionAtHex(dest, attackedStack) + Point(225, 225) - Point(126, 105);
|
||||
|
||||
owner->stacksController->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", shotTarget.x, shotTarget.y));
|
||||
|
||||
if(catapultDamage > 0)
|
||||
{
|
||||
CCS->soundh->playSound("WALLHIT");
|
||||
}
|
||||
else
|
||||
{
|
||||
CCS->soundh->playSound("WALLMISS");
|
||||
}
|
||||
}
|
||||
|
||||
CShootingAnimation::~CShootingAnimation()
|
||||
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);
|
||||
|
||||
@ -877,180 +851,167 @@ CShootingAnimation::~CShootingAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender)
|
||||
: CRangedAttackAnimation(owner_, attacker, dest_, defender)
|
||||
CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked)
|
||||
: CRangedAttackAnimation(_owner, attacker, _dest, _attacked)
|
||||
{
|
||||
logAnim->debug("Created shooting anim for %s", stack->getName());
|
||||
}
|
||||
|
||||
void CShootingAnimation::createProjectile(const Point & from, const Point & dest) const
|
||||
{
|
||||
owner->projectilesController->createProjectile(attackingStack, attackedStack, from, dest);
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CShootingAnimation::getUpwardsGroup() const
|
||||
{
|
||||
return CCreatureAnim::SHOOT_UP;
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CShootingAnimation::getForwardGroup() const
|
||||
{
|
||||
return CCreatureAnim::SHOOT_FRONT;
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CShootingAnimation::getDownwardsGroup() const
|
||||
{
|
||||
return CCreatureAnim::SHOOT_DOWN;
|
||||
}
|
||||
|
||||
CCatapultAnimation::CCatapultAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, int _catapultDmg)
|
||||
: CShootingAnimation(_owner, attacker, _dest, _attacked),
|
||||
catapultDamage(_catapultDmg),
|
||||
explosionEmitted(false)
|
||||
{
|
||||
logAnim->debug("Created shooting anim for %s", stack->getName());
|
||||
}
|
||||
|
||||
void CCatapultAnimation::nextFrame()
|
||||
{
|
||||
CShootingAnimation::nextFrame();
|
||||
|
||||
if ( explosionEmitted)
|
||||
return;
|
||||
|
||||
if ( !projectileEmitted)
|
||||
return;
|
||||
|
||||
if (owner->projectilesController->hasActiveProjectile(attackingStack))
|
||||
return;
|
||||
|
||||
explosionEmitted = true;
|
||||
Point shotTarget = owner->stacksController->getStackPositionAtHex(dest, attackedStack) + Point(225, 225) - Point(126, 105);
|
||||
|
||||
if(catapultDamage > 0)
|
||||
owner->stacksController->addNewAnim( new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", shotTarget));
|
||||
else
|
||||
owner->stacksController->addNewAnim( new CPointEffectAnimation(owner, soundBase::WALLMISS, "CSGRCK.DEF", shotTarget));
|
||||
}
|
||||
|
||||
void CCatapultAnimation::createProjectile(const Point & from, const Point & dest) const
|
||||
{
|
||||
owner->projectilesController->createCatapultProjectile(attackingStack, from, dest);
|
||||
}
|
||||
|
||||
|
||||
CCastAnimation::CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell)
|
||||
: CRangedAttackAnimation(owner_, attacker, dest_, defender),
|
||||
spell(spell)
|
||||
{
|
||||
assert(dest.isValid());// FIXME: when?
|
||||
|
||||
if(!dest_.isValid() && defender)
|
||||
dest = defender->getPosition();
|
||||
}
|
||||
|
||||
bool CCastAnimation::init()
|
||||
CCreatureAnim::EAnimType CCastAnimation::findValidGroup( const std::vector<CCreatureAnim::EAnimType> candidates ) const
|
||||
{
|
||||
if(!CAttackAnimation::checkInitialConditions())
|
||||
return false;
|
||||
|
||||
if(!attackingStack || myAnim->isDeadOrDying())
|
||||
for ( auto group : candidates)
|
||||
{
|
||||
delete this;
|
||||
return false;
|
||||
if(myAnim->framesInGroup(group) > 0)
|
||||
return group;
|
||||
}
|
||||
|
||||
//reverse unit if necessary
|
||||
if(attackedStack)
|
||||
{
|
||||
if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), stackFacingRight(attackingStack), attackingStack->doubleWide(), stackFacingRight(attackedStack)))
|
||||
{
|
||||
owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, stackFacingRight(attackingStack), false, false))
|
||||
{
|
||||
owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: display spell projectile here
|
||||
|
||||
static const double straightAngle = 0.2;
|
||||
|
||||
|
||||
Point fromPos;
|
||||
Point destPos;
|
||||
|
||||
// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
|
||||
fromPos = stackAnimation(attackingStack)->pos.topLeft();
|
||||
//xycoord = owner->stacksController->getStackPositionAtHex(shooter->getPosition(), shooter);
|
||||
|
||||
destPos = owner->stacksController->getStackPositionAtHex(dest, attackedStack);
|
||||
|
||||
|
||||
double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x));
|
||||
if(attackingStack->getPosition() < dest)
|
||||
projectileAngle = -projectileAngle;
|
||||
|
||||
|
||||
if(projectileAngle > straightAngle)
|
||||
group = CCreatureAnim::VCMI_CAST_UP;
|
||||
else if(projectileAngle < -straightAngle)
|
||||
group = CCreatureAnim::VCMI_CAST_DOWN;
|
||||
else
|
||||
group = CCreatureAnim::VCMI_CAST_FRONT;
|
||||
|
||||
//fall back to H3 cast/2hex
|
||||
//even if creature have 2hex attack instead of cast it is ok since we fall back to attack anyway
|
||||
if(myAnim->framesInGroup(group) == 0)
|
||||
{
|
||||
if(projectileAngle > straightAngle)
|
||||
group = CCreatureAnim::CAST_UP;
|
||||
else if(projectileAngle < -straightAngle)
|
||||
group = CCreatureAnim::CAST_DOWN;
|
||||
else
|
||||
group = CCreatureAnim::CAST_FRONT;
|
||||
}
|
||||
|
||||
//fall back to ranged attack
|
||||
if(myAnim->framesInGroup(group) == 0)
|
||||
{
|
||||
if(projectileAngle > straightAngle)
|
||||
group = CCreatureAnim::SHOOT_UP;
|
||||
else if(projectileAngle < -straightAngle)
|
||||
group = CCreatureAnim::SHOOT_DOWN;
|
||||
else
|
||||
group = CCreatureAnim::SHOOT_FRONT;
|
||||
}
|
||||
|
||||
//fall back to normal attack
|
||||
if(myAnim->framesInGroup(group) == 0)
|
||||
{
|
||||
if(projectileAngle > straightAngle)
|
||||
group = CCreatureAnim::ATTACK_UP;
|
||||
else if(projectileAngle < -straightAngle)
|
||||
group = CCreatureAnim::ATTACK_DOWN;
|
||||
else
|
||||
group = CCreatureAnim::ATTACK_FRONT;
|
||||
}
|
||||
|
||||
return true;
|
||||
assert(0);
|
||||
return CCreatureAnim::HOLDING;
|
||||
}
|
||||
|
||||
void CCastAnimation::nextFrame()
|
||||
CCreatureAnim::EAnimType CCastAnimation::getUpwardsGroup() const
|
||||
{
|
||||
for(auto & it : pendingAnimations())
|
||||
{
|
||||
CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it);
|
||||
if(anim && anim->stack->ID == stack->ID && anim->priority)
|
||||
return;
|
||||
}
|
||||
|
||||
if(myAnim->getType() != group)
|
||||
{
|
||||
myAnim->setType(group);
|
||||
myAnim->onAnimationReset += [&](){ delete this; };
|
||||
}
|
||||
|
||||
CBattleAnimation::nextFrame();
|
||||
return findValidGroup({
|
||||
CCreatureAnim::VCMI_CAST_UP,
|
||||
CCreatureAnim::CAST_UP,
|
||||
CCreatureAnim::SHOOT_UP,
|
||||
CCreatureAnim::ATTACK_UP
|
||||
});
|
||||
}
|
||||
|
||||
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip, bool _alignToBottom)
|
||||
: CBattleAnimation(_owner),
|
||||
destTile(BattleHex::INVALID),
|
||||
x(_x),
|
||||
y(_y),
|
||||
dx(_dx),
|
||||
dy(_dy),
|
||||
Vflip(_Vflip),
|
||||
alignToBottom(_alignToBottom)
|
||||
CCreatureAnim::EAnimType CCastAnimation::getForwardGroup() const
|
||||
{
|
||||
logAnim->debug("Created effect animation %s", _customAnim);
|
||||
|
||||
customAnim = std::make_shared<CAnimation>(_customAnim);
|
||||
return findValidGroup({
|
||||
CCreatureAnim::VCMI_CAST_FRONT,
|
||||
CCreatureAnim::CAST_FRONT,
|
||||
CCreatureAnim::SHOOT_FRONT,
|
||||
CCreatureAnim::ATTACK_FRONT
|
||||
});
|
||||
}
|
||||
|
||||
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx, int _dy)
|
||||
: CBattleAnimation(_owner),
|
||||
destTile(BattleHex::INVALID),
|
||||
customAnim(_customAnim),
|
||||
x(_x),
|
||||
y(_y),
|
||||
dx(_dx),
|
||||
dy(_dy),
|
||||
Vflip(false),
|
||||
alignToBottom(false)
|
||||
CCreatureAnim::EAnimType CCastAnimation::getDownwardsGroup() const
|
||||
{
|
||||
logAnim->debug("Created custom effect animation");
|
||||
return findValidGroup({
|
||||
CCreatureAnim::VCMI_CAST_DOWN,
|
||||
CCreatureAnim::CAST_DOWN,
|
||||
CCreatureAnim::SHOOT_DOWN,
|
||||
CCreatureAnim::ATTACK_DOWN
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
CEffectAnimation::CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip, bool _alignToBottom)
|
||||
: CBattleAnimation(_owner),
|
||||
destTile(_destTile),
|
||||
x(-1),
|
||||
y(-1),
|
||||
dx(0),
|
||||
dy(0),
|
||||
Vflip(_Vflip),
|
||||
alignToBottom(_alignToBottom)
|
||||
void CCastAnimation::createProjectile(const Point & from, const Point & dest) const
|
||||
{
|
||||
logAnim->debug("Created effect animation %s", _customAnim);
|
||||
customAnim = std::make_shared<CAnimation>(_customAnim);
|
||||
owner->projectilesController->createSpellProjectile(attackingStack, attackedStack, from, dest, spell);
|
||||
}
|
||||
|
||||
bool CEffectAnimation::init()
|
||||
CPointEffectAnimation::CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, int effects):
|
||||
CBattleAnimation(_owner),
|
||||
animation(std::make_shared<CAnimation>(animationName)),
|
||||
sound(sound),
|
||||
effectFlags(effects),
|
||||
soundPlayed(false),
|
||||
soundFinished(false),
|
||||
effectFinished(false)
|
||||
{
|
||||
}
|
||||
|
||||
CPointEffectAnimation::CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, std::vector<BattleHex> pos, int effects):
|
||||
CPointEffectAnimation(_owner, sound, animationName, effects)
|
||||
{
|
||||
battlehexes = pos;
|
||||
}
|
||||
|
||||
CPointEffectAnimation::CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, BattleHex pos, int effects):
|
||||
CPointEffectAnimation(_owner, sound, animationName, effects)
|
||||
{
|
||||
assert(pos.isValid());
|
||||
battlehexes.push_back(pos);
|
||||
}
|
||||
|
||||
CPointEffectAnimation::CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, std::vector<Point> pos, int effects):
|
||||
CPointEffectAnimation(_owner, sound, animationName, effects)
|
||||
{
|
||||
positions = pos;
|
||||
}
|
||||
|
||||
CPointEffectAnimation::CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, Point pos, int effects):
|
||||
CPointEffectAnimation(_owner, sound, animationName, effects)
|
||||
{
|
||||
positions.push_back(pos);
|
||||
}
|
||||
|
||||
bool CPointEffectAnimation::init()
|
||||
{
|
||||
if(!CBattleAnimation::checkInitialConditions())
|
||||
return false;
|
||||
|
||||
const bool areaEffect = (!destTile.isValid() && x == -1 && y == -1);
|
||||
|
||||
std::shared_ptr<CAnimation> animation = customAnim;
|
||||
|
||||
animation->preload();
|
||||
if(Vflip)
|
||||
animation->verticalFlip();
|
||||
|
||||
auto first = animation->getImage(0, 0, true);
|
||||
if(!first)
|
||||
@ -1059,72 +1020,105 @@ bool CEffectAnimation::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
if(areaEffect) //f.e. armageddon
|
||||
if (positions.empty() && battlehexes.empty())
|
||||
{
|
||||
//armageddon, create screen fill
|
||||
for(int i=0; i * first->width() < owner->pos.w ; ++i)
|
||||
{
|
||||
for(int j=0; j * first->height() < owner->pos.h ; ++j)
|
||||
{
|
||||
BattleEffect be;
|
||||
be.effectID = ID;
|
||||
be.animation = animation;
|
||||
be.currentFrame = 0;
|
||||
|
||||
be.x = i * first->width() + owner->pos.x;
|
||||
be.y = j * first->height() + owner->pos.y;
|
||||
be.position = BattleHex::INVALID;
|
||||
|
||||
owner->effectsController->battleEffects.push_back(be);
|
||||
}
|
||||
}
|
||||
positions.push_back(Point(i * first->width(), j * first->height()));
|
||||
}
|
||||
else // Effects targeted at a specific creature/hex.
|
||||
|
||||
BattleEffect be;
|
||||
be.effectID = ID;
|
||||
be.animation = animation;
|
||||
be.currentFrame = 0;
|
||||
|
||||
for ( auto const position : positions)
|
||||
{
|
||||
const CStack * destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(destTile, false);
|
||||
BattleEffect be;
|
||||
be.effectID = ID;
|
||||
be.animation = animation;
|
||||
be.currentFrame = 0;
|
||||
be.x = position.x;
|
||||
be.y = position.y;
|
||||
be.position = BattleHex::INVALID;
|
||||
|
||||
owner->effectsController->battleEffects.push_back(be);
|
||||
}
|
||||
|
||||
//todo: lightning anim frame count override
|
||||
for ( auto const tile : battlehexes)
|
||||
{
|
||||
const CStack * destStack = owner->getCurrentPlayerInterface()->cb->battleGetStackByPos(tile, false);
|
||||
|
||||
// if(effect == 1)
|
||||
// be.maxFrame = 3;
|
||||
assert(tile.isValid());
|
||||
if(!tile.isValid())
|
||||
continue;
|
||||
|
||||
be.x = x;
|
||||
be.y = y;
|
||||
if(destTile.isValid())
|
||||
{
|
||||
Rect tilePos = owner->fieldController->hexPosition(destTile);
|
||||
if(x == -1)
|
||||
be.x = tilePos.x + tilePos.w/2 - first->width()/2;
|
||||
if(y == -1)
|
||||
{
|
||||
if(alignToBottom)
|
||||
be.y = tilePos.y + tilePos.h - first->height();
|
||||
else
|
||||
be.y = tilePos.y - first->height()/2;
|
||||
}
|
||||
Rect tilePos = owner->fieldController->hexPosition(tile);
|
||||
be.position = tile;
|
||||
be.x = tilePos.x + tilePos.w/2 - first->width()/2;
|
||||
|
||||
// Correction for 2-hex creatures.
|
||||
if(destStack != nullptr && destStack->doubleWide())
|
||||
be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
|
||||
}
|
||||
if(destStack && destStack->doubleWide()) // Correction for 2-hex creatures.
|
||||
be.x += (destStack->side == BattleSide::ATTACKER ? -1 : 1)*tilePos.w/2;
|
||||
|
||||
assert(be.x != -1 && be.y != -1);
|
||||
|
||||
//Indicate if effect should be drawn on top of everything or just on top of the hex
|
||||
be.position = destTile;
|
||||
if (alignToBottom())
|
||||
be.y = tilePos.y + tilePos.h - first->height();
|
||||
else
|
||||
be.y = tilePos.y - first->height()/2;
|
||||
|
||||
owner->effectsController->battleEffects.push_back(be);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CEffectAnimation::nextFrame()
|
||||
void CPointEffectAnimation::nextFrame()
|
||||
{
|
||||
playSound();
|
||||
playEffect();
|
||||
|
||||
if (soundFinished && effectFinished)
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool CPointEffectAnimation::alignToBottom() const
|
||||
{
|
||||
return effectFlags & ALIGN_TO_BOTTOM;
|
||||
}
|
||||
|
||||
bool CPointEffectAnimation::waitForSound() const
|
||||
{
|
||||
return effectFlags & WAIT_FOR_SOUND;
|
||||
}
|
||||
|
||||
void CPointEffectAnimation::onEffectFinished()
|
||||
{
|
||||
effectFinished = true;
|
||||
clearEffect();
|
||||
}
|
||||
|
||||
void CPointEffectAnimation::onSoundFinished()
|
||||
{
|
||||
soundFinished = true;
|
||||
}
|
||||
|
||||
void CPointEffectAnimation::playSound()
|
||||
{
|
||||
if (soundPlayed)
|
||||
return;
|
||||
|
||||
soundPlayed = true;
|
||||
if (sound == soundBase::invalid)
|
||||
{
|
||||
onSoundFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
int channel = CCS->soundh->playSound(sound);
|
||||
|
||||
if (!waitForSound() || channel == -1)
|
||||
onSoundFinished();
|
||||
else
|
||||
CCS->soundh->setCallback(channel, [&](){ onSoundFinished(); });
|
||||
}
|
||||
|
||||
void CPointEffectAnimation::playEffect()
|
||||
{
|
||||
//notice: there may be more than one effect in owner->battleEffects correcponding to this animation (ie. armageddon)
|
||||
for(auto & elem : owner->effectsController->battleEffects)
|
||||
{
|
||||
if(elem.effectID == ID)
|
||||
@ -1133,19 +1127,14 @@ void CEffectAnimation::nextFrame()
|
||||
|
||||
if(elem.currentFrame >= elem.animation->size())
|
||||
{
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
elem.x += dx;
|
||||
elem.y += dy;
|
||||
onEffectFinished();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CEffectAnimation::~CEffectAnimation()
|
||||
void CPointEffectAnimation::clearEffect()
|
||||
{
|
||||
auto & effects = owner->effectsController->battleEffects;
|
||||
|
||||
@ -1157,3 +1146,43 @@ CEffectAnimation::~CEffectAnimation()
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
CPointEffectAnimation::~CPointEffectAnimation()
|
||||
{
|
||||
assert(effectFinished);
|
||||
assert(soundFinished);
|
||||
}
|
||||
|
||||
CWaitingAnimation::CWaitingAnimation(CBattleInterface * owner_):
|
||||
CBattleAnimation(owner_)
|
||||
{}
|
||||
|
||||
void CWaitingAnimation::nextFrame()
|
||||
{
|
||||
// initialization conditions fulfilled, delay is over
|
||||
delete this;
|
||||
}
|
||||
|
||||
CWaitingProjectileAnimation::CWaitingProjectileAnimation(CBattleInterface * owner_, const CStack * shooter):
|
||||
CWaitingAnimation(owner_),
|
||||
shooter(shooter)
|
||||
{}
|
||||
|
||||
bool CWaitingProjectileAnimation::init()
|
||||
{
|
||||
for(auto & elem : pendingAnimations())
|
||||
{
|
||||
auto * attackAnim = dynamic_cast<CRangedAttackAnimation *>(elem);
|
||||
|
||||
if( attackAnim && shooter && attackAnim->stack->ID == shooter->ID && !attackAnim->isInitialized() )
|
||||
{
|
||||
// there is ongoing ranged attack that involves our stack, but projectile was not created yet
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(owner->projectilesController->hasActiveProjectile(shooter))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/battle/BattleHex.h"
|
||||
#include "../../lib/CSoundBase.h"
|
||||
#include "../widgets/Images.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -128,11 +129,13 @@ public:
|
||||
CMeleeAttackAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
|
||||
};
|
||||
|
||||
/// Base class for all animations that play during stack movement
|
||||
class CStackMoveAnimation : public CBattleStackAnimation
|
||||
{
|
||||
public:
|
||||
BattleHex currentHex;
|
||||
|
||||
protected:
|
||||
CStackMoveAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex _currentHex);
|
||||
};
|
||||
|
||||
@ -193,60 +196,133 @@ public:
|
||||
|
||||
class CRangedAttackAnimation : public CAttackAnimation
|
||||
{
|
||||
public:
|
||||
CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
/// Shooting attack
|
||||
class CShootingAnimation : public CRangedAttackAnimation
|
||||
{
|
||||
private:
|
||||
bool projectileEmitted;
|
||||
bool explosionEmitted;
|
||||
int catapultDamage;
|
||||
|
||||
void setAnimationGroup();
|
||||
void initializeProjectile();
|
||||
void emitProjectile();
|
||||
void emitExplosion();
|
||||
|
||||
protected:
|
||||
bool projectileEmitted;
|
||||
|
||||
virtual CCreatureAnim::EAnimType getUpwardsGroup() const = 0;
|
||||
virtual CCreatureAnim::EAnimType getForwardGroup() const = 0;
|
||||
virtual CCreatureAnim::EAnimType getDownwardsGroup() const = 0;
|
||||
|
||||
virtual void createProjectile(const Point & from, const Point & dest) const = 0;
|
||||
|
||||
public:
|
||||
CRangedAttackAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest, const CStack * defender);
|
||||
~CRangedAttackAnimation();
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
//last two params only for catapult attacks
|
||||
CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest,
|
||||
const CStack * _attacked, bool _catapult = false, int _catapultDmg = 0);
|
||||
~CShootingAnimation();
|
||||
/// Shooting attack
|
||||
class CShootingAnimation : public CRangedAttackAnimation
|
||||
{
|
||||
CCreatureAnim::EAnimType getUpwardsGroup() const override;
|
||||
CCreatureAnim::EAnimType getForwardGroup() const override;
|
||||
CCreatureAnim::EAnimType getDownwardsGroup() const override;
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
|
||||
public:
|
||||
CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex dest, const CStack * defender);
|
||||
|
||||
};
|
||||
|
||||
/// Catapult attack
|
||||
class CCatapultAnimation : public CShootingAnimation
|
||||
{
|
||||
private:
|
||||
bool explosionEmitted;
|
||||
int catapultDamage;
|
||||
|
||||
public:
|
||||
CCatapultAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
class CCastAnimation : public CRangedAttackAnimation
|
||||
{
|
||||
public:
|
||||
CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender);
|
||||
const CSpell * spell;
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
CCreatureAnim::EAnimType findValidGroup( const std::vector<CCreatureAnim::EAnimType> candidates ) const;
|
||||
CCreatureAnim::EAnimType getUpwardsGroup() const override;
|
||||
CCreatureAnim::EAnimType getForwardGroup() const override;
|
||||
CCreatureAnim::EAnimType getDownwardsGroup() const override;
|
||||
|
||||
void createProjectile(const Point & from, const Point & dest) const override;
|
||||
|
||||
public:
|
||||
CCastAnimation(CBattleInterface * owner_, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
|
||||
};
|
||||
|
||||
/// This class manages effect animation
|
||||
class CEffectAnimation : public CBattleAnimation
|
||||
/// Class that plays effect at one or more positions along with (single) sound effect
|
||||
class CPointEffectAnimation : public CBattleAnimation
|
||||
{
|
||||
private:
|
||||
BattleHex destTile;
|
||||
std::shared_ptr<CAnimation> customAnim;
|
||||
int x, y, dx, dy;
|
||||
bool Vflip;
|
||||
bool alignToBottom;
|
||||
soundBase::soundID sound;
|
||||
bool soundPlayed;
|
||||
bool soundFinished;
|
||||
bool effectFinished;
|
||||
int effectFlags;
|
||||
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
std::vector<Point> positions;
|
||||
std::vector<BattleHex> battlehexes;
|
||||
|
||||
bool alignToBottom() const;
|
||||
bool waitForSound() const;
|
||||
|
||||
void onEffectFinished();
|
||||
void onSoundFinished();
|
||||
void clearEffect();
|
||||
|
||||
void playSound();
|
||||
void playEffect();
|
||||
|
||||
public:
|
||||
enum EEffectFlags
|
||||
{
|
||||
ALIGN_TO_BOTTOM = 1,
|
||||
WAIT_FOR_SOUND = 2
|
||||
};
|
||||
|
||||
/// Create animation with screen-wide effect
|
||||
CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, int effects = 0);
|
||||
|
||||
/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
|
||||
CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, Point pos , int effects = 0);
|
||||
CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, std::vector<Point> pos , int effects = 0);
|
||||
|
||||
/// Create animation positioned at certain hex(es)
|
||||
CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, BattleHex pos , int effects = 0);
|
||||
CPointEffectAnimation(CBattleInterface * _owner, soundBase::soundID sound, std::string animationName, std::vector<BattleHex> pos, int effects = 0);
|
||||
~CPointEffectAnimation();
|
||||
|
||||
bool init() override;
|
||||
void nextFrame() override;
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx = 0, int _dy = 0, bool _Vflip = false, bool _alignToBottom = false);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::shared_ptr<CAnimation> _customAnim, int _x, int _y, int _dx = 0, int _dy = 0);
|
||||
|
||||
CEffectAnimation(CBattleInterface * _owner, std::string _customAnim, BattleHex _destTile, bool _Vflip = false, bool _alignToBottom = false);
|
||||
~CEffectAnimation();
|
||||
};
|
||||
|
||||
/// Base class (e.g. for use in dynamic_cast's) for "animations" that wait for certain event
|
||||
class CWaitingAnimation : public CBattleAnimation
|
||||
{
|
||||
protected:
|
||||
CWaitingAnimation(CBattleInterface * owner_);
|
||||
public:
|
||||
void nextFrame() override;
|
||||
};
|
||||
|
||||
/// Class that waits till projectile of certain shooter hits a target
|
||||
class CWaitingProjectileAnimation : public CWaitingAnimation
|
||||
{
|
||||
const CStack * shooter;
|
||||
public:
|
||||
CWaitingProjectileAnimation(CBattleInterface * owner_, const CStack * shooter);
|
||||
|
||||
bool init() override;
|
||||
};
|
||||
|
@ -35,27 +35,26 @@ CBattleEffectsController::CBattleEffectsController(CBattleInterface * owner):
|
||||
|
||||
void CBattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile)
|
||||
{
|
||||
std::string customAnim = graphics->battleACToDef[effect][0];
|
||||
|
||||
owner->stacksController->addNewAnim(new CEffectAnimation(owner, customAnim, destTile));//FIXME: check positioning for double-hex creatures
|
||||
displayEffect(effect, soundBase::invalid, destTile);
|
||||
}
|
||||
|
||||
void CBattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile)
|
||||
{
|
||||
displayEffect(effect, destTile);
|
||||
if(soundBase::soundID(soundID) != soundBase::invalid )
|
||||
CCS->soundh->playSound(soundBase::soundID(soundID));
|
||||
std::string customAnim = graphics->battleACToDef[effect][0];
|
||||
|
||||
owner->stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::soundID(soundID), customAnim, destTile));
|
||||
}
|
||||
|
||||
void CBattleEffectsController::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
|
||||
{
|
||||
for(const CustomEffectInfo & one : customEffects)
|
||||
{
|
||||
if(one.sound != 0)
|
||||
CCS->soundh->playSound(soundBase::soundID(one.sound));
|
||||
const CStack * s = owner->curInt->cb->battleGetStackByID(one.stack, false);
|
||||
if(s && one.effect != 0)
|
||||
displayEffect(EBattleEffect::EBattleEffect(one.effect), s->getPosition());
|
||||
|
||||
assert(s);
|
||||
assert(one.effect != 0);
|
||||
|
||||
displayEffect(EBattleEffect::EBattleEffect(one.effect), soundBase::soundID(one.sound), s->getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ struct SDL_Surface;
|
||||
class CAnimation;
|
||||
class CCanvas;
|
||||
class CBattleInterface;
|
||||
class CEffectAnimation;
|
||||
class CPointEffectAnimation;
|
||||
|
||||
namespace EBattleEffect
|
||||
{
|
||||
@ -36,6 +36,7 @@ namespace EBattleEffect
|
||||
BAD_MORALE = 30,
|
||||
BAD_LUCK = 48,
|
||||
RESURRECT = 50,
|
||||
DRAIN_LIFE = 52, // hardcoded constant in CGameHandler
|
||||
POISON = 67,
|
||||
DEATH_BLOW = 73,
|
||||
REGENERATION = 74,
|
||||
@ -69,12 +70,14 @@ public:
|
||||
|
||||
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
|
||||
|
||||
void displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile); //displays custom effect on the battlefield
|
||||
void displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile); //displays custom effect on the battlefield
|
||||
//displays custom effect on the battlefield
|
||||
void displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile);
|
||||
void displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile);
|
||||
//void displayEffects(EBattleEffect::EBattleEffect effect, uint32_t soundID, const std::vector<BattleHex> & destTiles);
|
||||
|
||||
void battleTriggerEffect(const BattleTriggerEffect & bte);
|
||||
|
||||
void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & destTile);
|
||||
|
||||
|
||||
friend class CEffectAnimation; // currently, battleEffects is largely managed by CEffectAnimation, TODO: move this logic into CBattleEffectsController
|
||||
friend class CPointEffectAnimation;
|
||||
};
|
||||
|
@ -81,9 +81,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
||||
tacticsMode = static_cast<bool>(tacticianInterface);
|
||||
|
||||
//create stack queue
|
||||
|
||||
bool embedQueue;
|
||||
|
||||
std::string queueSize = settings["battle"]["queueSize"].String();
|
||||
|
||||
if(queueSize == "auto")
|
||||
@ -120,7 +118,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
||||
actionsController.reset( new CBattleActionsController(this));
|
||||
effectsController.reset(new CBattleEffectsController(this));
|
||||
|
||||
|
||||
//loading hero animations
|
||||
if(hero1) // attacking hero
|
||||
{
|
||||
@ -182,7 +179,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
|
||||
CCS->musich->playMusicFromSet("battle", true, true);
|
||||
battleActionsStarted = true;
|
||||
activateStack();
|
||||
controlPanel->blockUI(settings["session"]["spectate"].Bool());
|
||||
controlPanel->blockUI(settings["session"]["spectate"].Bool() || stacksController->getActiveStack() == nullptr);
|
||||
battleIntroSoundChannel = -1;
|
||||
}
|
||||
};
|
||||
@ -203,12 +200,9 @@ CBattleInterface::~CBattleInterface()
|
||||
deactivate();
|
||||
}
|
||||
|
||||
//TODO: play AI tracks if battle was during AI turn
|
||||
//if (!curInt->makingTurn)
|
||||
//CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
|
||||
|
||||
if (adventureInt && adventureInt->selection)
|
||||
{
|
||||
//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
|
||||
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
|
||||
CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
|
||||
}
|
||||
@ -360,9 +354,9 @@ void CBattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attacked
|
||||
for(ui8 side = 0; side < 2; side++)
|
||||
{
|
||||
if(killedBySide.at(side) > killedBySide.at(1-side))
|
||||
setHeroAnimation(side, 2);
|
||||
setHeroAnimation(side, CCreatureAnim::HERO_DEFEAT);
|
||||
else if(killedBySide.at(side) < killedBySide.at(1-side))
|
||||
setHeroAnimation(side, 3);
|
||||
setHeroAnimation(side, CCreatureAnim::HERO_VICTORY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,6 +482,7 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
const SpellID spellID = sc->spellID;
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
|
||||
assert(spell);
|
||||
if(!spell)
|
||||
return;
|
||||
|
||||
@ -496,64 +491,31 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
if (!castSoundPath.empty())
|
||||
CCS->soundh->playSound(castSoundPath);
|
||||
|
||||
const auto casterStackID = sc->casterStack;
|
||||
const CStack * casterStack = nullptr;
|
||||
if(casterStackID >= 0)
|
||||
if ( sc->activeCast )
|
||||
{
|
||||
casterStack = curInt->cb->battleGetStackByID(casterStackID);
|
||||
}
|
||||
const CStack * casterStack = curInt->cb->battleGetStackByID(sc->casterStack);
|
||||
|
||||
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position by default
|
||||
{
|
||||
if(casterStack != nullptr)
|
||||
if(casterStack != nullptr )
|
||||
{
|
||||
srccoord = stacksController->getStackPositionAtHex(casterStack->getPosition(), casterStack);
|
||||
srccoord.x += 250;
|
||||
srccoord.y += 240;
|
||||
displaySpellCast(spellID, casterStack->getPosition());
|
||||
|
||||
stacksController->addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell));
|
||||
}
|
||||
}
|
||||
|
||||
if(casterStack != nullptr && sc->activeCast)
|
||||
{
|
||||
//todo: custom cast animation for hero
|
||||
displaySpellCast(spellID, casterStack->getPosition());
|
||||
|
||||
stacksController->addNewAnim(new CCastAnimation(this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile)));
|
||||
}
|
||||
|
||||
waitForAnims(); //wait for cast animation
|
||||
|
||||
//playing projectile animation
|
||||
if (sc->tile.isValid())
|
||||
{
|
||||
Point destcoord = stacksController->getStackPositionAtHex(sc->tile, curInt->cb->battleGetStackByPos(sc->tile)); //position attacked by projectile
|
||||
destcoord.x += 250; destcoord.y += 240;
|
||||
|
||||
//animation angle
|
||||
double angle = atan2(static_cast<double>(destcoord.x - srccoord.x), static_cast<double>(destcoord.y - srccoord.y));
|
||||
bool Vflip = (angle < 0);
|
||||
if (Vflip)
|
||||
angle = -angle;
|
||||
|
||||
std::string animToDisplay = spell->animationInfo.selectProjectile(angle);
|
||||
|
||||
if(!animToDisplay.empty())
|
||||
else
|
||||
if (sc->tile.isValid() && !spell->animationInfo.projectile.empty())
|
||||
{
|
||||
//TODO: calculate inside CEffectAnimation
|
||||
std::shared_ptr<CAnimation> tmp = std::make_shared<CAnimation>(animToDisplay);
|
||||
tmp->load(0, 0);
|
||||
auto first = tmp->getImage(0, 0);
|
||||
// this is spell cast by hero with valid destination & valid projectile -> play animation
|
||||
|
||||
//displaying animation
|
||||
double diffX = (destcoord.x - srccoord.x)*(destcoord.x - srccoord.x);
|
||||
double diffY = (destcoord.y - srccoord.y)*(destcoord.y - srccoord.y);
|
||||
double distance = sqrt(diffX + diffY);
|
||||
const CStack * target = curInt->cb->battleGetStackByPos(sc->tile);
|
||||
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position
|
||||
Point destcoord = stacksController->getStackPositionAtHex(sc->tile, target); //position attacked by projectile
|
||||
destcoord += Point(250, 240); // FIXME: what are these constants?
|
||||
|
||||
int steps = static_cast<int>(distance / AnimationControls::getSpellEffectSpeed() + 1);
|
||||
int dx = (destcoord.x - srccoord.x - first->width())/steps;
|
||||
int dy = (destcoord.y - srccoord.y - first->height())/steps;
|
||||
projectilesController->createSpellProjectile( nullptr, target, srccoord, destcoord, spell);
|
||||
projectilesController->emitStackProjectile( nullptr );
|
||||
|
||||
stacksController->addNewAnim(new CEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
|
||||
// wait fo projectile to end
|
||||
stacksController->addNewAnim(new CWaitingProjectileAnimation(this, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,8 +545,8 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
{
|
||||
Point leftHero = Point(15, 30) + pos;
|
||||
Point rightHero = Point(755, 30) + pos;
|
||||
stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero.x, leftHero.y, 0, 0, false));
|
||||
stacksController->addNewAnim(new CEffectAnimation(this, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero.x, rightHero.y, 0, 0, false));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,7 +588,14 @@ void CBattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue
|
||||
if(animation.pause > 0)
|
||||
stacksController->addNewAnim(new CDummyAnimation(this, animation.pause));
|
||||
else
|
||||
stacksController->addNewAnim(new CEffectAnimation(this, animation.resourceName, destinationTile, false, animation.verticalPosition == VerticalPosition::BOTTOM));
|
||||
{
|
||||
if (!destinationTile.isValid())
|
||||
stacksController->addNewAnim(new CPointEffectAnimation(this, soundBase::invalid, animation.resourceName));
|
||||
else if (animation.verticalPosition == VerticalPosition::BOTTOM)
|
||||
stacksController->addNewAnim(new CPointEffectAnimation(this, soundBase::invalid, animation.resourceName, destinationTile, CPointEffectAnimation::ALIGN_TO_BOTTOM));
|
||||
else
|
||||
stacksController->addNewAnim(new CPointEffectAnimation(this, soundBase::invalid, animation.resourceName, destinationTile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -705,7 +674,7 @@ void CBattleInterface::endAction(const BattleAction* action)
|
||||
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
|
||||
|
||||
if(action->actionType == EActionType::HERO_SPELL)
|
||||
setHeroAnimation(action->side, 0);
|
||||
setHeroAnimation(action->side, CCreatureAnim::HERO_HOLDING);
|
||||
|
||||
stacksController->endAction(action);
|
||||
|
||||
@ -784,7 +753,7 @@ void CBattleInterface::startAction(const BattleAction* action)
|
||||
|
||||
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
|
||||
{
|
||||
setHeroAnimation(action->side, 4);
|
||||
setHeroAnimation(action->side, CCreatureAnim::HERO_CAST_SPELL);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -839,7 +808,7 @@ void CBattleInterface::tacticNextStack(const CStack * current)
|
||||
|
||||
}
|
||||
|
||||
void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
|
||||
void CBattleInterface::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi)
|
||||
{
|
||||
obstacleController->obstaclePlaced(oi);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ public:
|
||||
void hideQueue();
|
||||
void showQueue();
|
||||
|
||||
void obstaclePlaced(const CObstacleInstance & oi);
|
||||
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
|
||||
|
||||
void gateStateChanged(const EGateState state);
|
||||
|
||||
@ -185,7 +185,6 @@ public:
|
||||
|
||||
friend class CBattleResultWindow;
|
||||
friend class CBattleHero;
|
||||
friend class CEffectAnimation;
|
||||
friend class CBattleStackAnimation;
|
||||
friend class CReverseAnimation;
|
||||
friend class CDefenceAnimation;
|
||||
|
@ -29,87 +29,91 @@ CBattleObstacleController::CBattleObstacleController(CBattleInterface * owner):
|
||||
auto obst = owner->curInt->cb->battleGetAllObstacles();
|
||||
for(auto & elem : obst)
|
||||
{
|
||||
if(elem->obstacleType == CObstacleInstance::USUAL)
|
||||
{
|
||||
std::string animationName = elem->getInfo().animation;
|
||||
|
||||
auto cached = animationsCache.find(animationName);
|
||||
|
||||
if(cached == animationsCache.end())
|
||||
{
|
||||
auto animation = std::make_shared<CAnimation>(animationName);
|
||||
animationsCache[animationName] = animation;
|
||||
obstacleAnimations[elem->uniqueID] = animation;
|
||||
animation->preload();
|
||||
}
|
||||
else
|
||||
{
|
||||
obstacleAnimations[elem->uniqueID] = cached->second;
|
||||
}
|
||||
}
|
||||
else if (elem->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
{
|
||||
std::string animationName = elem->getInfo().animation;
|
||||
|
||||
auto cached = animationsCache.find(animationName);
|
||||
|
||||
if(cached == animationsCache.end())
|
||||
{
|
||||
auto animation = std::make_shared<CAnimation>();
|
||||
animation->setCustom(animationName, 0, 0);
|
||||
animationsCache[animationName] = animation;
|
||||
obstacleAnimations[elem->uniqueID] = animation;
|
||||
animation->preload();
|
||||
}
|
||||
else
|
||||
{
|
||||
obstacleAnimations[elem->uniqueID] = cached->second;
|
||||
}
|
||||
}
|
||||
if ( elem->obstacleType == CObstacleInstance::MOAT )
|
||||
continue; // handled by siege controller;
|
||||
loadObstacleImage(*elem);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleObstacleController::obstaclePlaced(const CObstacleInstance & oi)
|
||||
void CBattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
|
||||
{
|
||||
//so when multiple obstacles are added, they show up one after another
|
||||
owner->waitForAnims();
|
||||
std::string animationName;
|
||||
|
||||
//soundBase::soundID sound; // FIXME(v.markovtsev): soundh->playSound() is commented in the end => warning
|
||||
|
||||
std::string defname;
|
||||
|
||||
switch(oi.obstacleType)
|
||||
if (auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(&oi))
|
||||
{
|
||||
case CObstacleInstance::SPELL_CREATED:
|
||||
{
|
||||
auto &spellObstacle = dynamic_cast<const SpellCreatedObstacle&>(oi);
|
||||
defname = spellObstacle.appearAnimation;
|
||||
//TODO: sound
|
||||
//soundBase::QUIKSAND
|
||||
//soundBase::LANDMINE
|
||||
//soundBase::FORCEFLD
|
||||
//soundBase::fireWall
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi.obstacleType);
|
||||
return;
|
||||
animationName = spellObstacle->animation;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( oi.obstacleType == CObstacleInstance::USUAL || oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE);
|
||||
animationName = oi.getInfo().animation;
|
||||
}
|
||||
|
||||
auto animation = std::make_shared<CAnimation>(defname);
|
||||
animation->preload();
|
||||
if (animationsCache.count(animationName) == 0)
|
||||
{
|
||||
if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
{
|
||||
// obstacle use single bitmap image for animations
|
||||
auto animation = std::make_shared<CAnimation>();
|
||||
animation->setCustom(animationName, 0, 0);
|
||||
animationsCache[animationName] = animation;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto animation = std::make_shared<CAnimation>(animationName);
|
||||
animationsCache[animationName] = animation;
|
||||
animation->preload();
|
||||
}
|
||||
}
|
||||
obstacleAnimations[oi.uniqueID] = animationsCache[animationName];
|
||||
}
|
||||
|
||||
auto first = animation->getImage(0, 0);
|
||||
if(!first)
|
||||
return;
|
||||
void CBattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & obstacles)
|
||||
{
|
||||
assert(obstaclesBeingPlaced.empty());
|
||||
for (auto const & oi : obstacles)
|
||||
obstaclesBeingPlaced.push_back(oi->uniqueID);
|
||||
|
||||
//we assume here that effect graphics have the same size as the usual obstacle image
|
||||
// -> if we know how to blit obstacle, let's blit the effect in the same place
|
||||
Point whereTo = getObstaclePosition(first, oi);
|
||||
owner->stacksController->addNewAnim(new CEffectAnimation(owner, animation, whereTo.x, whereTo.y));
|
||||
for (auto const & oi : obstacles)
|
||||
{
|
||||
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(oi.get());
|
||||
|
||||
//TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad
|
||||
//CCS->soundh->playSound(sound);
|
||||
if (!spellObstacle)
|
||||
{
|
||||
logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi->obstacleType);
|
||||
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string defname = spellObstacle->appearAnimation;
|
||||
|
||||
//TODO: sound
|
||||
//soundBase::QUIKSAND
|
||||
//soundBase::LANDMINE
|
||||
//soundBase::FORCEFLD
|
||||
//soundBase::fireWall
|
||||
|
||||
auto animation = std::make_shared<CAnimation>(defname);
|
||||
animation->preload();
|
||||
|
||||
auto first = animation->getImage(0, 0);
|
||||
if(!first)
|
||||
{
|
||||
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
|
||||
continue;
|
||||
}
|
||||
|
||||
//we assume here that effect graphics have the same size as the usual obstacle image
|
||||
// -> if we know how to blit obstacle, let's blit the effect in the same place
|
||||
Point whereTo = getObstaclePosition(first, *oi);
|
||||
owner->stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::QUIKSAND, defname, whereTo, CPointEffectAnimation::WAIT_FOR_SOUND));
|
||||
|
||||
//so when multiple obstacles are added, they show up one after another
|
||||
owner->waitForAnims();
|
||||
|
||||
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
|
||||
loadObstacleImage(*spellObstacle);
|
||||
}
|
||||
}
|
||||
|
||||
void CBattleObstacleController::showAbsoluteObstacles(std::shared_ptr<CCanvas> canvas, const Point & offset)
|
||||
@ -153,40 +157,28 @@ std::shared_ptr<IImage> CBattleObstacleController::getObstacleImage(const CObsta
|
||||
int frameIndex = (owner->animCount+1) *25 / owner->getAnimSpeed();
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
|
||||
if(oi.obstacleType == CObstacleInstance::USUAL || oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
|
||||
if (obstacleAnimations.count(oi.uniqueID) == 0)
|
||||
{
|
||||
animation = obstacleAnimations[oi.uniqueID];
|
||||
}
|
||||
else if(oi.obstacleType == CObstacleInstance::SPELL_CREATED)
|
||||
{
|
||||
const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(&oi);
|
||||
if(!spellObstacle)
|
||||
return std::shared_ptr<IImage>();
|
||||
|
||||
std::string animationName = spellObstacle->animation;
|
||||
|
||||
auto cacheIter = animationsCache.find(animationName);
|
||||
|
||||
if(cacheIter == animationsCache.end())
|
||||
if (boost::range::find(obstaclesBeingPlaced, oi.uniqueID) != obstaclesBeingPlaced.end())
|
||||
{
|
||||
logAnim->trace("Creating obstacle animation %s", animationName);
|
||||
|
||||
animation = std::make_shared<CAnimation>(animationName);
|
||||
animation->preload();
|
||||
animationsCache[animationName] = animation;
|
||||
// obstacle is not loaded yet, don't show anything
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
animation = cacheIter->second;
|
||||
assert(0); // how?
|
||||
loadObstacleImage(oi);
|
||||
}
|
||||
}
|
||||
|
||||
animation = obstacleAnimations[oi.uniqueID];
|
||||
assert(animation);
|
||||
|
||||
if(animation)
|
||||
{
|
||||
frameIndex %= animation->size(0);
|
||||
return animation->getImage(frameIndex, 0);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,19 @@ class CBattleObstacleController
|
||||
|
||||
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
|
||||
|
||||
// semi-debug member, contains obstacles that should not yet be visible due to ongoing placement animation
|
||||
// used only for sanity checks to ensure that there are no invisible obstacles
|
||||
std::vector<si32> obstaclesBeingPlaced;
|
||||
|
||||
void loadObstacleImage(const CObstacleInstance & oi);
|
||||
|
||||
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
|
||||
Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
|
||||
|
||||
public:
|
||||
CBattleObstacleController(CBattleInterface * owner);
|
||||
|
||||
void obstaclePlaced(const CObstacleInstance & oi);
|
||||
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);
|
||||
void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
|
||||
void showAbsoluteObstacles(std::shared_ptr<CCanvas> canvas, const Point & offset);
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "../gui/Geometries.h"
|
||||
#include "../gui/CAnimation.h"
|
||||
#include "../gui/CCanvas.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../../lib/CStack.h"
|
||||
@ -47,6 +48,7 @@ static double calculateCatapultParabolaY(const Point & from, const Point & dest,
|
||||
|
||||
void ProjectileMissile::show(std::shared_ptr<CCanvas> canvas)
|
||||
{
|
||||
logAnim->info("Projectile rendering, %d / %d", step, steps);
|
||||
size_t group = reverse ? 1 : 0;
|
||||
auto image = animation->getImage(frameNum, group, true);
|
||||
|
||||
@ -61,14 +63,23 @@ void ProjectileMissile::show(std::shared_ptr<CCanvas> canvas)
|
||||
|
||||
canvas->draw(image, pos);
|
||||
}
|
||||
|
||||
++step;
|
||||
}
|
||||
|
||||
void ProjectileAnimatedMissile::show(std::shared_ptr<CCanvas> canvas)
|
||||
{
|
||||
ProjectileMissile::show(canvas);
|
||||
frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
|
||||
size_t animationSize = animation->size(reverse ? 1 : 0);
|
||||
while (frameProgress > animationSize)
|
||||
frameProgress -= animationSize;
|
||||
|
||||
frameNum = std::floor(frameProgress);
|
||||
}
|
||||
|
||||
void ProjectileCatapult::show(std::shared_ptr<CCanvas> canvas)
|
||||
{
|
||||
size_t group = reverse ? 1 : 0;
|
||||
auto image = animation->getImage(frameNum, group, true);
|
||||
auto image = animation->getImage(frameNum, 0, true);
|
||||
|
||||
if(image)
|
||||
{
|
||||
@ -171,8 +182,12 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
|
||||
return;
|
||||
|
||||
const CCreature * creature = getShooter(stack);
|
||||
projectilesCache[creature->animation.projectileImageName] = createProjectileImage(creature->animation.projectileImageName);
|
||||
}
|
||||
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
|
||||
std::shared_ptr<CAnimation> CBattleProjectileController::createProjectileImage(const std::string & path )
|
||||
{
|
||||
std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(path);
|
||||
projectile->preload();
|
||||
|
||||
if(projectile->size(1) != 0)
|
||||
@ -180,7 +195,7 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
|
||||
else
|
||||
projectile->createFlippedGroup(0, 1);
|
||||
|
||||
projectilesCache[creature->animation.projectileImageName] = projectile;
|
||||
return projectile;
|
||||
}
|
||||
|
||||
std::shared_ptr<CAnimation> CBattleProjectileController::getProjectileImage(const CStack * stack)
|
||||
@ -196,9 +211,11 @@ std::shared_ptr<CAnimation> CBattleProjectileController::getProjectileImage(cons
|
||||
|
||||
void CBattleProjectileController::emitStackProjectile(const CStack * stack)
|
||||
{
|
||||
int stackID = stack ? stack->ID : -1;
|
||||
|
||||
for (auto projectile : projectiles)
|
||||
{
|
||||
if ( !projectile->playing && projectile->shooterID == stack->ID)
|
||||
if ( !projectile->playing && projectile->shooterID == stackID)
|
||||
{
|
||||
projectile->playing = true;
|
||||
return;
|
||||
@ -228,9 +245,11 @@ void CBattleProjectileController::showProjectiles(std::shared_ptr<CCanvas> canva
|
||||
|
||||
bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
|
||||
{
|
||||
int stackID = stack ? stack->ID : -1;
|
||||
|
||||
for(auto const & instance : projectiles)
|
||||
{
|
||||
if(instance->shooterID == stack->ID)
|
||||
if(instance->shooterID == stackID)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -238,85 +257,94 @@ bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
|
||||
return false;
|
||||
}
|
||||
|
||||
int CBattleProjectileController::computeProjectileFlightTime( Point from, Point dest, double animSpeed)
|
||||
{
|
||||
double distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
|
||||
double distance = sqrt(distanceSquared);
|
||||
int steps = std::round(distance / animSpeed);
|
||||
|
||||
if (steps > 0)
|
||||
return steps;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int CBattleProjectileController::computeProjectileFrameID( Point from, Point dest, const CStack * stack)
|
||||
{
|
||||
const CCreature * creature = getShooter(stack);
|
||||
|
||||
auto & angles = creature->animation.missleFrameAngles;
|
||||
auto animation = getProjectileImage(stack);
|
||||
|
||||
// only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used
|
||||
size_t maxFrame = std::min<size_t>(angles.size(), animation->size(0));
|
||||
|
||||
assert(maxFrame > 0);
|
||||
double projectileAngle = -atan2(dest.y - from.y, std::abs(dest.x - from.x));
|
||||
|
||||
// values in angles array indicate position from which this frame was rendered, in degrees.
|
||||
// possible range is 90 ... -90, where projectile for +90 will be used for shooting upwards, +0 for shots towards right and -90 for downwards shots
|
||||
// find frame that has closest angle to one that we need for this shot
|
||||
int bestID = 0;
|
||||
double bestDiff = fabs( angles[0] / 180 * M_PI - projectileAngle );
|
||||
|
||||
for (int i=1; i<maxFrame; i++)
|
||||
{
|
||||
double currentDiff = fabs( angles[i] / 180 * M_PI - projectileAngle );
|
||||
if (currentDiff < bestDiff)
|
||||
{
|
||||
bestID = i;
|
||||
bestDiff = currentDiff;
|
||||
}
|
||||
}
|
||||
return bestID;
|
||||
}
|
||||
|
||||
void CBattleProjectileController::createCatapultProjectile(const CStack * shooter, Point from, Point dest)
|
||||
{
|
||||
auto catapultProjectile = new ProjectileCatapult();
|
||||
|
||||
catapultProjectile->animation = getProjectileImage(shooter);
|
||||
catapultProjectile->frameNum = 0;
|
||||
catapultProjectile->step = 0;
|
||||
catapultProjectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
|
||||
catapultProjectile->from = from;
|
||||
catapultProjectile->dest = dest;
|
||||
catapultProjectile->shooterID = shooter->ID;
|
||||
catapultProjectile->step = 0;
|
||||
catapultProjectile->playing = false;
|
||||
|
||||
projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
|
||||
}
|
||||
|
||||
void CBattleProjectileController::createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest)
|
||||
{
|
||||
assert(target);
|
||||
|
||||
const CCreature *shooterInfo = getShooter(shooter);
|
||||
|
||||
std::shared_ptr<ProjectileBase> projectile;
|
||||
|
||||
if (!target)
|
||||
if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
auto catapultProjectile= new ProjectileCatapult();
|
||||
projectile.reset(catapultProjectile);
|
||||
|
||||
catapultProjectile->animation = getProjectileImage(shooter);
|
||||
catapultProjectile->wallDamageAmount = 0; //FIXME - receive from caller
|
||||
catapultProjectile->frameNum = 0;
|
||||
catapultProjectile->reverse = false;
|
||||
catapultProjectile->step = 0;
|
||||
catapultProjectile->steps = 0;
|
||||
|
||||
//double animSpeed = AnimationControls::getProjectileSpeed() / 10;
|
||||
//catapultProjectile->steps = std::round(std::abs((dest.x - from.x) / animSpeed));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo->nameSing);
|
||||
}
|
||||
|
||||
if (stackUsesRayProjectile(shooter))
|
||||
{
|
||||
auto rayProjectile = new ProjectileRay();
|
||||
projectile.reset(rayProjectile);
|
||||
|
||||
rayProjectile->rayConfig = shooterInfo->animation.projectileRay;
|
||||
}
|
||||
else if (stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
auto missileProjectile = new ProjectileMissile();
|
||||
projectile.reset(missileProjectile);
|
||||
|
||||
auto & angles = shooterInfo->animation.missleFrameAngles;
|
||||
|
||||
missileProjectile->animation = getProjectileImage(shooter);
|
||||
missileProjectile->reverse = !owner->stacksController->facingRight(shooter);
|
||||
|
||||
// only frames below maxFrame are usable: anything higher is either no present or we don't know when it should be used
|
||||
size_t maxFrame = std::min<size_t>(angles.size(), missileProjectile->animation->size(0));
|
||||
|
||||
assert(maxFrame > 0);
|
||||
double projectileAngle = -atan2(dest.y - from.y, std::abs(dest.x - from.x));
|
||||
|
||||
// values in angles array indicate position from which this frame was rendered, in degrees.
|
||||
// possible range is 90 ... -90, where projectile for +90 will be used for shooting upwards, +0 for shots towards right and -90 for downwards shots
|
||||
// find frame that has closest angle to one that we need for this shot
|
||||
int bestID = 0;
|
||||
double bestDiff = fabs( angles[0] / 180 * M_PI - projectileAngle );
|
||||
|
||||
for (int i=1; i<maxFrame; i++)
|
||||
{
|
||||
double currentDiff = fabs( angles[i] / 180 * M_PI - projectileAngle );
|
||||
if (currentDiff < bestDiff)
|
||||
{
|
||||
bestID = i;
|
||||
bestDiff = currentDiff;
|
||||
}
|
||||
}
|
||||
missileProjectile->frameNum = bestID;
|
||||
}
|
||||
logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo->nameSing);
|
||||
}
|
||||
|
||||
double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile
|
||||
if (!target)
|
||||
animSpeed *= 0.2; // catapult attack needs slower speed
|
||||
if (stackUsesRayProjectile(shooter))
|
||||
{
|
||||
auto rayProjectile = new ProjectileRay();
|
||||
projectile.reset(rayProjectile);
|
||||
|
||||
double distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
|
||||
double distance = sqrt(distanceSquared);
|
||||
projectile->steps = std::round(distance / animSpeed);
|
||||
if(projectile->steps == 0)
|
||||
projectile->steps = 1;
|
||||
rayProjectile->rayConfig = shooterInfo->animation.projectileRay;
|
||||
}
|
||||
else if (stackUsesMissileProjectile(shooter))
|
||||
{
|
||||
auto missileProjectile = new ProjectileMissile();
|
||||
projectile.reset(missileProjectile);
|
||||
|
||||
missileProjectile->animation = getProjectileImage(shooter);
|
||||
missileProjectile->reverse = !owner->stacksController->facingRight(shooter);
|
||||
missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
|
||||
missileProjectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
|
||||
}
|
||||
|
||||
projectile->from = from;
|
||||
projectile->dest = dest;
|
||||
@ -326,3 +354,29 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
|
||||
|
||||
projectiles.push_back(projectile);
|
||||
}
|
||||
|
||||
void CBattleProjectileController::createSpellProjectile(const CStack * shooter, const CStack * target, Point from, Point dest, const CSpell * spell)
|
||||
{
|
||||
double projectileAngle = std::abs(atan2(dest.x - from.x, dest.y - from.y));
|
||||
std::string animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
|
||||
|
||||
assert(!animToDisplay.empty());
|
||||
|
||||
if(!animToDisplay.empty())
|
||||
{
|
||||
auto projectile = new ProjectileAnimatedMissile();
|
||||
|
||||
projectile->animation = createProjectileImage(animToDisplay);
|
||||
projectile->frameProgress = 0;
|
||||
projectile->frameNum = 0;
|
||||
projectile->reverse = from.x > dest.x;
|
||||
projectile->from = from;
|
||||
projectile->dest = dest;
|
||||
projectile->shooterID = shooter ? shooter->ID : -1;
|
||||
projectile->step = 0;
|
||||
projectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getSpellEffectSpeed());
|
||||
projectile->playing = false;
|
||||
|
||||
projectiles.push_back(std::shared_ptr<ProjectileBase>(projectile));
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CStack;
|
||||
class CSpell;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -48,11 +49,18 @@ struct ProjectileMissile : ProjectileBase
|
||||
bool reverse; // if true, projectile will be flipped by vertical axis
|
||||
};
|
||||
|
||||
struct ProjectileCatapult : ProjectileMissile
|
||||
struct ProjectileAnimatedMissile : ProjectileMissile
|
||||
{
|
||||
void show(std::shared_ptr<CCanvas> canvas) override;
|
||||
float frameProgress;
|
||||
};
|
||||
|
||||
struct ProjectileCatapult : ProjectileBase
|
||||
{
|
||||
void show(std::shared_ptr<CCanvas> canvas) override;
|
||||
|
||||
int wallDamageAmount;
|
||||
std::shared_ptr<CAnimation> animation;
|
||||
int frameNum; // frame to display from projectile animation
|
||||
};
|
||||
|
||||
struct ProjectileRay : ProjectileBase
|
||||
@ -76,6 +84,7 @@ class CBattleProjectileController
|
||||
std::vector<std::shared_ptr<ProjectileBase>> projectiles;
|
||||
|
||||
std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack);
|
||||
std::shared_ptr<CAnimation> createProjectileImage(const std::string & path );
|
||||
void initStackProjectile(const CStack * stack);
|
||||
|
||||
bool stackUsesRayProjectile(const CStack * stack);
|
||||
@ -84,6 +93,9 @@ class CBattleProjectileController
|
||||
void showProjectile(std::shared_ptr<CCanvas> canvas, std::shared_ptr<ProjectileBase> projectile);
|
||||
|
||||
const CCreature * getShooter(const CStack * stack);
|
||||
|
||||
int computeProjectileFrameID( Point from, Point dest, const CStack * stack);
|
||||
int computeProjectileFlightTime( Point from, Point dest, double speed);
|
||||
public:
|
||||
CBattleProjectileController(CBattleInterface * owner);
|
||||
|
||||
@ -92,4 +104,6 @@ public:
|
||||
bool hasActiveProjectile(const CStack * stack);
|
||||
void emitStackProjectile(const CStack * stack);
|
||||
void createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest);
|
||||
void createSpellProjectile(const CStack * shooter, const CStack * target, Point from, Point dest, const CSpell * spell);
|
||||
void createCatapultProjectile(const CStack * shooter, Point from, Point dest);
|
||||
};
|
||||
|
@ -321,18 +321,19 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
|
||||
const CStack *stack = owner->curInt->cb->battleGetStackByID(ca.attacker);
|
||||
for (auto attackInfo : ca.attackedParts)
|
||||
{
|
||||
owner->stacksController->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
|
||||
owner->stacksController->addNewAnim(new CCatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<Point> positions;
|
||||
|
||||
//no attacker stack, assume spell-related (earthquake) - only hit animation
|
||||
for (auto attackInfo : ca.attackedParts)
|
||||
{
|
||||
Point destPos = owner->stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120);
|
||||
positions.push_back(owner->stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
|
||||
|
||||
owner->stacksController->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y));
|
||||
}
|
||||
|
||||
owner->stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::invalid, "SGEXPL.DEF", positions));
|
||||
}
|
||||
|
||||
owner->waitForAnims();
|
||||
|
@ -113,6 +113,11 @@ float AnimationControls::getProjectileSpeed()
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 100);
|
||||
}
|
||||
|
||||
float AnimationControls::getCatapultSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 20);
|
||||
}
|
||||
|
||||
float AnimationControls::getSpellEffectSpeed()
|
||||
{
|
||||
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
|
||||
|
@ -35,6 +35,9 @@ namespace AnimationControls
|
||||
/// TODO: make it time-based
|
||||
float getProjectileSpeed();
|
||||
|
||||
/// returns speed of catapult projectile
|
||||
float getCatapultSpeed();
|
||||
|
||||
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
|
||||
float getSpellEffectSpeed();
|
||||
|
||||
|
@ -34,6 +34,8 @@ CObstacleInstance::~CObstacleInstance()
|
||||
|
||||
const ObstacleInfo & CObstacleInstance::getInfo() const
|
||||
{
|
||||
assert( obstacleType == USUAL || obstacleType == ABSOLUTE_OBSTACLE);
|
||||
|
||||
return *Obstacle(ID).getInfo();
|
||||
}
|
||||
|
||||
|
@ -547,7 +547,7 @@ std::string CSpell::AnimationInfo::selectProjectile(const double angle) const
|
||||
|
||||
for(const auto & info : projectile)
|
||||
{
|
||||
if(info.minimumAngle < angle && info.minimumAngle > maximum)
|
||||
if(info.minimumAngle < angle && info.minimumAngle >= maximum)
|
||||
{
|
||||
maximum = info.minimumAngle;
|
||||
res = info.resourceName;
|
||||
|
Loading…
Reference in New Issue
Block a user