From 146a5e5ef8126af630ee4a283e19c0e49757e397 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 6 Jul 2013 16:10:20 +0000 Subject: [PATCH] Made speed of most of battle animations time-based - speed of creature-related animation no longer depends on framerate - most of values from cranim.txt are now used correctly - removed BMPPalette struct in favor of SDL_Color - animation group "DEAD" that is used to display dead stacks, by default consists from last frame of "DEATH" group Notes: - animation speed may still need some adjustments - range of config property "battle/animationSpeed" has been changed. This may result in extremely fast animation. To fix - change animation speed via options. --- client/CAnimation.h | 20 +- client/CBitmapHandler.h | 10 +- client/CCreatureWindow.cpp | 4 +- client/CDefHandler.cpp | 20 +- client/CDefHandler.h | 4 +- client/CPlayerInterface.cpp | 2 +- client/FunctionList.h | 7 +- client/GUIClasses.cpp | 2 + client/battle/CBattleAnimations.cpp | 464 +++++++--------- client/battle/CBattleAnimations.h | 47 +- client/battle/CBattleInterface.cpp | 238 ++++----- client/battle/CBattleInterface.h | 15 +- client/battle/CBattleInterfaceClasses.cpp | 6 +- client/battle/CCreatureAnimation.cpp | 622 ++++++++++++---------- client/battle/CCreatureAnimation.h | 158 ++++-- client/gui/CIntObjectClasses.cpp | 10 +- client/gui/SDL_Extensions.cpp | 10 - lib/BattleHex.cpp | 14 +- lib/CCreatureHandler.cpp | 3 + server/CGameHandler.cpp | 10 +- test/CMapEditManagerTest.cpp | 6 +- 21 files changed, 845 insertions(+), 827 deletions(-) diff --git a/client/CAnimation.h b/client/CAnimation.h index 72af31be9..1046dff52 100644 --- a/client/CAnimation.h +++ b/client/CAnimation.h @@ -320,15 +320,14 @@ public: enum EAnimType // list of creature animations, numbers were taken from def files { - WHOLE_ANIM=-1, //just for convenience - MOVING=0, //will automatically add MOVE_START and MOVE_END to queue - MOUSEON=1, //rename to IDLE - HOLDING=2, //rename to STANDING + MOVING=0, + MOUSEON=1, + HOLDING=2, HITTED=3, DEFENCE=4, DEATH=5, //DEATH2=6, //unused? - TURN_L=7, //will automatically play second part of anim and rotate creature + TURN_L=7, TURN_R=8, //same //TURN_L2=9, //identical to previous? //TURN_R2=10, @@ -341,13 +340,10 @@ public: CAST_UP=17, CAST_FRONT=18, CAST_DOWN=19, - DHEX_ATTACK_UP=17, - DHEX_ATTACK_FRONT=18, - DHEX_ATTACK_DOWN=19, - MOVE_START=20, //no need to use this two directly - MOVING will be enought - MOVE_END=21 - //MOUSEON=22 //special group for border-only images - IDLE will be used as base - //READY=23 //same but STANDING is base + MOVE_START=20, + MOVE_END=21, + DEAD = 22 // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here + }; private: diff --git a/client/CBitmapHandler.h b/client/CBitmapHandler.h index 4823b04e9..d1cca6ae5 100644 --- a/client/CBitmapHandler.h +++ b/client/CBitmapHandler.h @@ -1,9 +1,5 @@ #pragma once - - -struct SDL_Surface; - /* * CBitmapHandler.h, part of VCMI engine * @@ -14,11 +10,7 @@ struct SDL_Surface; * */ -/// Struct which stands for a simple rgba palette -struct BMPPalette -{ - ui8 R,G,B,F; -}; +struct SDL_Surface; namespace BitmapHandler { diff --git a/client/CCreatureWindow.cpp b/client/CCreatureWindow.cpp index 30c5bc2dd..9fcb3bc95 100644 --- a/client/CCreatureWindow.cpp +++ b/client/CCreatureWindow.cpp @@ -106,8 +106,8 @@ CCreatureWindow::CCreatureWindow(const CStackInstance &st, CreWinType Type, std: fs += Upg; fs += boost::bind(&CCreatureWindow::close,this); CFunctionList cfl; - cfl = [&] { - LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[207], fs, nullptr, false, upgResCost); }; + cfl += boost::bind(&CPlayerInterface::showYesNoDialog, + LOCPLINT, CGI->generaltexth->allTexts[207], fs, nullptr, false, upgResCost); upgrade = new CAdventureMapButton("",CGI->generaltexth->zelp[446].second,cfl,385, 148,"IVIEWCR.DEF",SDLK_u); } else diff --git a/client/CDefHandler.cpp b/client/CDefHandler.cpp index f2ad4781e..7b9681125 100644 --- a/client/CDefHandler.cpp +++ b/client/CDefHandler.cpp @@ -52,7 +52,7 @@ CDefEssential::~CDefEssential() void CDefHandler::openFromMemory(ui8 *table, const std::string & name) { - BMPPalette palette[256]; + SDL_Color palette[256]; SDefEntry &de = * reinterpret_cast(table); ui8 *p; @@ -64,10 +64,10 @@ void CDefHandler::openFromMemory(ui8 *table, const std::string & name) for (ui32 it=0;it<256;it++) { - palette[it].R = de.palette[it].R; - palette[it].G = de.palette[it].G; - palette[it].B = de.palette[it].B; - palette[it].F = 255; + palette[it].r = de.palette[it].R; + palette[it].g = de.palette[it].G; + palette[it].b = de.palette[it].B; + palette[it].unused = 255; } // The SDefEntryBlock starts just after the SDefEntry @@ -128,7 +128,7 @@ void CDefHandler::expand(ui8 N,ui8 & BL, ui8 & BR) BR = N & 0x1F; } -SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const BMPPalette * palette) const +SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const { SDL_Surface * ret=nullptr; @@ -176,10 +176,10 @@ SDL_Surface * CDefHandler::getSprite (int SIndex, const ui8 * FDef, const BMPPal for(int i=0; i<256; ++i) { SDL_Color pr; - pr.r = palette[i].R; - pr.g = palette[i].G; - pr.b = palette[i].B; - pr.unused = palette[i].F; + pr.r = palette[i].r; + pr.g = palette[i].g; + pr.b = palette[i].b; + pr.unused = palette[i].unused; (*(ret->format->palette->colors+i))=pr; } diff --git a/client/CDefHandler.h b/client/CDefHandler.h index 66aab00d7..50a25bca9 100644 --- a/client/CDefHandler.h +++ b/client/CDefHandler.h @@ -3,7 +3,7 @@ #include "../lib/vcmi_endian.h" struct SDL_Surface; -struct BMPPalette; +struct SDL_Color; /* * CDefHandler.h, part of VCMI engine @@ -94,7 +94,7 @@ public: CDefHandler(); //c-tor ~CDefHandler(); //d-tor - SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const BMPPalette * palette) const; //saves picture with given number to "testtt.bmp" + SDL_Surface * getSprite (int SIndex, const ui8 * FDef, const SDL_Color * palette) const; //saves picture with given number to "testtt.bmp" static void expand(ui8 N,ui8 & BL, ui8 & BR); void openFromMemory(ui8 * table, const std::string & name); CDefEssential * essentialize(); diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index c41f32f33..c803bac0a 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -638,7 +638,7 @@ void CPlayerInterface::battleStacksHealedRes(const std::vectorbattleGetStackByID(healedStack.first); - if(battleInt->creAnims[healed->ID]->getType() == CCreatureAnim::DEATH) + if(battleInt->creAnims[healed->ID]->isDead()) { //stack has been resurrected battleInt->creAnims[healed->ID]->setType(CCreatureAnim::HOLDING); diff --git a/client/FunctionList.h b/client/FunctionList.h index 3fcddb6ce..4ced8d9c5 100644 --- a/client/FunctionList.h +++ b/client/FunctionList.h @@ -27,11 +27,8 @@ public: } CFunctionList(const boost::function &first) { - funcs.push_back(first); - } - CFunctionList(boost::function &first) - { - funcs.push_back(first); + if (first) + funcs.push_back(first); } CFunctionList(std::nullptr_t) {} diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index d936f524a..e6c0cff67 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -3,6 +3,8 @@ #include "gui/SDL_Extensions.h" #include "CAdvmapInterface.h" +#include "CBitmapHandler.h" +#include "CDefHandler.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" #include "../CCallback.h" diff --git a/client/battle/CBattleAnimations.cpp b/client/battle/CBattleAnimations.cpp index 3b0dfaced..648b5ead1 100644 --- a/client/battle/CBattleAnimations.cpp +++ b/client/battle/CBattleAnimations.cpp @@ -2,22 +2,26 @@ #include "CBattleAnimations.h" #include -#include "../CMusicHandler.h" -#include "../CGameInfo.h" -#include "CBattleInterface.h" + #include "CBattleInterfaceClasses.h" +#include "CBattleInterface.h" #include "CCreatureAnimation.h" -#include "../../lib/BattleState.h" + +#include "../CDefHandler.h" +#include "../CGameInfo.h" +#include "../CMusicHandler.h" #include "../CPlayerInterface.h" -#include "../../CCallback.h" -#include "../gui/SDL_Extensions.h" #include "../Graphics.h" #include "../gui/CCursorHandler.h" +#include "../gui/CGuiHandler.h" +#include "../gui/SDL_Extensions.h" + +#include "../../CCallback.h" +#include "../../lib/BattleState.h" #include "../../lib/CTownHandler.h" - CBattleAnimation::CBattleAnimation(CBattleInterface * _owner) -: owner(_owner), ID(_owner->animIDhelper++) + : owner(_owner), ID(_owner->animIDhelper++) {} void CBattleAnimation::endAnim() @@ -29,7 +33,6 @@ void CBattleAnimation::endAnim() elem.first = nullptr; } } - } bool CBattleAnimation::isEarliest(bool perStackConcurrency) @@ -59,33 +62,35 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency) return (ID == lowestMoveID) || (lowestMoveID == (owner->animIDhelper + 5)); } -CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack) -: CBattleAnimation(_owner), stack(_stack) +CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CStack * stack) + : CBattleAnimation(owner), + myAnim(owner->creAnims[stack->ID]), + stack(stack) {} -CCreatureAnimation* CBattleStackAnimation::myAnim() -{ - return owner->creAnims[stack->ID]; -} - void CAttackAnimation::nextFrame() { - if(myAnim()->getType() != group) - myAnim()->setType(group); + if(myAnim->getType() != group) + { + myAnim->setType(group); + myAnim->onAnimationReset += std::bind(&CAttackAnimation::endAnim, this); + } - if(myAnim()->onFirstFrameInGroup()) + if(!soundPlayed) { if(shooting) CCS->soundh->playSound(battle_sound(attackingStack->getCreature(), shoot)); else CCS->soundh->playSound(battle_sound(attackingStack->getCreature(), attack)); + soundPlayed = true; } - else if(myAnim()->onLastFrameInGroup()) - { - myAnim()->setType(CCreatureAnim::HOLDING); - endAnim(); - return; //execution of endAnim deletes this !!! - } + CBattleAnimation::nextFrame(); +} + +void CAttackAnimation::endAnim() +{ + myAnim->setType(CCreatureAnim::HOLDING); + CBattleStackAnimation::endAnim(); } bool CAttackAnimation::checkInitialConditions() @@ -94,7 +99,9 @@ bool CAttackAnimation::checkInitialConditions() } CAttackAnimation::CAttackAnimation(CBattleInterface *_owner, const CStack *attacker, BattleHex _dest, const CStack *defender) -: CBattleStackAnimation(_owner, attacker), dest(_dest), attackedStack(defender), attackingStack(attacker) +: CBattleStackAnimation(_owner, attacker), + soundPlayed(false), + dest(_dest), attackedStack(defender), attackingStack(attacker) { assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n"); @@ -113,13 +120,6 @@ killed(_attackedInfo.killed) bool CDefenceAnimation::init() { - //checking initial conditions - - //if(owner->creAnims[stackID]->getType() != 2) - //{ - // return false; - //} - if(attacker == nullptr && owner->battleEffects.size() > 0) return false; @@ -154,8 +154,6 @@ bool CDefenceAnimation::init() if(ID > lowestMoveID) return false; - - //reverse unit if necessary if (attacker && owner->curInt->cb->isToReverse(stack->position, attacker->position, owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID])) { @@ -179,54 +177,33 @@ bool CDefenceAnimation::init() if(killed) { CCS->soundh->playSound(battle_sound(stack->getCreature(), killed)); - myAnim()->setType(CCreatureAnim::DEATH); //death + myAnim->setType(CCreatureAnim::DEATH); //death } 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 + myAnim->setType(CCreatureAnim::HITTED); //getting hit } + myAnim->onAnimationReset += std::bind(&CDefenceAnimation::endAnim, this); + return true; //initialized successfuly } void CDefenceAnimation::nextFrame() { - if(!killed && myAnim()->getType() != CCreatureAnim::HITTED) - { - myAnim()->setType(CCreatureAnim::HITTED); - } - - if(!myAnim()->onLastFrameInGroup()) - { - if( myAnim()->getType() == CCreatureAnim::DEATH && (owner->animCount+1)%(4/owner->getAnimSpeed())==0 - && !myAnim()->onLastFrameInGroup() ) - { - myAnim()->incrementFrame(); - } - } - else - { - endAnim(); - } + assert(myAnim->getType() == CCreatureAnim::HITTED || myAnim->getType() == CCreatureAnim::DEATH); + CBattleAnimation::nextFrame(); } void CDefenceAnimation::endAnim() { - //restoring animType - - if(myAnim()->getType() == CCreatureAnim::HITTED) - myAnim()->setType(CCreatureAnim::HOLDING); - - //printing info to console - - //if(attacker!=nullptr) - // owner->printConsoleAttacked(stack, dmg, amountKilled, attacker); - - //const CStack * attacker = owner->curInt->cb->battleGetStackByID(IDby, false); - //const CStack * attacked = owner->curInt->cb->battleGetStackByID(stackID, false); + if (killed) + myAnim->setType(CCreatureAnim::DEAD); + else + myAnim->setType(CCreatureAnim::HOLDING); CBattleAnimation::endAnim(); @@ -234,7 +211,7 @@ void CDefenceAnimation::endAnim() } CDummyAnimation::CDummyAnimation(CBattleInterface * _owner, int howManyFrames) -: CBattleAnimation(_owner), counter(0), howMany(howManyFrames) +: CBattleAnimation(_owner), counter(0), howMany(howManyFrames) {} bool CDummyAnimation::init() @@ -261,12 +238,7 @@ bool CMeleeAttackAnimation::init() if( !CAttackAnimation::checkInitialConditions() ) return false; - //if(owner->creAnims[stackID]->getType()!=2) - //{ - // return false; - //} - - if(!attackingStack || myAnim()->getType() == 5) + if(!attackingStack || myAnim->isDead()) { endAnim(); @@ -277,7 +249,6 @@ bool CMeleeAttackAnimation::init() if (toReverse) { - owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true)); return false; } @@ -323,155 +294,124 @@ CMeleeAttackAnimation::CMeleeAttackAnimation(CBattleInterface * _owner, const CS : CAttackAnimation(_owner, attacker, _dest, _attacked) {} -void CMeleeAttackAnimation::nextFrame() -{ - /*for(std::list >::const_iterator it = owner->pendingAnims.begin(); it != owner->pendingAnims.end(); ++it) - { - CBattleMoveStart * anim = dynamic_cast(it->first); - CReverseAnim * anim2 = dynamic_cast(it->first); - if( (anim && anim->stackID == stackID) || (anim2 && anim2->stackID == stackID ) ) - return; - }*/ - - CAttackAnimation::nextFrame(); -} - void CMeleeAttackAnimation::endAnim() { - CBattleAnimation::endAnim(); - + CAttackAnimation::endAnim(); delete this; } +bool CMovementAnimation::shouldRotate() +{ + Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack->attackerOwned, stack, owner); + Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack->attackerOwned, stack, owner); + + if((begPosition.x > endPosition.x) && owner->creDir[stack->ID] == true) + { + return true; + } + else if ((begPosition.x < endPosition.x) && owner->creDir[stack->ID] == false) + { + return true; + } + return false; +} + bool CMovementAnimation::init() { -if( !isEarliest(false) ) + if( !isEarliest(false) ) return false; - //a few useful variables - steps = static_cast(myAnim()->framesInGroup(CCreatureAnim::MOVING) * owner->getAnimSpeedMultiplier() - 1); - - const CStack * movedStack = stack; - if(!movedStack || myAnim()->getType() == 5) + if(!stack || myAnim->isDead()) { endAnim(); return false; } - Point begPosition = CClickableHex::getXYUnitAnim(curStackPos, movedStack->attackerOwned, movedStack, owner); - Point endPosition = CClickableHex::getXYUnitAnim(nextHex, movedStack->attackerOwned, movedStack, owner); - - if(steps < 0 || stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1))) //no movement or teleport + if(owner->creAnims[stack->ID]->framesInGroup(CCreatureAnim::MOVING) == 0 || + stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1))) { - //this creature seems to have no move animation so we can end it immediately + //no movement or teleport, end immediately endAnim(); return false; } - whichStep = 0; - int hexWbase = 44, hexHbase = 42; - - //bool twoTiles = movedStack->doubleWide(); - - int mutPos = BattleHex::mutualPosition(curStackPos, nextHex); //reverse unit if necessary - if((begPosition.x > endPosition.x) && owner->creDir[stack->ID] == true) + if(shouldRotate()) { - owner->addNewAnim(new CReverseAnimation(owner, stack, curStackPos, true)); - return false; - } - else if ((begPosition.x < endPosition.x) && owner->creDir[stack->ID] == false) - { - owner->addNewAnim(new CReverseAnimation(owner, stack, curStackPos, true)); - return false; - } - - if(myAnim()->getType() != CCreatureAnim::MOVING) - { - myAnim()->setType(CCreatureAnim::MOVING); - } - //unit reversed - - if (owner->moveSh == -1) - { - owner->moveSh = CCS->soundh->playSound(battle_sound(movedStack->getCreature(), move), -1); - } - - //step shift calculation - posX = myAnim()->pos.x, posY = myAnim()->pos.y; // for precise calculations ;] - if(mutPos == -1 && movedStack->hasBonusOfType(Bonus::FLYING)) - { - steps *= distance; - steps /= 2; //to make animation faster - - stepX = (endPosition.x - begPosition.x) / static_cast(steps); - stepY = (endPosition.y - begPosition.y) / static_cast(steps); - } - else - { - switch(mutPos) + // it seems that H3 does NOT plays full rotation animation here in most situations + // Logical since it takes quite a lot of time + if (curentMoveIndex == 0) // full rotation only for moving towards first tile. { - case 0: - stepX = -1.0 * (hexWbase / (2.0 * steps)); - stepY = -1.0 * (hexHbase / (static_cast(steps))); - break; - case 1: - stepX = hexWbase / (2.0 * steps); - stepY = -1.0 * hexHbase / (static_cast(steps)); - break; - case 2: - stepX = hexWbase / static_cast(steps); - stepY = 0.0; - break; - case 3: - stepX = hexWbase / (2.0 * steps); - stepY = hexHbase / static_cast(steps); - break; - case 4: - stepX = -1.0 * hexWbase / (2.0 * steps); - stepY = hexHbase / static_cast(steps); - break; - case 5: - stepX = -1.0 * hexWbase / static_cast(steps); - stepY = 0.0; - break; + owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true)); + return false; + } + else + { + CReverseAnimation::rotateStack(owner, stack, oldPos); } } - //step shifts calculated + + if(myAnim->getType() != CCreatureAnim::MOVING) + { + myAnim->setType(CCreatureAnim::MOVING); + } + + if (owner->moveSoundHander == -1) + { + owner->moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1); + } + + Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack->attackerOwned, stack, owner); + Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack->attackerOwned, stack, owner); + + timeToMove = AnimationControls::getMovementDuration(stack->getCreature()); + + begX = begPosition.x; + begY = begPosition.y; + progress = 0; + distanceX = endPosition.x - begPosition.x; + distanceY = endPosition.y - begPosition.y; + + if (stack->hasBonus(Selector::type(Bonus::FLYING))) + { + float distance = sqrt(distanceX * distanceX + distanceY * distanceY); + + timeToMove *= distance / AnimationControls::getFlightDistance(stack->getCreature()); + } return true; } void CMovementAnimation::nextFrame() { - //moving instructions - posX += stepX; - myAnim()->pos.x = static_cast(posX); - posY += stepY; - myAnim()->pos.y = static_cast(posY); + progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove; - // Increments step count and check if we are finished with current animation - ++whichStep; - if(whichStep == steps) + //moving instructions + myAnim->pos.x = static_cast(begX + distanceX * progress ); + myAnim->pos.y = static_cast(begY + distanceY * progress ); + + CBattleAnimation::nextFrame(); + + if(progress >= 1.0) { // Sets the position of the creature animation sprites Point coords = CClickableHex::getXYUnitAnim(nextHex, owner->creDir[stack->ID], stack, owner); - myAnim()->pos = coords; + myAnim->pos = coords; // true if creature haven't reached the final destination hex - if ((nextPos + 1) < destTiles.size()) + if ((curentMoveIndex + 1) < destTiles.size()) { // update the next hex field which has to be reached by the stack - nextPos++; - curStackPos = nextHex; - nextHex = destTiles[nextPos]; + curentMoveIndex++; + oldPos = nextHex; + nextHex = destTiles[curentMoveIndex]; // update position of double wide creatures bool twoTiles = stack->doubleWide(); if(twoTiles && bool(stack->attackerOwned) && (owner->creDir[stack->ID] != bool(stack->attackerOwned) )) //big attacker creature is reversed - myAnim()->pos.x -= 44; + myAnim->pos.x -= 44; else if(twoTiles && (! bool(stack->attackerOwned) ) && (owner->creDir[stack->ID] != bool(stack->attackerOwned) )) //big defender creature is reversed - myAnim()->pos.x += 44; + myAnim->pos.x += 44; // re-init animation for(auto & elem : owner->pendingAnims) @@ -490,27 +430,32 @@ void CMovementAnimation::nextFrame() void CMovementAnimation::endAnim() { - const CStack * movedStack = stack; + assert(stack); - myAnim()->pos = CClickableHex::getXYUnitAnim(nextHex, owner->creDir[stack->ID], movedStack, owner); + myAnim->pos = CClickableHex::getXYUnitAnim(nextHex, owner->creDir[stack->ID], stack, owner); CBattleAnimation::endAnim(); - if(movedStack) - owner->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex)); + owner->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex)); - if(owner->moveSh >= 0) + if(owner->moveSoundHander != -1) { - CCS->soundh->stopSound(owner->moveSh); - owner->moveSh = -1; + CCS->soundh->stopSound(owner->moveSoundHander); + owner->moveSoundHander = -1; } delete this; } CMovementAnimation::CMovementAnimation(CBattleInterface *_owner, const CStack *_stack, std::vector _destTiles, int _distance) -: CBattleStackAnimation(_owner, _stack), destTiles(_destTiles), nextPos(0), distance(_distance), stepX(0.0), stepY(0.0) + : CBattleStackAnimation(_owner, _stack), + destTiles(_destTiles), + curentMoveIndex(0), + oldPos(stack->position), + nextHex(destTiles.front()), + begX(0), begY(0), + distanceX(0), distanceY(0), + timeToMove(0.0), + progress(0.0) { - curStackPos = stack->position; - nextHex = destTiles.front(); } CMovementEndAnimation::CMovementEndAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex destTile) @@ -522,8 +467,8 @@ bool CMovementEndAnimation::init() if( !isEarliest(true) ) return false; - if(!stack || myAnim()->framesInGroup(CCreatureAnim::MOVE_END) == 0 || - myAnim()->getType() == CCreatureAnim::DEATH) + if(!stack || myAnim->framesInGroup(CCreatureAnim::MOVE_END) == 0 || + myAnim->isDead()) { endAnim(); @@ -532,25 +477,19 @@ bool CMovementEndAnimation::init() CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving)); - myAnim()->setType(CCreatureAnim::MOVE_END); + myAnim->setType(CCreatureAnim::MOVE_END); + + myAnim->onAnimationReset += std::bind(&CMovementEndAnimation::endAnim, this); return true; } -void CMovementEndAnimation::nextFrame() -{ - if(myAnim()->onLastFrameInGroup()) - { - endAnim(); - } -} - void CMovementEndAnimation::endAnim() { CBattleAnimation::endAnim(); - if(myAnim()->getType() != CCreatureAnim::DEATH) - myAnim()->setType(CCreatureAnim::HOLDING); //resetting to default + if(myAnim->getType() != CCreatureAnim::DEAD) + myAnim->setType(CCreatureAnim::HOLDING); //resetting to default CCS->curh->show(); delete this; @@ -566,31 +505,19 @@ bool CMovementStartAnimation::init() return false; - if(!stack || myAnim()->getType() == CCreatureAnim::DEATH) + if(!stack || myAnim->isDead()) { CMovementStartAnimation::endAnim(); return false; } CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving)); - myAnim()->setType(CCreatureAnim::MOVE_START); + myAnim->setType(CCreatureAnim::MOVE_START); + myAnim->onAnimationReset += std::bind(&CMovementStartAnimation::endAnim, this); return true; } -void CMovementStartAnimation::nextFrame() -{ - if(myAnim()->onLastFrameInGroup()) - { - endAnim(); - } - else - { - if((owner->animCount+1)%(4/owner->getAnimSpeed())==0) - myAnim()->incrementFrame(); - } -} - void CMovementStartAnimation::endAnim() { CBattleAnimation::endAnim(); @@ -599,63 +526,64 @@ void CMovementStartAnimation::endAnim() } CReverseAnimation::CReverseAnimation(CBattleInterface * _owner, const CStack * stack, BattleHex dest, bool _priority) -: CBattleStackAnimation(_owner, stack), partOfAnim(1), secondPartSetup(false), hex(dest), priority(_priority) +: CBattleStackAnimation(_owner, stack), hex(dest), priority(_priority) {} bool CReverseAnimation::init() { - if(myAnim() == nullptr || myAnim()->getType() == CCreatureAnim::DEATH) + if(myAnim == nullptr || myAnim->isDead()) { endAnim(); - return false; //there is no such creature } if(!priority && !isEarliest(false)) return false; - //myAnim()->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner); + //myAnim->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner); - if(myAnim()->framesInGroup(CCreatureAnim::TURN_L)) - myAnim()->setType(CCreatureAnim::TURN_L); + if(myAnim->framesInGroup(CCreatureAnim::TURN_L)) + { + myAnim->setType(CCreatureAnim::TURN_L); + myAnim->onAnimationReset += std::bind(&CReverseAnimation::setupSecondPart, this); + } else + { setupSecondPart(); - - + } return true; } -void CReverseAnimation::nextFrame() -{ - if(partOfAnim == 1) //first part of animation - { - if(myAnim()->onLastFrameInGroup()) - { - partOfAnim = 2; - } - } - else if(partOfAnim == 2) - { - if(!secondPartSetup) - { - setupSecondPart(); - } - if(myAnim()->onLastFrameInGroup()) - { - endAnim(); - } - } -} - void CReverseAnimation::endAnim() { CBattleAnimation::endAnim(); if( stack->alive() )//don't do that if stack is dead - myAnim()->setType(CCreatureAnim::HOLDING); + myAnim->setType(CCreatureAnim::HOLDING); delete this; } +void CReverseAnimation::rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex) +{ + owner->creDir[stack->ID] = !owner->creDir[stack->ID]; + + owner->creAnims[stack->ID]->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner); + + if(stack->doubleWide()) + { + if(stack->attackerOwned) + { + if(!owner->creDir[stack->ID]) + owner->creAnims[stack->ID]->pos.x -= 44; + } + else + { + if(owner->creDir[stack->ID]) + owner->creAnims[stack->ID]->pos.x += 44; + } + } +} + void CReverseAnimation::setupSecondPart() { if(!stack) @@ -664,28 +592,13 @@ void CReverseAnimation::setupSecondPart() return; } - owner->creDir[stack->ID] = !owner->creDir[stack->ID]; + rotateStack(owner, stack, hex); - myAnim()->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner); - - if(stack->doubleWide()) + if(myAnim->framesInGroup(CCreatureAnim::TURN_R)) { - if(stack->attackerOwned) - { - if(!owner->creDir[stack->ID]) - myAnim()->pos.x -= 44; - } - else - { - if(owner->creDir[stack->ID]) - myAnim()->pos.x += 44; - } + myAnim->setType(CCreatureAnim::TURN_R); + myAnim->onAnimationReset += std::bind(&CReverseAnimation::endAnim, this); } - - secondPartSetup = true; - - if(myAnim()->framesInGroup(CCreatureAnim::TURN_R)) - myAnim()->setType(CCreatureAnim::TURN_R); else endAnim(); } @@ -701,7 +614,7 @@ bool CShootingAnimation::init() const CStack * shooter = attackingStack; - if(!shooter || myAnim()->getType() == CCreatureAnim::DEATH) + if(!shooter || myAnim->isDead()) { endAnim(); return false; @@ -730,6 +643,7 @@ bool CShootingAnimation::init() } ProjectileInfo spi; + spi.shotDone = false; spi.creID = shooter->getCreature()->idNumber; spi.stackID = shooter->ID; // reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) @@ -783,7 +697,7 @@ bool CShootingAnimation::init() if (attackedStack) { - double animSpeed = 23.0 * owner->getAnimSpeed(); // flight speed of projectile + double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile spi.lastStep = static_cast(sqrt(static_cast((destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y))) / animSpeed); if(spi.lastStep == 0) spi.lastStep = 1; @@ -795,7 +709,7 @@ bool CShootingAnimation::init() // Catapult attack spi.catapultInfo.reset(new CatapultProjectileInfo(Point(spi.x, spi.y), destPos)); - double animSpeed = 3.318 * owner->getAnimSpeed(); + double animSpeed = AnimationControls::getProjectileSpeed() / 10; spi.lastStep = abs((destPos.x - spi.x) / animSpeed); spi.dx = animSpeed; spi.dy = 0; @@ -854,10 +768,10 @@ bool CShootingAnimation::init() void CShootingAnimation::nextFrame() { - for(std::list >::const_iterator it = owner->pendingAnims.begin(); it != owner->pendingAnims.end(); ++it) + for(auto & it : owner->pendingAnims) { - CMovementStartAnimation * anim = dynamic_cast(it->first); - CReverseAnimation * anim2 = dynamic_cast(it->first); + CMovementStartAnimation * anim = dynamic_cast(it.first); + CReverseAnimation * anim2 = dynamic_cast(it.first); if( (anim && anim->stack->ID == stack->ID) || (anim2 && anim2->stack->ID == stack->ID && anim2->priority ) ) return; } @@ -867,7 +781,7 @@ void CShootingAnimation::nextFrame() void CShootingAnimation::endAnim() { - CBattleAnimation::endAnim(); + CAttackAnimation::endAnim(); delete this; } diff --git a/client/battle/CBattleAnimations.h b/client/battle/CBattleAnimations.h index 730bb43eb..10ffda68e 100644 --- a/client/battle/CBattleAnimations.h +++ b/client/battle/CBattleAnimations.h @@ -25,15 +25,14 @@ class CBattleAnimation protected: CBattleInterface * owner; public: - virtual bool init()=0; //to be called - if returned false, call again until returns true - virtual void nextFrame()=0; //call every new frame + 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 ui32 ID; //unique identifier - CBattleAnimation(CBattleInterface * _owner); }; @@ -41,16 +40,17 @@ public: class CBattleStackAnimation : public CBattleAnimation { public: + CCreatureAnimation * myAnim; //animation for our stack, managed by CBattleInterface const CStack * stack; //id of stack whose animation it is CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack); - - CCreatureAnimation * myAnim(); //animation for our stack }; /// This class is responsible for managing the battle attack animation class CAttackAnimation : public CBattleStackAnimation { + bool soundPlayed; + protected: BattleHex dest; //attacked hex bool shooting; @@ -60,6 +60,7 @@ protected: int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature public: void nextFrame(); + void endAnim(); bool checkInitialConditions(); CAttackAnimation(CBattleInterface *_owner, const CStack *attacker, BattleHex _dest, const CStack *defender); @@ -101,7 +102,6 @@ class CMeleeAttackAnimation : public CAttackAnimation { public: bool init(); - void nextFrame(); void endAnim(); CMeleeAttackAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked); @@ -112,14 +112,20 @@ public: class CMovementAnimation : public CBattleStackAnimation { private: - std::vector destTiles; //destination - BattleHex nextHex; - ui32 nextPos; - int distance; - double stepX, stepY; //how far stack is moved in one frame - double posX, posY; - int steps, whichStep; - int curStackPos; //position of stack before move + bool shouldRotate(); + + std::vector destTiles; //full path, includes already passed hexes + ui32 curentMoveIndex; // index of nextHex in destTiles + + BattleHex oldPos; //position of stack before move + BattleHex nextHex; // next hex, to which creature move right now + + double begX, begY; // starting position + double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft + + double timeToMove; // full length of movement animation + double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends + public: bool init(); void nextFrame(); @@ -136,7 +142,6 @@ private: BattleHex destinationTile; public: bool init(); - void nextFrame(); void endAnim(); CMovementEndAnimation(CBattleInterface * _owner, const CStack * _stack, BattleHex destTile); @@ -148,7 +153,6 @@ class CMovementStartAnimation : public CBattleStackAnimation { public: bool init(); - void nextFrame(); void endAnim(); CMovementStartAnimation(CBattleInterface * _owner, const CStack * _stack); @@ -158,14 +162,12 @@ public: /// Class responsible for animation of stack chaning direction (left <-> right) class CReverseAnimation : public CBattleStackAnimation { -private: - int partOfAnim; //1 - first, 2 - second - bool secondPartSetup; BattleHex hex; public: bool priority; //true - high, false - low bool init(); - void nextFrame(); + + static void rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex); void setupSecondPart(); void endAnim(); @@ -184,7 +186,8 @@ struct ProjectileInfo int stackID; //ID of stack int frameNum; //frame to display form projectile animation //bool spin; //if true, frameNum will be increased - int animStartDelay; //how many times projectile must be attempted to be shown till it's really show (decremented after hit) + int animStartDelay; //frame of shooter animation when projectile should appear + bool shotDone; // actual shot already done, projectile is flying bool reverse; //if true, projectile will be flipped by vertical asix std::shared_ptr catapultInfo; // holds info about the parabolic trajectory of the cannon }; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index dd49eab1f..47443e1bb 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -5,6 +5,7 @@ #include "../gui/SDL_Extensions.h" #include "../CAdvmapInterface.h" #include "../CAnimation.h" +#include "../CBitmapHandler.h" #include "../../lib/CObjectHandler.h" #include "../../lib/CHeroHandler.h" #include "../CDefHandler.h" @@ -43,8 +44,6 @@ const double M_PI = 3.14159265358979323846; using namespace boost::assign; -const time_t CBattleInterface::HOVER_ANIM_DELTA = 1; - /* * CBattleInterface.cpp, part of VCMI engine * @@ -57,13 +56,27 @@ const time_t CBattleInterface::HOVER_ANIM_DELTA = 1; CondSh CBattleInterface::animsAreDisplayed; -struct CMP_stack2 +static void onAnimationFinished(const CStack *stack, CCreatureAnimation * anim) { - inline bool operator ()(const CStack& a, const CStack& b) + if (anim->isIdle()) { - return (a.Speed())>(b.Speed()); + const CCreature *creature = stack->getCreature(); + + if (anim->framesInGroup(CCreatureAnim::MOUSEON) > 0) + { + if (float(rand() % 100) < creature->animation.timeBetweenFidgets * 10) + anim->playOnce(CCreatureAnim::MOUSEON); + else + anim->setType(CCreatureAnim::HOLDING); + } + else + { + anim->setType(CCreatureAnim::HOLDING); + } } -} cmpst2 ; + // always reset callback + anim->onAnimationReset += std::bind(&onAnimationFinished, stack, anim); +} static void transformPalette(SDL_Surface * surf, double rCor, double gCor, double bCor) { @@ -80,7 +93,6 @@ static void transformPalette(SDL_Surface * surf, double rCor, double gCor, doubl } } } -////////////////////// void CBattleInterface::addNewAnim(CBattleAnimation * anim) { @@ -93,10 +105,10 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe const SDL_Rect & myRect, shared_ptr att, shared_ptr defen) : background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0), - activeStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1), + activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1), currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellSelMode(NO_LOCATION), spellToCast(nullptr), sp(nullptr), siegeH(nullptr), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0), - givenCommand(nullptr), myTurn(false), resWindow(nullptr), moveStarted(false), moveSh(-1), bresult(nullptr) + givenCommand(nullptr), myTurn(false), resWindow(nullptr), moveStarted(false), moveSoundHander(-1), bresult(nullptr) { OBJ_CONSTRUCTION; @@ -719,7 +731,7 @@ void CBattleInterface::show(SDL_Surface * to) const CStack *s = stack; if(creAnims.find(s->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks continue; - if(creAnims[s->ID]->getType() != CCreatureAnim::DEATH && s->position >= 0) //don't show turrets here + if(creAnims[s->ID]->getType() != CCreatureAnim::DEAD && s->position >= 0) //don't show turrets here stackAliveByHex[s->position].push_back(s); } std::vector stackDeadByHex[GameConstants::BFIELD_SIZE]; @@ -728,7 +740,7 @@ void CBattleInterface::show(SDL_Surface * to) const CStack *s = stack; if(creAnims.find(s->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks continue; - if(creAnims[s->ID]->getType() == CCreatureAnim::DEATH) + if(creAnims[s->ID]->isDead()) stackDeadByHex[s->position].push_back(s); } @@ -790,7 +802,8 @@ void CBattleInterface::show(SDL_Surface * to) { for(size_t v=0; vID]->nextFrame(to, creAnims[elem[v]->ID]->pos.x, creAnims[elem[v]->ID]->pos.y, creDir[elem[v]->ID], animCount, false); //increment always when moving, never if stack died + creAnims[elem[v]->ID]->nextFrame(to, creAnims[elem[v]->ID]->pos.x, creAnims[elem[v]->ID]->pos.y, creDir[elem[v]->ID]); + creAnims[elem[v]->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000); } } std::vector flyingStacks; //flying stacks should be displayed later, over other stacks and obstacles @@ -964,7 +977,7 @@ void CBattleInterface::showAliveStacks(std::vector *aliveStacks, { const CStack *s = elem; - if(!s->hasBonusOfType(Bonus::FLYING) || creAnims[s->ID]->getType() != CCreatureAnim::DEATH) + if(!s->hasBonusOfType(Bonus::FLYING) || creAnims[s->ID]->getType() != CCreatureAnim::DEAD) showAliveStack(s, to); else flyingStacks->push_back(s); @@ -1376,8 +1389,9 @@ void CBattleInterface::newStack(const CStack * stack) if(stack->position < 0) //turret { - const CCreature & turretCreature = *CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter]; - creAnims[stack->ID] = new CCreatureAnimation(turretCreature.animDefName); + const CCreature * turretCreature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter]; + + creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature); // Turret positions are read out of the /config/wall_pos.txt int posID = 0; @@ -1399,13 +1413,18 @@ void CBattleInterface::newStack(const CStack * stack) coords.x = siegeH->town->town->clientInfo.siegePositions[posID].x + this->pos.x; coords.y = siegeH->town->town->clientInfo.siegePositions[posID].y + this->pos.y; } + creAnims[stack->ID]->pos.h = siegeH->town->town->clientInfo.siegeShooterCropHeight; } else { - creAnims[stack->ID] = new CCreatureAnimation(stack->getCreature()->animDefName); + creAnims[stack->ID] = AnimationControls::getAnimation(stack->getCreature()); + creAnims[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, creAnims[stack->ID]); + creAnims[stack->ID]->pos.h = creAnims[stack->ID]->getHeight(); } + creAnims[stack->ID]->pos.x = coords.x; + creAnims[stack->ID]->pos.y = coords.y; + creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth(); creAnims[stack->ID]->setType(CCreatureAnim::HOLDING); - creAnims[stack->ID]->pos = Rect(coords.x, coords.y, creAnims[stack->ID]->fullWidth, creAnims[stack->ID]->fullHeight); creDir[stack->ID] = stack->attackerOwned; } @@ -1541,14 +1560,14 @@ void CBattleInterface::giveCommand(Battle::ActionType action, BattleHex tile, ui { logGlobal->traceStream() << "Setting command for " << (stack ? stack->nodeName() : "hero"); myTurn = false; - activeStack = nullptr; + setActiveStack(nullptr); givenCommand->setn(ba); } else { curInt->cb->battleMakeTacticAction(ba); vstd::clear_pointer(ba); - activeStack = nullptr; + setActiveStack(nullptr); //next stack will be activated when action ends } } @@ -1618,7 +1637,7 @@ void CBattleInterface::battleFinished(const BattleResult& br) animsAreDisplayed.waitUntil(false); } displayBattleFinished(); - activeStack = nullptr; + setActiveStack(nullptr); } void CBattleInterface::displayBattleFinished() @@ -2041,17 +2060,49 @@ void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte) void CBattleInterface::setAnimSpeed(int set) { Settings speed = settings.write["battle"]["animationSpeed"]; - speed->Float() = set; + speed->Float() = float(set) / 100; } int CBattleInterface::getAnimSpeed() const { - return settings["battle"]["animationSpeed"].Float(); + return round(settings["battle"]["animationSpeed"].Float() * 100); +} + +void CBattleInterface::setActiveStack(const CStack * stack) +{ + if (activeStack) // update UI + creAnims[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder()); + + activeStack = stack; + + if (activeStack) // update UI + creAnims[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder()); +} + +void CBattleInterface::setHoveredStack(const CStack * stack) +{ + if (mouseHoveredStack) + creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder()); + + // stack must be alive and not active (which uses gold border instead) + if (stack && stack->alive() && stack != activeStack) + { + mouseHoveredStack = stack; + + if (mouseHoveredStack) + { + creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder()); + if (creAnims[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0) + creAnims[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON); + } + } + else + mouseHoveredStack = nullptr; } void CBattleInterface::activateStack() { - activeStack = stackToActivate; + setActiveStack(stackToActivate); stackToActivate = nullptr; const CStack *s = activeStack; @@ -2098,21 +2149,6 @@ void CBattleInterface::activateStack() GH.fakeMouseMove(); } -double CBattleInterface::getAnimSpeedMultiplier() const -{ - switch(getAnimSpeed()) - { - case 1: - return 3.5; - case 2: - return 2.2; - case 4: - return 1.0; - default: - return 0.0; - } -} - void CBattleInterface::endCastingSpell() { assert(spellDestSelectMode); @@ -2204,63 +2240,9 @@ void CBattleInterface::showAliveStack(const CStack *stack, SDL_Surface * to) int ID = stack->ID; if(creAnims.find(ID) == creAnims.end()) //eg. for summoned but not yet handled stacks return; - const CCreature *creature = stack->getCreature(); - SDL_Rect unitRect = {creAnims[ID]->pos.x, creAnims[ID]->pos.y, uint16_t(creAnims[ID]->fullWidth), uint16_t(creAnims[ID]->fullHeight)}; - CCreatureAnim::EAnimType animType = creAnims[ID]->getType(); - - int affectingSpeed = getAnimSpeed(); - if(animType == CCreatureAnim::MOUSEON || animType == CCreatureAnim::HOLDING) //standing stacks should not stand faster :) - affectingSpeed = 2; - bool incrementFrame = (animCount%(4/affectingSpeed)==0) && animType!=CCreatureAnim::DEATH && - animType!=CCreatureAnim::MOVE_START && animType!=CCreatureAnim::HOLDING; - - if (creature->idNumber == CreatureID::ARROW_TOWERS) - { - // a turret creature has a limited height, so cut it at a certain position; turret creature has no standing anim - unitRect.h = siegeH->town->town->clientInfo.siegeShooterCropHeight; - } - else - { - // standing animation - if(animType == CCreatureAnim::HOLDING) - { - if(standingFrame.find(ID)!=standingFrame.end()) - { - incrementFrame = (animCount%(8/affectingSpeed)==0); - if(incrementFrame) - { - ++standingFrame[ID]; - if(standingFrame[ID] == creAnims[ID]->framesInGroup(CCreatureAnim::HOLDING)) - { - standingFrame.erase(standingFrame.find(ID)); - } - } - } - else - { - if((rand()%50) == 0) - { - standingFrame.insert(std::make_pair(ID, 0)); - } - } - } - } - - // As long as the projectile of the shooter-stack is flying incrementFrame should be false - //bool shootingFinished = true; - for (auto & elem : projectiles) - { - if (elem.stackID == ID) - { - //shootingFinished = false; - if (elem.animStartDelay == 0) - incrementFrame = false; - } - } - - // Increment always when moving, never if stack died - creAnims[ID]->nextFrame(to, unitRect.x, unitRect.y, creDir[ID], animCount, incrementFrame, activeStack && ID==activeStack->ID, ID==mouseHoveredStack, &unitRect); + creAnims[ID]->nextFrame(to, creAnims[ID]->pos.x, creAnims[ID]->pos.y, creDir[ID], nullptr); + creAnims[ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000); //printing amount if(stack->count > 0 //don't print if stack is not alive @@ -2471,17 +2453,19 @@ void CBattleInterface::projectileShowHelper(SDL_Surface * to) if(to == nullptr) to = screen; std::list< std::list::iterator > toBeDeleted; - for(auto it=projectiles.begin(); it!=projectiles.end(); ++it) + for(auto it = projectiles.begin(); it!=projectiles.end(); ++it) { - // Creature have to be in a shooting anim and the anim start delay must be over. - // Otherwise abort to start moving the projectile. - if (it->animStartDelay > 0) + // Check if projectile is already visible (shooter animation did the shot) + if (!it->shotDone) { - if(it->animStartDelay == creAnims[it->stackID]->getAnimationFrame() + 1 - && creAnims[it->stackID]->getType() >= 14 && creAnims[it->stackID]->getType() <= 16) - it->animStartDelay = 0; + if (creAnims[it->stackID]->getCurrentFrame() > it->animStartDelay) + { + //at this point projectile should become visible + creAnims[it->stackID]->pause(); // pause animation + it->shotDone = true; + } else - continue; + continue; // wait... } SDL_Rect dst; @@ -2528,6 +2512,8 @@ void CBattleInterface::projectileShowHelper(SDL_Surface * to) } for(auto & elem : toBeDeleted) { + // resume animation + creAnims[elem->stackID]->play(); projectiles.erase(elem); } } @@ -2545,24 +2531,19 @@ void CBattleInterface::endAction(const BattleAction* action) } if(stack && action->actionType == Battle::WALK && - creAnims[action->stackNumber]->getType() != CCreatureAnim::HOLDING) //walk or walk & attack + !creAnims[action->stackNumber]->isIdle()) //walk or walk & attack { pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false)); } - if(action->actionType == Battle::CATAPULT) //catapult - { - } //check if we should reverse stacks //for some strange reason, it's not enough -// std::set stacks; -// stacks.insert(LOCPLINT->cb->battleGetStackByID(action->stackNumber)); -// stacks.insert(LOCPLINT->cb->battleGetStackByPos(action->destinationTile)); TStacks stacks = curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY); for(const CStack *s : stacks) { - if(s && creDir[s->ID] != bool(s->attackerOwned) && s->alive()) + if(s && creDir[s->ID] != bool(s->attackerOwned) && s->alive() + && creAnims[s->ID]->isIdle()) { addNewAnim(new CReverseAnimation(this, s, s->position, false)); } @@ -2613,6 +2594,9 @@ void CBattleInterface::showQueue() void CBattleInterface::startAction(const BattleAction* action) { + setActiveStack(nullptr); + setHoveredStack(nullptr); + if(action->actionType == Battle::END_TACTIC_PHASE) { SDL_FreeSurface(menu); @@ -2719,7 +2703,7 @@ void CBattleInterface::waitForAnims() void CBattleInterface::bEndTacticPhase() { - activeStack = nullptr; + setActiveStack(nullptr); btactEnd->block(true); tacticsMode = false; } @@ -2839,9 +2823,12 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) if (shere) ourStack = shere->owner == curInt->playerID; - //TODO: handle - bool noStackIsHovered = true; //will cause removing a blue glow - + //stack changed, update selection border + if (shere != mouseHoveredStack) + { + setHoveredStack(shere); + } + localActions.clear(); illegalActions.clear(); @@ -3145,23 +3132,8 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) cursorFrame = ECursor::COMBAT_QUERY; consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str(); realizeAction = [=]{ GH.pushInt(createCreWindow(shere, true)); }; - - //setting console text - const time_t curTime = time(nullptr); - CCreatureAnimation *hoveredStackAnim = creAnims[shere->ID]; - - if (shere->ID != mouseHoveredStack - && curTime > lastMouseHoveredStackAnimationTime + HOVER_ANIM_DELTA - && hoveredStackAnim->getType() == CCreatureAnim::HOLDING - && hoveredStackAnim->framesInGroup(CCreatureAnim::MOUSEON) > 0) - { - hoveredStackAnim->playOnce(CCreatureAnim::MOUSEON); - lastMouseHoveredStackAnimationTime = curTime; - } - noStackIsHovered = false; - mouseHoveredStack = shere->ID; - } break; + } } } else //no possible valid action, display message @@ -3249,11 +3221,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType) } }; } - realizeThingsToDo(); - if(noStackIsHovered) - mouseHoveredStack = -1; - } bool CBattleInterface::isCastingPossibleHere (const CStack * sactive, const CStack * shere, BattleHex myNumber) @@ -3447,7 +3415,7 @@ Rect CBattleInterface::hexPosition(BattleHex hex) const SDL_Surface * CBattleInterface::imageOfObstacle(const CObstacleInstance &oi) const { - int frameIndex = (animCount+1) / (40/getAnimSpeed()); + int frameIndex = (animCount+1) * 25 / getAnimSpeed(); switch(oi.obstacleType) { case CObstacleInstance::USUAL: diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 8722f9cfa..f99f8f1af 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -125,20 +125,16 @@ private: std::map< int, bool > creDir; // //TODO: move it to battle callback ui8 animCount; const CStack * activeStack; //number of active stack; nullptr - no one + const CStack * mouseHoveredStack; // stack below mouse pointer, used for border animation const CStack * stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none const CStack * selectedStack; //for Teleport / Sacrifice void activateStack(); //sets activeStack to stackToActivate etc. - int mouseHoveredStack; //stack hovered by mouse; if -1 -> none - time_t lastMouseHoveredStackAnimationTime; // time when last mouse hovered animation occurred - static const time_t HOVER_ANIM_DELTA; std::vector occupyableHexes, //hexes available for active stack attackableHexes; //hexes attackable by active stack bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back int previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago int currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon) int attackingHex; //hex from which the stack would perform attack with current cursor - double getAnimSpeedMultiplier() const; //returns multiplier for number of frames in a group - std::map standingFrame; //number of frame in standing animation by stack ID, helps in showing 'random moves' shared_ptr tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players bool tacticsMode; @@ -156,6 +152,9 @@ private: PossibleActions selectedAction; //last action chosen (and saved) by player PossibleActions illegalAction; //most likely action that can't be performed here + void setActiveStack(const CStack * stack); + void setHoveredStack(const CStack * stack); + void requestAutofightingAIToTakeAction(); void getPossibleActionsForStack (const CStack * stack); //called when stack gets its turn @@ -214,8 +213,8 @@ public: void setPrintCellBorders(bool set); //if true, cell borders will be printed void setPrintStackRange(bool set); //if true,range of active stack will be printed void setPrintMouseShadow(bool set); //if true, hex under mouse will be shaded - void setAnimSpeed(int set); //speed of animation; 1 - slowest, 2 - medium, 4 - fastest - int getAnimSpeed() const; //speed of animation; 1 - slowest, 2 - medium, 4 - fastest + void setAnimSpeed(int set); //speed of animation; range 1..100 + int getAnimSpeed() const; //speed of animation; range 1..100 std::vector bfield; //11 lines, 17 hexes on each //std::vector< CBattleObstacle * > obstacles; //vector of obstacles on the battlefield @@ -225,7 +224,7 @@ public: CBattleResultWindow * resWindow; //window of end of battle bool moveStarted; //if true, the creature that is already moving is going to make its first step - int moveSh; // sound handler used when moving a unit + int moveSoundHander; // sound handler used when moving a unit const BattleResult * bresult; //result of a battle; if non-zero then display when all animations end diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 057d228f5..7793a0feb 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -256,9 +256,9 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt mouseShadow->select(settings["battle"]["mouseShadow"].Bool()); animSpeeds = new CHighlightableButtonsGroup(0); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[422].first),CGI->generaltexth->zelp[422].second, "sysopb9.def", 28, 225, 1); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[423].first),CGI->generaltexth->zelp[423].second, "sysob10.def", 92, 225, 2); - animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[424].first),CGI->generaltexth->zelp[424].second, "sysob11.def",156, 225, 4); + animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[422].first),CGI->generaltexth->zelp[422].second, "sysopb9.def", 28, 225, 40); + animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[423].first),CGI->generaltexth->zelp[423].second, "sysob10.def", 92, 225, 63); + animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[424].first),CGI->generaltexth->zelp[424].second, "sysob11.def",156, 225, 100); animSpeeds->select(owner->getAnimSpeed(), 1); animSpeeds->onChange = boost::bind(&CBattleInterface::setAnimSpeed, owner, _1); diff --git a/client/battle/CCreatureAnimation.cpp b/client/battle/CCreatureAnimation.cpp index b0e1c077a..8754fa200 100644 --- a/client/battle/CCreatureAnimation.cpp +++ b/client/battle/CCreatureAnimation.cpp @@ -1,12 +1,16 @@ #include "StdInc.h" #include "CCreatureAnimation.h" -#include "../../lib/filesystem/CResourceLoader.h" -#include "../../lib/VCMI_Lib.h" +#include "../../lib/CConfigHandler.h" +#include "../../lib/CCreatureHandler.h" #include "../../lib/vcmi_endian.h" #include "../gui/SDL_Extensions.h" #include "../gui/SDL_Pixels.h" +#include "../../lib/filesystem/CResourceLoader.h" +#include "../../lib/filesystem/CBinaryReader.h" +#include "../../lib/filesystem/CMemoryStream.h" + /* * CCreatureAnimation.cpp, part of VCMI engine * @@ -17,6 +21,100 @@ * */ +static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 }; +static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 }; +static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 }; + +SDL_Color AnimationControls::getBlueBorder() +{ + return creatureBlueBorder; +} + +SDL_Color AnimationControls::getGoldBorder() +{ + return creatureGoldBorder; +} + +SDL_Color AnimationControls::getNoBorder() +{ + return creatureNoBorder; +} + +CCreatureAnimation * AnimationControls::getAnimation(const CCreature * creature) +{ + auto func = boost::bind(&AnimationControls::getCreatureAnimationSpeed, creature, _1, _2); + return new CCreatureAnimation(creature->animDefName, func); +} + +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 + + + // a lot of arbitrary multipliers, mostly to make animation speed closer to H3 + CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group); + const float baseSpeed = 10; + const float speedMult = settings["battle"]["animationSpeed"].Float() * 20; + const float speed = baseSpeed * speedMult; + + switch (type) + { + case CCreatureAnim::MOVING: + return speed / creature->animation.walkAnimationTime / anim->framesInGroup(type); + + case CCreatureAnim::MOUSEON: + 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: + case CCreatureAnim::CAST_UP: + case CCreatureAnim::CAST_FRONT: + case CCreatureAnim::CAST_DOWN: + return speed * 2 / creature->animation.attackAnimationTime / anim->framesInGroup(type); + + case CCreatureAnim::TURN_L: + case CCreatureAnim::TURN_R: + return speed; + + case CCreatureAnim::MOVE_START: + case CCreatureAnim::MOVE_END: + return speed / 5; + + case CCreatureAnim::HITTED: + case CCreatureAnim::DEFENCE: + case CCreatureAnim::DEATH: + case CCreatureAnim::DEAD: + return speed / 5; + + default: + assert(0); + return 1; + } +} + +float AnimationControls::getProjectileSpeed() +{ + return settings["battle"]["animationSpeed"].Float() * 100; +} + +float AnimationControls::getMovementDuration(const CCreature * creature) +{ + return settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime; +} + +float AnimationControls::getFlightDistance(const CCreature * creature) +{ + return creature->animation.flightAnimationDistance * 200; +} + CCreatureAnim::EAnimType CCreatureAnimation::getType() const { return type; @@ -24,325 +122,313 @@ CCreatureAnim::EAnimType CCreatureAnimation::getType() const void CCreatureAnimation::setType(CCreatureAnim::EAnimType type) { - assert(framesInGroup(type) > 0 && "Bad type for void CCreatureAnimation::setType(int type)!"); + assert(type >= 0); + assert(framesInGroup(type) != 0); + this->type = type; - internalFrame = 0; - if(type!=-1) - { - curFrame = frameGroups[type][0]; - } - else - { - if(curFrame>=frames) - { - curFrame = 0; - } - } + currentFrame = 0; + once = false; + + play(); } -CCreatureAnimation::CCreatureAnimation(std::string name) : internalFrame(0), once(false) +CCreatureAnimation::CCreatureAnimation(std::string name, TSpeedController controller) + : defName(name), + speed(0.1), + currentFrame(0), + elapsedTime(0), + type(CCreatureAnim::HOLDING), + border({0, 0, 0, 0}), + speedController(controller), + once(false) { - //load main file - FDef = CResourceHandler::get()->loadData( - ResourceID(std::string("SPRITES/") + name, EResType::ANIMATION)).first.release(); + // separate block to avoid accidental use of "data" after it was moved into "pixelData" + { + auto data = CResourceHandler::get()->loadData( + ResourceID(std::string("SPRITES/") + name, EResType::ANIMATION)); - //init anim data - int i,j, totalInBlock; + pixelData = std::move(data.first); + pixelDataSize = data.second; + } - defName=name; - i = 0; - DEFType = read_le_u32(FDef + i); i+=4; - fullWidth = read_le_u32(FDef + i); i+=4; - fullHeight = read_le_u32(FDef + i); i+=4; - i=0xc; - totalBlocks = read_le_u32(FDef + i); i+=4; + CBinaryReader reader(new CMemoryStream(pixelData.get(), pixelDataSize)); + + reader.readInt32(); // def type, unused + + fullWidth = reader.readInt32(); + fullHeight = reader.readInt32(); + + int totalBlocks = reader.readInt32(); - i=0x10; for (auto & elem : palette) { - elem.R = FDef[i++]; - elem.G = FDef[i++]; - elem.B = FDef[i++]; - elem.F = 0; + elem.r = reader.readUInt8(); + elem.g = reader.readUInt8(); + elem.b = reader.readUInt8(); + elem.unused = 0; } - i=0x310; - totalEntries=0; - for (int z=0; z frameIDs; - int group = read_le_u32(FDef + i); i+=4; //block ID - totalInBlock = read_le_u32(FDef + i); i+=4; - for (j=SEntries.size(); j= float(framesInGroup(type))) { - ++internalFrame; - if(internalFrame == frameGroups[type].size()) //rewind - { - internalFrame = 0; - if(once) //playing animation once - return to standing animation - { - type = CCreatureAnim::HOLDING; - once = false; - curFrame = frameGroups[2][0]; - } - else // - { - curFrame = frameGroups[type][0]; - } - } - curFrame = frameGroups[type][internalFrame]; - } - else //when whole animation is played - { - ++curFrame; - if(curFrame>=frames) - curFrame = 0; - } -} + // just in case of extremely low fps + while (currentFrame >= float(framesInGroup(type))) + currentFrame -= framesInGroup(type); -int CCreatureAnimation::getFrame() const -{ - return curFrame; -} + if (once) + setType(CCreatureAnim::HOLDING); -int CCreatureAnimation::getAnimationFrame() const -{ - return internalFrame; -} - -bool CCreatureAnimation::onFirstFrameInGroup() -{ - return internalFrame == 0; -} - -bool CCreatureAnimation::onLastFrameInGroup() -{ - if(internalFrame == frameGroups[type].size() - 1) + endAnimation(); return true; + } return false; } +void CCreatureAnimation::setBorderColor(SDL_Color palette) +{ + border = palette; +} + +int CCreatureAnimation::getWidth() const +{ + return fullWidth; +} + +int CCreatureAnimation::getHeight() const +{ + return fullHeight; +} + +float CCreatureAnimation::getCurrentFrame() const +{ + return currentFrame; +} + void CCreatureAnimation::playOnce( CCreatureAnim::EAnimType type ) { setType(type); once = true; } - -template -int CCreatureAnimation::nextFrameT(SDL_Surface * dest, int x, int y, bool attacker, ui8 animCount, bool IncrementFrame /*= true*/, bool yellowBorder /*= false*/, bool blueBorder /*= false*/, SDL_Rect * destRect /*= nullptr*/) +inline int getBorderStrength(float time) { - //increasing frame number - int SIndex = curFrame; - if (IncrementFrame) - incrementFrame(); + float borderStrength = fabs(round(time) - time) * 2; // generate value in range 0-1 -#if 0 - long SpriteWidth, SpriteHeight, //sprite format - LeftMargin, RightMargin, TopMargin,BottomMargin, - i, FullHeight, - -#endif - ui8 SegmentType, SegmentLength; - ui32 i; - - i = SEntries[SIndex].offset; - - /*int prSize = read_le_u32(FDef + i);*/ i += 4; //TODO use me - const ui32 defType2 = read_le_u32(FDef + i); i += 4; - const ui32 FullWidth = read_le_u32(FDef + i); i += 4; - const ui32 FullHeight = read_le_u32(FDef + i); i += 4; - const ui32 SpriteWidth = read_le_u32(FDef + i); i += 4; - const ui32 SpriteHeight = read_le_u32(FDef + i); i += 4; - const int LeftMargin = read_le_u32(FDef + i); i += 4; - const int TopMargin = read_le_u32(FDef + i); i += 4; - const int RightMargin = FullWidth - SpriteWidth - LeftMargin; - const int BottomMargin = FullHeight - SpriteHeight - TopMargin; - - if (defType2 == 1) //as it should be always in creature animations - { - const int BaseOffsetor = i; - int ftcp = 0; - - if (TopMargin > 0) - { - ftcp += FullWidth * TopMargin; - } - ui32 *RLEntries = (ui32 *)(FDef + BaseOffsetor); - - for (int i = 0; i < SpriteHeight; i++) - { - int BaseOffset = BaseOffsetor + read_le_u32(RLEntries + i); - int TotalRowLength; // length of read segment - - if (LeftMargin > 0) - { - ftcp += LeftMargin; - } - - TotalRowLength = 0; - - // Note: Bug fixed (Rev 2115): The implementation of omitting lines was false. - // We've to calculate several things so not showing/putting pixels should suffice. - - int yB = ftcp / FullWidth + y; - - do - { - SegmentType = FDef[BaseOffset++]; - SegmentLength = FDef[BaseOffset++]; - - const int remainder = ftcp % FullWidth; - int xB = (attacker ? remainder : FullWidth - remainder - 1) + x; - - const ui8 aCountMod = (animCount & 0x20) ? ((animCount & 0x1e) >> 1) << 4 : (0x0f - ((animCount & 0x1e) >> 1)) << 4; - - for (int k = 0; k <= SegmentLength; k++) - { - if(xB >= 0 && xB < dest->w && yB >= 0 && yB < dest->h) - { - if(!destRect || (destRect->x <= xB && destRect->x + destRect->w > xB && destRect->y <= yB && destRect->y + destRect->h > yB)) - { - const ui8 colorNr = SegmentType == 0xff ? FDef[BaseOffset+k] : SegmentType; - putPixel(dest, xB, yB, palette[colorNr], colorNr, yellowBorder, blueBorder, aCountMod); - } - } - ftcp++; //increment pos - if(attacker) - xB++; - else - xB--; - if ( SegmentType == 0xFF && TotalRowLength+k+1 >= SpriteWidth ) - break; - } - if (SegmentType == 0xFF) - { - BaseOffset += SegmentLength+1; - } - - TotalRowLength+=SegmentLength+1; - } while(TotalRowLength < SpriteWidth); - if (RightMargin > 0) - { - ftcp += RightMargin; - } - } - if (BottomMargin > 0) - { - ftcp += BottomMargin * FullWidth; - } - } - - return 0; + return borderStrength * 155 + 100; // scale to 0-255 } -int CCreatureAnimation::nextFrame(SDL_Surface *dest, int x, int y, bool attacker, ui8 animCount, bool IncrementFrame, bool yellowBorder, bool blueBorder, SDL_Rect * destRect) +static SDL_Color genShadow(ui8 alpha) +{ + return {0, 0, 0, alpha}; +} + +static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base) +{ + return {base.r, base.g, base.b, ui8(base.unused * alpha / 256)}; +} + +static ui8 mixChannels(ui8 c1, ui8 c2, ui8 a1, ui8 a2) +{ + return c1*a1 / 256 + c2*a2*(255 - a1) / 256 / 256; +} + +static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over) +{ + return { + mixChannels(over.r, base.r, over.unused, base.unused), + mixChannels(over.g, base.g, over.unused, base.unused), + mixChannels(over.b, base.b, over.unused, base.unused), + ui8(over.unused + base.unused * (255 - over.unused) / 256) + }; +} + +std::array CCreatureAnimation::genSpecialPalette() +{ + std::array ret; + + ret[0] = genShadow(0); + ret[1] = genShadow(64); + ret[2] = genShadow(128); + ret[3] = genShadow(128); + ret[4] = genShadow(128); + ret[5] = genBorderColor(getBorderStrength(elapsedTime), border); + ret[6] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border)); + ret[7] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border)); + + return ret; +} + +template +void CCreatureAnimation::nextFrameT(SDL_Surface * dest, int x, int y, bool rotate, SDL_Rect * destRect /*= nullptr*/) +{ + assert(dataOffsets.count(type) && dataOffsets.at(type).size() > size_t(currentFrame)); + + ui32 offset = dataOffsets.at(type).at(floor(currentFrame)); + + CBinaryReader reader(new CMemoryStream(pixelData.get(), pixelDataSize)); + + reader.getStream()->seek(offset); + + reader.readUInt32(); // unused, size of pixel data for this frame + const ui32 defType2 = reader.readUInt32(); + const ui32 fullWidth = reader.readUInt32(); + /*const ui32 fullHeight =*/ reader.readUInt32(); + const ui32 spriteWidth = reader.readUInt32(); + const ui32 spriteHeight = reader.readUInt32(); + const int leftMargin = reader.readInt32(); + const int topMargin = reader.readInt32(); + + const int rightMargin = fullWidth - spriteWidth - leftMargin; + //const int bottomMargin = fullHeight - spriteHeight - topMargin; + + const size_t baseOffset = reader.getStream()->tell(); + + assert(defType2 == 1); + + auto specialPalette = genSpecialPalette(); + + for (ui32 i=0; i(dest, destX + (rotate?(-j):(j)), destY, lineData[currentOffset + j], specialPalette, destRect); + + currentOffset += length; + } + else// RLE + { + if (type != 0) // transparency row, handle it here for speed + { + for (size_t j=0; j(dest, destX + (rotate?(-j):(j)), destY, type, specialPalette, destRect); + } + } + + destX += rotate ? (-length) : (length); + totalRowLength += length; + } + } +} + +void CCreatureAnimation::nextFrame(SDL_Surface *dest, int x, int y, bool attacker, SDL_Rect * destRect) { switch(dest->format->BytesPerPixel) { - case 2: return nextFrameT<2>(dest, x, y, attacker, animCount, IncrementFrame, yellowBorder, blueBorder, destRect); - case 3: return nextFrameT<3>(dest, x, y, attacker, animCount, IncrementFrame, yellowBorder, blueBorder, destRect); - case 4: return nextFrameT<4>(dest, x, y, attacker, animCount, IncrementFrame, yellowBorder, blueBorder, destRect); + case 2: return nextFrameT<2>(dest, x, y, !attacker, destRect); + case 3: return nextFrameT<3>(dest, x, y, !attacker, destRect); + case 4: return nextFrameT<4>(dest, x, y, !attacker, destRect); default: logGlobal->errorStream() << (int)dest->format->BitsPerPixel << " bpp is not supported!!!"; - return -1; } } int CCreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const { - if(frameGroups.find(group) == frameGroups.end()) + if(dataOffsets.count(group) == 0) return 0; - return frameGroups.find(group)->second.size(); + + return dataOffsets.at(group).size(); } -CCreatureAnimation::~CCreatureAnimation() +ui8 * CCreatureAnimation::getPixelAddr(SDL_Surface * dest, int X, int Y) const { - delete [] FDef; + return (ui8*)dest->pixels + X * dest->format->BytesPerPixel + Y * dest->pitch; } template -inline void CCreatureAnimation::putPixel( - SDL_Surface * dest, - const int & ftcpX, - const int & ftcpY, - const BMPPalette & color, - const ui8 & palc, - const bool & yellowBorder, - const bool & blueBorder, - const ui8 & animCount -) const -{ - if(palc!=0) +inline void CCreatureAnimation::putPixelAt(SDL_Surface * dest, int X, int Y, size_t index, const std::array & special, SDL_Rect * destRect) const +{ + if (destRect == nullptr) + putPixel(getPixelAddr(dest, X, Y), palette[index], index, special); + else { - Uint8 * p = (Uint8*)dest->pixels + ftcpX*dest->format->BytesPerPixel + ftcpY*dest->pitch; - if(palc > 7) //normal color - { - ColorPutter::PutColor(p, color.R, color.G, color.B); - } - else if((yellowBorder || blueBorder) && (palc == 6 || palc == 7)) //selection highlight - { - if(blueBorder) - ColorPutter::PutColor(p, 0, 0x0f + animCount, 0x0f + animCount); - else - ColorPutter::PutColor(p, 0x0f + animCount, 0x0f + animCount, 0); - } - else if (palc == 5) //selection highlight or transparent - { - if(blueBorder) - ColorPutter::PutColor(p, color.B, color.G - 0xf0 + animCount, color.R - 0xf0 + animCount); //shouldn't it be reversed? its bgr instead of rgb - else if (yellowBorder) - ColorPutter::PutColor(p, color.R - 0xf0 + animCount, color.G - 0xf0 + animCount, color.B); - } - else //shadow - { - //determining transparency value, 255 or 0 should be already filtered - static Uint16 colToAlpha[8] = {255,192,128,128,128,255,128,192}; - Uint16 alpha = colToAlpha[palc]; - - if(bpp != 3 && bpp != 4) - { - ColorPutter::PutColor(p, 0, 0, 0, alpha); - } - else - { - p[0] = (p[0] * alpha)>>8; - p[1] = (p[1] * alpha)>>8; - p[2] = (p[2] * alpha)>>8; - } - } + if ( X > destRect->x && X < destRect->w + destRect->x && + Y > destRect->y && Y < destRect->h + destRect->y ) + putPixel(getPixelAddr(dest, X, Y), palette[index], index, special); } } + +template +inline void CCreatureAnimation::putPixel(ui8 * dest, const SDL_Color & color, size_t index, const std::array & special) const +{ + if (index < 8) + { + const SDL_Color & pal = special[index]; + ColorPutter::PutColor(dest, pal.r, pal.g, pal.b, pal.unused); + } + else + { + ColorPutter::PutColor(dest, color.r, color.g, color.b); + } +} + +bool CCreatureAnimation::isDead() const +{ + return getType() == CCreatureAnim::DEAD + || getType() == CCreatureAnim::DEATH; +} + +bool CCreatureAnimation::isIdle() const +{ + return getType() == CCreatureAnim::HOLDING + || getType() == CCreatureAnim::MOUSEON; +} + +void CCreatureAnimation::pause() +{ + speed = 0; +} + +void CCreatureAnimation::play() +{ + speed = speedController(this, type); +} diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index bd9fb5704..390d1c1ad 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -1,8 +1,7 @@ #pragma once - -#include "../CDefHandler.h" -#include "../../client/CBitmapHandler.h" +#include "../FunctionList.h" +//#include "../CDefHandler.h" #include "../CAnimation.h" /* @@ -15,65 +14,124 @@ * */ -struct BMPPalette; class CIntObject; +class CCreatureAnimation; + +/// Namespace for some common controls of animations +namespace AnimationControls +{ + /// get SDL_Color for creature selection borders + SDL_Color getBlueBorder(); + SDL_Color getGoldBorder(); + SDL_Color getNoBorder(); + + /// creates animation object with preset speed control + CCreatureAnimation * getAnimation(const CCreature * creature); + + /// returns animation speed of specific group, taking in mind game setting + 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 duration of full movement animation, in seconds. Needed to move animation on screen + float getMovementDuration(const CCreature * creature); + + /// Returns distance on which flying creatures should during one animation loop + float getFlightDistance(const CCreature * creature); +} /// Class which manages animations of creatures/units inside battles +/// TODO: split into constant image container and class that does *control* of animation class CCreatureAnimation : public CIntObject { -private: - int totalEntries, DEFType, totalBlocks; - BMPPalette palette[256]; - struct SEntry - { - int offset; - int group; - } ; - std::vector SEntries ; - std::string defName, curDir; - - template - void putPixel( - SDL_Surface * dest, - const int & ftcpX, - const int & ftcpY, - const BMPPalette & color, - const ui8 & palc, - const bool & yellowBorder, - const bool & blueBorder, - const ui8 & animCount - ) const; - - //////////// - - ui8 * FDef; //animation raw data - int curFrame, internalFrame; //number of currently displayed frame - ui32 frames; //number of frames - CCreatureAnim::EAnimType type; //type of animation being displayed (-1 - whole animation, >0 - specified part [default: -1]) - - template - int nextFrameT(SDL_Surface * dest, int x, int y, bool attacker, ui8 animCount, bool incrementFrame = true, bool yellowBorder = false, bool blueBorder = false, SDL_Rect * destRect = nullptr); //0 - success, any other - error //print next - int nextFrameMiddle(SDL_Surface * dest, int x, int y, bool attacker, ui8 animCount, bool IncrementFrame = true, bool yellowBorder = false, bool blueBorder = false, SDL_Rect * destRect = nullptr); //0 - success, any other - error //print next - - std::map > frameGroups; //groups of frames; [groupID] -> vector of frame IDs in group - bool once; - public: - int fullWidth, fullHeight; //read-only, please! - CCreatureAnimation(std::string name); //c-tor - ~CCreatureAnimation(); //d-tor + typedef boost::function TSpeedController; + +private: + std::string defName; + + int fullWidth, fullHeight; + + // palette, as read from def file + std::array palette; + + //key = id of group (note that some groups may be missing) + //value = offset of pixel data for each frame, vector size = number of frames in group + std::map> dataOffsets; + + //animation raw data + //TODO: use vector instead? + unique_ptr pixelData; + size_t pixelDataSize; + + // speed of animation, measure in frames per second + float speed; + + // currently displayed frame. Float to allow H3-style animations where frames + // don't display for integer number of frames + float currentFrame; + // cumulative, real-time duration of animation. Used for effects like selection border + float elapsedTime; + CCreatureAnim::EAnimType type; //type of animation being displayed + + // border color, disabled if alpha = 0 + SDL_Color border; + + TSpeedController speedController; + + bool once; // animation will be played once and the reset to idling + + ui8 * getPixelAddr(SDL_Surface * dest, int ftcpX, int ftcpY) const; + + template + void putPixelAt(SDL_Surface * dest, int X, int Y, size_t index, const std::array & special, SDL_Rect * destRect = nullptr) const; + + template + void putPixel( ui8 * dest, const SDL_Color & color, size_t index, const std::array & special) const; + + template + void nextFrameT(SDL_Surface * dest, int x, int y, bool rotate, SDL_Rect * destRect = nullptr); + + void endAnimation(); + + /// creates 8 special colors for current frame + std::array genSpecialPalette(); +public: + + // function(s) that will be called when animation ends, after reset to 1st frame + // NOTE that these function will be fired only once + CFunctionList onAnimationReset; + + int getWidth() const; + int getHeight() const; + + /// Constructor + /// name - path to .def file, relative to SPRITES/ directory + /// controller - function that will return for how long *each* frame + /// in specified group of animation should be played, measured in seconds + CCreatureAnimation(std::string name, TSpeedController speedController); //c-tor void setType(CCreatureAnim::EAnimType type); //sets type of animation and cleares framecount CCreatureAnim::EAnimType getType() const; //returns type of animation - int nextFrame(SDL_Surface * dest, int x, int y, bool attacker, ui8 animCount, bool incrementFrame = true, bool yellowBorder = false, bool blueBorder = false, SDL_Rect * destRect = nullptr); //0 - success, any other - error //print next - void incrementFrame(); - int getFrame() const; // Gets the current frame ID relative to DEF file. - int getAnimationFrame() const; // Gets the current frame ID relative to frame group. - bool onFirstFrameInGroup(); - bool onLastFrameInGroup(); + void nextFrame(SDL_Surface * dest, int x, int y, bool rotate, SDL_Rect * destRect = nullptr); + + // should be called every frame, return true when animation was reset to beginning + bool incrementFrame(float timePassed); + void setBorderColor(SDL_Color palette); + + float getCurrentFrame() const; // Gets the current frame ID relative to frame group. void playOnce(CCreatureAnim::EAnimType type); //plays once given stage of animation, then resets to 2 int framesInGroup(CCreatureAnim::EAnimType group) const; //retirns number of fromes in given group + + void pause(); + void play(); + + //helpers. TODO: move them somewhere else + bool isDead() const; + bool isIdle() const; }; diff --git a/client/gui/CIntObjectClasses.cpp b/client/gui/CIntObjectClasses.cpp index 7a4812345..2edf1d0c4 100644 --- a/client/gui/CIntObjectClasses.cpp +++ b/client/gui/CIntObjectClasses.cpp @@ -546,12 +546,14 @@ CHighlightableButtonsGroup::~CHighlightableButtonsGroup() void CHighlightableButtonsGroup::select(int id, bool mode) { - CHighlightableButton *bt = nullptr; + assert(!buttons.empty()); + + CHighlightableButton *bt = buttons.front(); if(mode) { - for(size_t i=0;iID == id) - bt = buttons[i]; + for(auto btn : buttons) + if (btn->ID == id) + bt = btn; } else { diff --git a/client/gui/SDL_Extensions.cpp b/client/gui/SDL_Extensions.cpp index 18266eb4e..b10f6dc60 100644 --- a/client/gui/SDL_Extensions.cpp +++ b/client/gui/SDL_Extensions.cpp @@ -62,16 +62,6 @@ void blitAt(SDL_Surface * src, const SDL_Rect & pos, SDL_Surface * dst) blitAt(src,pos.x,pos.y,dst); } -SDL_Color genRGB(int r, int g, int b, int a=0) -{ - SDL_Color ret; - ret.b=b; - ret.g=g; - ret.r=r; - ret.unused=a; - return ret; -} - void updateRect (SDL_Rect * rect, SDL_Surface * scr) { SDL_UpdateRect(scr,rect->x,rect->y,rect->w,rect->h); diff --git a/lib/BattleHex.cpp b/lib/BattleHex.cpp index 3bc60b73e..cd7692991 100644 --- a/lib/BattleHex.cpp +++ b/lib/BattleHex.cpp @@ -60,12 +60,14 @@ std::vector BattleHex::neighbouringTiles() const { std::vector ret; const int WN = GameConstants::BFIELD_WIDTH; - checkAndPush(hex - ( (hex/WN)%2 ? WN+1 : WN ), ret); - checkAndPush(hex - ( (hex/WN)%2 ? WN : WN-1 ), ret); - checkAndPush(hex - 1, ret); - checkAndPush(hex + 1, ret); - checkAndPush(hex + ( (hex/WN)%2 ? WN-1 : WN ), ret); - checkAndPush(hex + ( (hex/WN)%2 ? WN : WN+1 ), ret); + // H3 order : TR, R, BR, BL, L, TL (T = top, B = bottom ...) + + checkAndPush(hex - ( (hex/WN)%2 ? WN+1 : WN ), ret); // 1 + checkAndPush(hex + 1, ret); // 2 + checkAndPush(hex + ( (hex/WN)%2 ? WN : WN+1 ), ret); // 3 + checkAndPush(hex + ( (hex/WN)%2 ? WN-1 : WN ), ret); // 4 + checkAndPush(hex - 1, ret); // 5 + checkAndPush(hex - ( (hex/WN)%2 ? WN : WN-1 ), ret); // 6 return ret; } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index caa0510d3..c0a1d4038 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -579,9 +579,12 @@ CCreature * CCreatureHandler::loadFromJson(const JsonNode & node) cre->addBonus(node["speed"].Float(), Bonus::STACKS_SPEED); cre->addBonus(node["attack"].Float(), Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK); cre->addBonus(node["defense"].Float(), Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE); + cre->addBonus(node["damage"]["min"].Float(), Bonus::CREATURE_DAMAGE, 1); cre->addBonus(node["damage"]["max"].Float(), Bonus::CREATURE_DAMAGE, 2); + assert(node["damage"]["min"].Float() <= node["damage"]["max"].Float()); + cre->ammMin = node["advMapAmount"]["min"].Float(); cre->ammMax = node["advMapAmount"]["max"].Float(); assert(cre->ammMin <= cre->ammMax); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index bd246d20e..aff084276 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3468,8 +3468,14 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) handleAfterAttackCasting(bat); } - //ballista & artillery handling - if(destStack->alive() && stack->getCreature()->idNumber == CreatureID::BALLISTA) + //second shot for ballista, only if hero has advanced artillery + + const CGHeroInstance * attackingHero = gs->curB->heroes[ba.side]; + + if( destStack->alive() + && (stack->getCreature()->idNumber == CreatureID::BALLISTA) + && (attackingHero->getSecSkillLevel(SecondarySkill::ARTILLERY) >= SecSkillLevel::ADVANCED) + ) { BattleAttack bat2; bat2.flags |= BattleAttack::SHOT; diff --git a/test/CMapEditManagerTest.cpp b/test/CMapEditManagerTest.cpp index 78a4f3179..19e6aef5a 100644 --- a/test/CMapEditManagerTest.cpp +++ b/test/CMapEditManagerTest.cpp @@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) CRandomGenerator gen; const JsonNode viewNode(ResourceID("test/terrainViewMappings", EResType::TEXT)); const auto & mappingsNode = viewNode["mappings"].Vector(); - BOOST_FOREACH(const auto & node, mappingsNode) + for (const auto & node : mappingsNode) { // Get terrain group and id const auto & patternStr = node["pattern"].String(); @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) const auto & mapping = (*pattern).mapping; const auto & positionsNode = node["pos"].Vector(); - BOOST_FOREACH(const auto & posNode, positionsNode) + for (const auto & posNode : positionsNode) { const auto & posVector = posNode.Vector(); if(posVector.size() != 3) throw std::runtime_error("A position should consist of three values x,y,z. Continue with next position."); @@ -119,7 +119,7 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) editManager->drawTerrain(originalTile.terType, &gen); const auto & tile = map->getTile(pos); bool isInRange = false; - BOOST_FOREACH(const auto & range, mapping) + for(const auto & range : mapping) { if(tile.terView >= range.first && tile.terView <= range.second) {