mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
more improvements for battle animations
- synchronized attack/defence animation - spell animation speed uses game settings - added logging domain for battle animations - fixed disrupting ray duration
This commit is contained in:
parent
4f7c6b8d34
commit
1a77fee7f7
@ -866,7 +866,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
|
||||
if (defender && !elem.isSecondary())
|
||||
battleInt->displayEffect(elem.effect, defender->position);
|
||||
}
|
||||
bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType == Battle::SHOOT : false); //FIXME: why action is deleted during enchanter cast?
|
||||
bool shooting = (LOCPLINT->curAction ? LOCPLINT->curAction->actionType != Battle::WALK_AND_ATTACK : false); //FIXME: why action is deleted during enchanter cast?
|
||||
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, shooting, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
|
||||
arg.push_back(to_put);
|
||||
}
|
||||
|
@ -32,10 +32,18 @@
|
||||
|
||||
CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
|
||||
: owner(_owner), ID(_owner->animIDhelper++)
|
||||
{}
|
||||
{
|
||||
logAnim->traceStream() << "Animation #" << ID << " created";
|
||||
}
|
||||
|
||||
CBattleAnimation::~CBattleAnimation()
|
||||
{
|
||||
logAnim->traceStream() << "Animation #" << ID << " deleted";
|
||||
}
|
||||
|
||||
void CBattleAnimation::endAnim()
|
||||
{
|
||||
logAnim->traceStream() << "Animation #" << ID << " ended, type is " << typeid(this).name();
|
||||
for(auto & elem : owner->pendingAnims)
|
||||
{
|
||||
if(elem.first == this)
|
||||
@ -58,7 +66,7 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
|
||||
if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
|
||||
continue;
|
||||
|
||||
if(sen && thSen && sen != thSen && perStackConcurrency)
|
||||
if(perStackConcurrency && sen && thSen && sen != thSen)
|
||||
continue;
|
||||
|
||||
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
|
||||
@ -105,6 +113,17 @@ void CAttackAnimation::endAnim()
|
||||
|
||||
bool CAttackAnimation::checkInitialConditions()
|
||||
{
|
||||
for(auto & elem : owner->pendingAnims)
|
||||
{
|
||||
CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
|
||||
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
|
||||
|
||||
if(revAnim) // enemy must be fully reversed
|
||||
{
|
||||
if (revAnim->stack->ID == attackedStack->ID)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isEarliest(false);
|
||||
}
|
||||
|
||||
@ -124,7 +143,7 @@ CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attac
|
||||
|
||||
CDefenceAnimation::CDefenceAnimation(StackAttackedInfo _attackedInfo, CBattleInterface * _owner)
|
||||
: CBattleStackAnimation(_owner, _attackedInfo.defender),
|
||||
attacker(_attackedInfo.attacker), byShooting(_attackedInfo.byShooting),
|
||||
attacker(_attackedInfo.attacker), rangedAttack(_attackedInfo.rangedAttack),
|
||||
killed(_attackedInfo.killed)
|
||||
{}
|
||||
|
||||
@ -144,23 +163,15 @@ bool CDefenceAnimation::init()
|
||||
if(attAnim && attAnim->stack->ID != stack->ID)
|
||||
continue;
|
||||
|
||||
if(attacker != nullptr)
|
||||
{
|
||||
int attackerAnimType = owner->creAnims[attacker->ID]->getType();
|
||||
if( ( attackerAnimType == CCreatureAnim::ATTACK_UP ||
|
||||
attackerAnimType == CCreatureAnim::ATTACK_FRONT ||
|
||||
attackerAnimType == CCreatureAnim::ATTACK_DOWN ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
CReverseAnimation * animAsRev = dynamic_cast<CReverseAnimation *>(elem.first);
|
||||
|
||||
if(animAsRev && animAsRev->priority)
|
||||
if(animAsRev /*&& animAsRev->priority*/)
|
||||
return false;
|
||||
|
||||
if(elem.first)
|
||||
vstd::amin(lowestMoveID, elem.first->ID);
|
||||
}
|
||||
|
||||
if(ID > lowestMoveID)
|
||||
return false;
|
||||
|
||||
@ -172,7 +183,7 @@ bool CDefenceAnimation::init()
|
||||
}
|
||||
//unit reversed
|
||||
|
||||
if(byShooting) //delay hit animation
|
||||
if(rangedAttack) //delay hit animation
|
||||
{
|
||||
for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it)
|
||||
{
|
||||
@ -183,27 +194,61 @@ bool CDefenceAnimation::init()
|
||||
}
|
||||
}
|
||||
|
||||
//initializing
|
||||
if(killed)
|
||||
// synchronize animation with attacker, unless defending or attacked by shooter:
|
||||
// wait for 1/2 of attack animation
|
||||
if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
|
||||
{
|
||||
CCS->soundh->playSound(battle_sound(stack->getCreature(), killed));
|
||||
myAnim->setType(CCreatureAnim::DEATH); //death
|
||||
float fps = AnimationControls::getCreatureAnimationSpeed(
|
||||
stack->getCreature(), owner->creAnims[stack->ID], getMyAnimType());
|
||||
|
||||
timeToWait = myAnim->framesInGroup(getMyAnimType()) / fps;
|
||||
|
||||
myAnim->setType(CCreatureAnim::HOLDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: this block doesn't seems correct if the unit is defending.
|
||||
CCS->soundh->playSound(battle_sound(stack->getCreature(), wince));
|
||||
myAnim->setType(CCreatureAnim::HITTED); //getting hit
|
||||
timeToWait = 0;
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
|
||||
|
||||
return true; //initialized successfuly
|
||||
}
|
||||
|
||||
std::string CDefenceAnimation::getMySound()
|
||||
{
|
||||
if(killed)
|
||||
return battle_sound(stack->getCreature(), killed);
|
||||
|
||||
if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
|
||||
return battle_sound(stack->getCreature(), defend);
|
||||
return battle_sound(stack->getCreature(), wince);
|
||||
}
|
||||
|
||||
CCreatureAnim::EAnimType CDefenceAnimation::getMyAnimType()
|
||||
{
|
||||
if(killed)
|
||||
return CCreatureAnim::DEATH;
|
||||
|
||||
if (stack->valOfBonuses(Selector::durationType(Bonus::STACK_GETS_TURN)))
|
||||
return CCreatureAnim::DEFENCE;
|
||||
return CCreatureAnim::HITTED;
|
||||
}
|
||||
|
||||
void CDefenceAnimation::startAnimation()
|
||||
{
|
||||
CCS->soundh->playSound(getMySound());
|
||||
myAnim->setType(getMyAnimType());
|
||||
myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this);
|
||||
}
|
||||
|
||||
void CDefenceAnimation::nextFrame()
|
||||
{
|
||||
assert(myAnim->getType() == CCreatureAnim::HITTED || myAnim->getType() == CCreatureAnim::DEATH);
|
||||
if (timeToWait > 0)
|
||||
{
|
||||
timeToWait -= float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000;
|
||||
if (timeToWait <= 0)
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
CBattleAnimation::nextFrame();
|
||||
}
|
||||
@ -262,6 +307,12 @@ bool CMeleeAttackAnimation::init()
|
||||
owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
// opponent must face attacker ( = different directions) before he can be attacked
|
||||
if (attackingStack && attackedStack &&
|
||||
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
return false;
|
||||
|
||||
//reversed
|
||||
|
||||
shooting = false;
|
||||
@ -386,7 +437,7 @@ bool CMovementAnimation::init()
|
||||
{
|
||||
float distance = sqrt(distanceX * distanceX + distanceY * distanceY);
|
||||
|
||||
timeToMove *= distance / AnimationControls::getFlightDistance(stack->getCreature());
|
||||
timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -614,6 +665,11 @@ bool CShootingAnimation::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
// opponent must face attacker ( = different directions) before he can be attacked
|
||||
if (attackingStack && attackedStack &&
|
||||
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID])
|
||||
return false;
|
||||
|
||||
// Create the projectile animation
|
||||
|
||||
//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value)
|
||||
@ -817,7 +873,7 @@ bool CSpellEffectAnimation::init()
|
||||
CSDL_Ext::VflipSurf(elem.bitmap);
|
||||
}
|
||||
}
|
||||
be.frame = 0;
|
||||
be.currentFrame = 0;
|
||||
be.maxFrame = be.anim->ourImages.size();
|
||||
be.x = i * anim->width + owner->pos.x;
|
||||
be.y = j * anim->height + owner->pos.y;
|
||||
@ -854,7 +910,7 @@ bool CSpellEffectAnimation::init()
|
||||
}
|
||||
}
|
||||
|
||||
be.frame = 0;
|
||||
be.currentFrame = 0;
|
||||
be.maxFrame = be.anim->ourImages.size();
|
||||
if(effect == 1)
|
||||
be.maxFrame = 3;
|
||||
@ -909,9 +965,9 @@ void CSpellEffectAnimation::nextFrame()
|
||||
{
|
||||
if(elem.effectID == ID)
|
||||
{
|
||||
++(elem.frame);
|
||||
elem.currentFrame += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
|
||||
|
||||
if(elem.frame == elem.maxFrame)
|
||||
if(elem.currentFrame >= elem.maxFrame)
|
||||
{
|
||||
endAnim();
|
||||
break;
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
virtual bool init() = 0; //to be called - if returned false, call again until returns true
|
||||
virtual void nextFrame() {} //call every new frame
|
||||
virtual void endAnim(); //to be called mostly internally; in this class it removes animation from pendingAnims list
|
||||
virtual ~CBattleAnimation(){}
|
||||
virtual ~CBattleAnimation();
|
||||
|
||||
bool isEarliest(bool perStackConcurrency); //determines if this animation is earliest of all
|
||||
|
||||
@ -69,11 +69,16 @@ public:
|
||||
/// Animation of a defending unit
|
||||
class CDefenceAnimation : public CBattleStackAnimation
|
||||
{
|
||||
private:
|
||||
//std::vector<StackAttackedInfo> attackedInfos;
|
||||
CCreatureAnim::EAnimType getMyAnimType();
|
||||
std::string getMySound();
|
||||
|
||||
void startAnimation();
|
||||
|
||||
const CStack * attacker; //attacking stack
|
||||
bool byShooting; //if true, stack has been attacked by shooting
|
||||
bool rangedAttack; //if true, stack has been attacked by shooting
|
||||
bool killed; //if true, stack has been killed
|
||||
|
||||
float timeToWait; // for how long this animation should be paused
|
||||
public:
|
||||
bool init();
|
||||
void nextFrame();
|
||||
|
@ -1010,7 +1010,10 @@ void CBattleInterface::showBattleEffects(const std::vector<const BattleEffect *>
|
||||
{
|
||||
for(auto & elem : battleEffects)
|
||||
{
|
||||
SDL_Surface * bitmapToBlit = elem->anim->ourImages[(elem->frame)%elem->anim->ourImages.size()].bitmap;
|
||||
int currentFrame = floor(elem->currentFrame);
|
||||
currentFrame %= elem->anim->ourImages.size();
|
||||
|
||||
SDL_Surface * bitmapToBlit = elem->anim->ourImages[currentFrame].bitmap;
|
||||
SDL_Rect temp_rect = genRect(bitmapToBlit->h, bitmapToBlit->w, elem->x, elem->y);
|
||||
SDL_BlitSurface(bitmapToBlit, nullptr, to, &temp_rect);
|
||||
}
|
||||
@ -1520,16 +1523,11 @@ void CBattleInterface::stackAttacking( const CStack * attacker, BattleHex dest,
|
||||
{
|
||||
addNewAnim(new CMeleeAttackAnimation(this, attacker, dest, attacked));
|
||||
}
|
||||
waitForAnims();
|
||||
//waitForAnims();
|
||||
}
|
||||
|
||||
void CBattleInterface::newRoundFirst( int round )
|
||||
{
|
||||
//handle regeneration
|
||||
std::vector<const CStack*> stacks = curInt->cb->battleGetStacks(); //gets only alive stacks
|
||||
// for(const CStack *s : stacks)
|
||||
// {
|
||||
// }
|
||||
waitForAnims();
|
||||
}
|
||||
|
||||
@ -1725,12 +1723,13 @@ void CBattleInterface::spellCast( const BattleSpellCast * sc )
|
||||
|
||||
//displaying animation
|
||||
CDefEssential * animDef = CDefHandler::giveDefEss(animToDisplay);
|
||||
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);
|
||||
|
||||
int steps = sqrt(static_cast<double>((destcoord.x - srccoord.x)*(destcoord.x - srccoord.x) + (destcoord.y - srccoord.y) * (destcoord.y - srccoord.y))) / 40;
|
||||
if(steps <= 0)
|
||||
steps = 1;
|
||||
|
||||
int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps, dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
|
||||
int steps = distance / AnimationControls::getSpellEffectSpeed() + 1;
|
||||
int dx = (destcoord.x - srccoord.x - animDef->ourImages[0].bitmap->w)/steps;
|
||||
int dy = (destcoord.y - srccoord.y - animDef->ourImages[0].bitmap->h)/steps;
|
||||
|
||||
delete animDef;
|
||||
addNewAnim(new CSpellEffectAnimation(this, animToDisplay, srccoord.x, srccoord.y, dx, dy, Vflip));
|
||||
@ -2614,7 +2613,7 @@ void CBattleInterface::showQueue()
|
||||
|
||||
void CBattleInterface::startAction(const BattleAction* action)
|
||||
{
|
||||
setActiveStack(nullptr);
|
||||
//setActiveStack(nullptr);
|
||||
setHoveredStack(nullptr);
|
||||
|
||||
if(action->actionType == Battle::END_TACTIC_PHASE)
|
||||
|
@ -57,7 +57,7 @@ struct StackAttackedInfo
|
||||
unsigned int dmg; //damage dealt
|
||||
unsigned int amountKilled; //how many creatures in stack has been killed
|
||||
const CStack * attacker; //attacking stack
|
||||
bool byShooting; //if true, stack has been attacked by shooting
|
||||
bool rangedAttack; //if true, stack has been attacked by shooting
|
||||
bool killed; //if true, stack has been killed
|
||||
bool rebirth; //if true, play rebirth animation after all
|
||||
bool cloneKilled;
|
||||
@ -67,7 +67,8 @@ struct StackAttackedInfo
|
||||
struct BattleEffect
|
||||
{
|
||||
int x, y; //position on the screen
|
||||
int frame, maxFrame;
|
||||
float currentFrame;
|
||||
int maxFrame;
|
||||
CDefHandler * anim; //animation to display
|
||||
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
|
||||
BattleHex position; //Indicates if effect which hex the effect is drawn on
|
||||
|
@ -48,11 +48,8 @@ CCreatureAnimation * AnimationControls::getAnimation(const CCreature * creature)
|
||||
|
||||
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t group)
|
||||
{
|
||||
// possible new fields for creature format
|
||||
//Shoot Animation Time
|
||||
//Cast Animation Time
|
||||
//Defence and/or Death Animation Time
|
||||
|
||||
// possible new fields for creature format:
|
||||
//split "Attack time" into "Shoot Time" and "Cast Time"
|
||||
|
||||
// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
|
||||
CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
|
||||
@ -69,9 +66,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
|
||||
case CCreatureAnim::HOLDING:
|
||||
return baseSpeed;
|
||||
|
||||
case CCreatureAnim::ATTACK_UP:
|
||||
case CCreatureAnim::ATTACK_FRONT:
|
||||
case CCreatureAnim::ATTACK_DOWN:
|
||||
case CCreatureAnim::SHOOT_UP:
|
||||
case CCreatureAnim::SHOOT_FRONT:
|
||||
case CCreatureAnim::SHOOT_DOWN:
|
||||
@ -80,6 +74,18 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
|
||||
case CCreatureAnim::CAST_DOWN:
|
||||
return speed * 2 / creature->animation.attackAnimationTime / anim->framesInGroup(type);
|
||||
|
||||
// as strange as it looks like "attackAnimationTime" does not affects melee attacks
|
||||
// necessary because length of attack animation must be same for all creatures for synchronization
|
||||
case CCreatureAnim::ATTACK_UP:
|
||||
case CCreatureAnim::ATTACK_FRONT:
|
||||
case CCreatureAnim::ATTACK_DOWN:
|
||||
case CCreatureAnim::DEFENCE:
|
||||
return speed * 2 / anim->framesInGroup(type);
|
||||
|
||||
case CCreatureAnim::DEATH:
|
||||
case CCreatureAnim::HITTED: // time-wise equals 1/2 of attack animation length
|
||||
return speed / anim->framesInGroup(type);
|
||||
|
||||
case CCreatureAnim::TURN_L:
|
||||
case CCreatureAnim::TURN_R:
|
||||
return speed;
|
||||
@ -88,9 +94,6 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
|
||||
case CCreatureAnim::MOVE_END:
|
||||
return speed / 5;
|
||||
|
||||
case CCreatureAnim::HITTED:
|
||||
case CCreatureAnim::DEFENCE:
|
||||
case CCreatureAnim::DEATH:
|
||||
case CCreatureAnim::DEAD:
|
||||
return speed / 5;
|
||||
|
||||
@ -105,6 +108,11 @@ float AnimationControls::getProjectileSpeed()
|
||||
return settings["battle"]["animationSpeed"].Float() * 100;
|
||||
}
|
||||
|
||||
float AnimationControls::getSpellEffectSpeed()
|
||||
{
|
||||
return settings["battle"]["animationSpeed"].Float() * 60;
|
||||
}
|
||||
|
||||
float AnimationControls::getMovementDuration(const CCreature * creature)
|
||||
{
|
||||
return settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime;
|
||||
@ -201,7 +209,7 @@ bool CCreatureAnimation::incrementFrame(float timePassed)
|
||||
currentFrame += timePassed * speed;
|
||||
if (currentFrame >= float(framesInGroup(type)))
|
||||
{
|
||||
// just in case of extremely low fps
|
||||
// just in case of extremely low fps (or insanely high speed)
|
||||
while (currentFrame >= float(framesInGroup(type)))
|
||||
currentFrame -= framesInGroup(type);
|
||||
|
||||
|
@ -28,13 +28,16 @@ namespace AnimationControls
|
||||
/// creates animation object with preset speed control
|
||||
CCreatureAnimation * getAnimation(const CCreature * creature);
|
||||
|
||||
/// returns animation speed of specific group, taking in mind game setting
|
||||
/// returns animation speed of specific group, taking in mind game setting (in frames per second)
|
||||
float getCreatureAnimationSpeed(const CCreature * creature, const CCreatureAnimation * anim, size_t groupID);
|
||||
|
||||
/// returns how far projectile should move each frame
|
||||
/// TODO: make it time-based
|
||||
float getProjectileSpeed();
|
||||
|
||||
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
|
||||
float getSpellEffectSpeed();
|
||||
|
||||
/// returns duration of full movement animation, in seconds. Needed to move animation on screen
|
||||
float getMovementDuration(const CCreature * creature);
|
||||
|
||||
|
@ -560,7 +560,7 @@
|
||||
"type": "PRIMARY_SKILL",
|
||||
"subtype": "primSkill.defence",
|
||||
"valueType": "ADDITIVE_VALUE",
|
||||
"duration": "N_TURNS",
|
||||
"duration": "ONE_BATTLE",
|
||||
"values":[-3,-3,-4,-5]
|
||||
}
|
||||
]
|
||||
|
@ -54,10 +54,9 @@ boost::recursive_mutex CLogManager::smx;
|
||||
DLL_LINKAGE CLogger * logGlobal = CLogger::getGlobalLogger();
|
||||
|
||||
DLL_LINKAGE CLogger * logBonus = CLogger::getLogger(CLoggerDomain("bonus"));
|
||||
|
||||
DLL_LINKAGE CLogger * logNetwork = CLogger::getLogger(CLoggerDomain("network"));
|
||||
|
||||
DLL_LINKAGE CLogger * logAi = CLogger::getLogger(CLoggerDomain("ai"));
|
||||
DLL_LINKAGE CLogger * logAnim = CLogger::getLogger(CLoggerDomain("animation"));
|
||||
|
||||
CLogger * CLogger::getLogger(const CLoggerDomain & domain)
|
||||
{
|
||||
|
@ -125,6 +125,7 @@ extern DLL_LINKAGE CLogger * logGlobal;
|
||||
extern DLL_LINKAGE CLogger * logBonus;
|
||||
extern DLL_LINKAGE CLogger * logNetwork;
|
||||
extern DLL_LINKAGE CLogger * logAi;
|
||||
extern DLL_LINKAGE CLogger * logAnim;
|
||||
|
||||
/// RAII class for tracing the program execution.
|
||||
/// It prints "Leaving function XYZ" automatically when the object gets destructed.
|
||||
|
Loading…
Reference in New Issue
Block a user