1
0
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:
Ivan Savenko 2013-07-16 18:12:47 +00:00
parent 4f7c6b8d34
commit 1a77fee7f7
10 changed files with 137 additions and 65 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -560,7 +560,7 @@
"type": "PRIMARY_SKILL",
"subtype": "primSkill.defence",
"valueType": "ADDITIVE_VALUE",
"duration": "N_TURNS",
"duration": "ONE_BATTLE",
"values":[-3,-3,-4,-5]
}
]

View File

@ -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)
{

View File

@ -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.