diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 8c8e8b6dd..8b5f5a509 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -866,7 +866,7 @@ void CPlayerInterface::battleStacksAttacked(const std::vectordisplayEffect(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); } diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index eeb03a94f..4897c31f7 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -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(stAnim); @@ -105,6 +113,17 @@ void CAttackAnimation::endAnim() bool CAttackAnimation::checkInitialConditions() { + for(auto & elem : owner->pendingAnims) + { + CBattleStackAnimation * stAnim = dynamic_cast(elem.first); + CReverseAnimation * revAnim = dynamic_cast(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(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::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; diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index 99d1ae5c2..fa95265f1 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -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 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(); diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 635f6557d..5734d0ebd 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1010,7 +1010,10 @@ void CBattleInterface::showBattleEffects(const std::vector { 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 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((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) diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index de00609d0..42c9a0186 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -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 diff --git a/client/battle/CCreatureAnimation.cpp b/client/battle/CCreatureAnimation.cpp index 135a386d3..f608c4de8 100644 --- a/client/battle/CCreatureAnimation.cpp +++ b/client/battle/CCreatureAnimation.cpp @@ -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); diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index e72539a95..620908a47 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -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); diff --git a/config/spell_info.json b/config/spell_info.json index 367899ff1..7cef02ec5 100644 --- a/config/spell_info.json +++ b/config/spell_info.json @@ -560,7 +560,7 @@ "type": "PRIMARY_SKILL", "subtype": "primSkill.defence", "valueType": "ADDITIVE_VALUE", - "duration": "N_TURNS", + "duration": "ONE_BATTLE", "values":[-3,-3,-4,-5] } ] diff --git a/lib/logging/CLogger.cpp b/lib/logging/CLogger.cpp index a9a2e8181..5f577f6e4 100644 --- a/lib/logging/CLogger.cpp +++ b/lib/logging/CLogger.cpp @@ -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) { diff --git a/lib/logging/CLogger.h b/lib/logging/CLogger.h index 2e1a74fa4..9689751c5 100644 --- a/lib/logging/CLogger.h +++ b/lib/logging/CLogger.h @@ -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.