1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

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.
This commit is contained in:
Ivan Savenko 2013-07-06 16:10:20 +00:00
parent 68603245c4
commit 146a5e5ef8
21 changed files with 845 additions and 827 deletions

View File

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

View File

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

View File

@ -106,8 +106,8 @@ CCreatureWindow::CCreatureWindow(const CStackInstance &st, CreWinType Type, std:
fs += Upg;
fs += boost::bind(&CCreatureWindow::close,this);
CFunctionList<void()> 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

View File

@ -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<SDefEntry *>(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;
}

View File

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

View File

@ -638,7 +638,7 @@ void CPlayerInterface::battleStacksHealedRes(const std::vector<std::pair<ui32, u
for(auto & healedStack : healedStacks)
{
const CStack * healed = cb->battleGetStackByID(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);

View File

@ -27,11 +27,8 @@ public:
}
CFunctionList(const boost::function<Signature> &first)
{
funcs.push_back(first);
}
CFunctionList(boost::function<Signature> &first)
{
funcs.push_back(first);
if (first)
funcs.push_back(first);
}
CFunctionList(std::nullptr_t)
{}

View File

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

View File

@ -2,22 +2,26 @@
#include "CBattleAnimations.h"
#include <boost/math/constants/constants.hpp>
#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<std::pair<CBattleAnimation *, bool> >::const_iterator it = owner->pendingAnims.begin(); it != owner->pendingAnims.end(); ++it)
{
CBattleMoveStart * anim = dynamic_cast<CBattleMoveStart *>(it->first);
CReverseAnim * anim2 = dynamic_cast<CReverseAnim *>(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<int>(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<double>(steps);
stepY = (endPosition.y - begPosition.y) / static_cast<double>(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<double>(steps)));
break;
case 1:
stepX = hexWbase / (2.0 * steps);
stepY = -1.0 * hexHbase / (static_cast<double>(steps));
break;
case 2:
stepX = hexWbase / static_cast<double>(steps);
stepY = 0.0;
break;
case 3:
stepX = hexWbase / (2.0 * steps);
stepY = hexHbase / static_cast<double>(steps);
break;
case 4:
stepX = -1.0 * hexWbase / (2.0 * steps);
stepY = hexHbase / static_cast<double>(steps);
break;
case 5:
stepX = -1.0 * hexWbase / static_cast<double>(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<Sint16>(posX);
posY += stepY;
myAnim()->pos.y = static_cast<Sint16>(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<Sint16>(begX + distanceX * progress );
myAnim->pos.y = static_cast<Sint16>(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<BattleHex> _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<int>(sqrt(static_cast<double>((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<std::pair<CBattleAnimation *, bool> >::const_iterator it = owner->pendingAnims.begin(); it != owner->pendingAnims.end(); ++it)
for(auto & it : owner->pendingAnims)
{
CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it->first);
CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it->first);
CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first);
CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(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;
}

View File

@ -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<BattleHex> 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<BattleHex> 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<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
};

View File

@ -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<bool> 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<CPlayerInterface> att, shared_ptr<CPlayerInterface> 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<const CStack *> 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; v<elem.size(); ++v)
{
creAnims[elem[v]->ID]->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<const CStack *> flyingStacks; //flying stacks should be displayed later, over other stacks and obstacles
@ -964,7 +977,7 @@ void CBattleInterface::showAliveStacks(std::vector<const CStack *> *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<ProjectileInfo>::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<const CStack *> 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:

View File

@ -125,20 +125,16 @@ private:
std::map< int, bool > creDir; // <creatureID, if false reverse creature's animation> //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<BattleHex> 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<int, int> standingFrame; //number of frame in standing animation by stack ID, helps in showing 'random moves'
shared_ptr<CPlayerInterface> 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<CClickableHex*> 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

View File

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

View File

@ -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<totalBlocks; z++)
for (int i=0; i<totalBlocks; i++)
{
std::vector<int> 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<totalEntries+totalInBlock; j++)
{
SEntries.push_back(SEntry());
SEntries[j].group = group;
frameIDs.push_back(j);
}
/*int unknown2 = read_le_u32(FDef + i);*/ i+=4; //TODO use me
/*int unknown3 = read_le_u32(FDef + i);*/ i+=4; //TODO use me
i+=13*totalInBlock; //omitting names
for (j=0; j<totalInBlock; j++)
{
SEntries[totalEntries+j].offset = read_le_u32(FDef + i); i+=4;
}
//totalEntries+=totalInBlock;
for(int hh=0; hh<totalInBlock; ++hh)
{
++totalEntries;
}
frameGroups[group] = frameIDs;
int groupID = reader.readInt32();
int totalInBlock = reader.readInt32();
reader.skip(4 + 4 + 13 * totalInBlock); // some unused data
for (int j=0; j<totalInBlock; j++)
dataOffsets[groupID].push_back(reader.readUInt32());
}
//init vars
curFrame = 0;
type = CCreatureAnim::WHOLE_ANIM;
frames = totalEntries;
// if necessary, add one frame into vcmi-only group DEAD
if (dataOffsets.count(CCreatureAnim::DEAD) == 0)
dataOffsets[CCreatureAnim::DEAD].push_back(dataOffsets[CCreatureAnim::DEATH].back());
play();
}
int CCreatureAnimation::nextFrameMiddle(SDL_Surface *dest, int x, int y, bool attacker, ui8 animCount, bool incrementFrame, bool yellowBorder, bool blueBorder, SDL_Rect * destRect)
void CCreatureAnimation::endAnimation()
{
return nextFrame(dest, x-fullWidth/2, y-fullHeight/2, attacker, animCount, incrementFrame, yellowBorder, blueBorder, destRect);
once = false;
auto copy = onAnimationReset;
onAnimationReset.clear();
copy();
}
void CCreatureAnimation::incrementFrame()
bool CCreatureAnimation::incrementFrame(float timePassed)
{
if(type!=-1) //when a specific part of animation is played
elapsedTime += timePassed;
currentFrame += timePassed * speed;
if (currentFrame >= 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 bpp>
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<bpp>(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<SDL_Color, 8> CCreatureAnimation::genSpecialPalette()
{
std::array<SDL_Color, 8> 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<int bpp>
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<spriteHeight; i++)
{
//NOTE: if this loop will be optimized to skip empty lines - recheck this read access
ui8 * lineData = pixelData.get() + baseOffset + reader.readUInt32();
size_t destX = x;
if (rotate)
destX += rightMargin + spriteWidth - 1;
else
destX += leftMargin;
size_t destY = y + topMargin + i;
size_t currentOffset = 0;
size_t totalRowLength = 0;
while (totalRowLength < spriteWidth)
{
ui8 type = lineData[currentOffset++];
ui32 length = lineData[currentOffset++] + 1;
if (type==0xFF)//Raw data
{
for (size_t j=0; j<length; j++)
putPixelAt<bpp>(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<length; j++)
putPixelAt<bpp>(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<int bpp>
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<SDL_Color, 8> & special, SDL_Rect * destRect) const
{
if (destRect == nullptr)
putPixel<bpp>(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<bpp, 0>::PutColor(p, color.R, color.G, color.B);
}
else if((yellowBorder || blueBorder) && (palc == 6 || palc == 7)) //selection highlight
{
if(blueBorder)
ColorPutter<bpp, 0>::PutColor(p, 0, 0x0f + animCount, 0x0f + animCount);
else
ColorPutter<bpp, 0>::PutColor(p, 0x0f + animCount, 0x0f + animCount, 0);
}
else if (palc == 5) //selection highlight or transparent
{
if(blueBorder)
ColorPutter<bpp, 0>::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<bpp, 0>::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<bpp, 0>::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<bpp>(getPixelAddr(dest, X, Y), palette[index], index, special);
}
}
template<int bpp>
inline void CCreatureAnimation::putPixel(ui8 * dest, const SDL_Color & color, size_t index, const std::array<SDL_Color, 8> & special) const
{
if (index < 8)
{
const SDL_Color & pal = special[index];
ColorPutter<bpp, 0>::PutColor(dest, pal.r, pal.g, pal.b, pal.unused);
}
else
{
ColorPutter<bpp, 0>::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);
}

View File

@ -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<SEntry> SEntries ;
std::string defName, curDir;
template<int bpp>
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 bpp>
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<int, std::vector<int> > 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<float(CCreatureAnimation *, size_t)> TSpeedController;
private:
std::string defName;
int fullWidth, fullHeight;
// palette, as read from def file
std::array<SDL_Color, 256> 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<int, std::vector<unsigned int>> dataOffsets;
//animation raw data
//TODO: use vector instead?
unique_ptr<ui8[]> 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<int bpp>
void putPixelAt(SDL_Surface * dest, int X, int Y, size_t index, const std::array<SDL_Color, 8> & special, SDL_Rect * destRect = nullptr) const;
template<int bpp>
void putPixel( ui8 * dest, const SDL_Color & color, size_t index, const std::array<SDL_Color, 8> & special) const;
template<int bpp>
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<SDL_Color, 8> 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<void()> 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;
};

View File

@ -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;i<buttons.size() && !bt; ++i)
if (buttons[i]->ID == id)
bt = buttons[i];
for(auto btn : buttons)
if (btn->ID == id)
bt = btn;
}
else
{

View File

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

View File

@ -60,12 +60,14 @@ std::vector<BattleHex> BattleHex::neighbouringTiles() const
{
std::vector<BattleHex> 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;
}

View File

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

View File

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

View File

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