Ver Fonte

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.
Ivan Savenko há 12 anos atrás
pai
commit
146a5e5ef8

+ 8 - 12
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:

+ 1 - 9
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
 {

+ 2 - 2
client/CCreatureWindow.cpp

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

+ 10 - 10
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<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;
 	}
 

+ 2 - 2
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();

+ 1 - 1
client/CPlayerInterface.cpp

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

+ 2 - 5
client/FunctionList.h

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

+ 2 - 0
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"

+ 172 - 258
client/battle/CBattleAnimations.cpp

@@ -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 "../../lib/CTownHandler.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()
+void CMeleeAttackAnimation::endAnim()
 {
-	/*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();
+	CAttackAnimation::endAnim();
+	delete this;
 }
 
-void CMeleeAttackAnimation::endAnim()
+bool CMovementAnimation::shouldRotate()
 {
-	CBattleAnimation::endAnim();
+	Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack->attackerOwned, stack, owner);
+	Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack->attackerOwned, stack, owner);
 
-	delete this;
+	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;
+		// 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.
+		{
+			owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true));
+			return false;
+		}
+		else
+		{
+			CReverseAnimation::rotateStack(owner, stack, oldPos);
+		}
 	}
 
-	if(myAnim()->getType() != CCreatureAnim::MOVING)
+	if(myAnim->getType() != CCreatureAnim::MOVING)
 	{
-		myAnim()->setType(CCreatureAnim::MOVING);
+		myAnim->setType(CCreatureAnim::MOVING);
 	}
-	//unit reversed
 
-	if (owner->moveSh == -1)
+	if (owner->moveSoundHander == -1)
 	{
-		owner->moveSh = CCS->soundh->playSound(battle_sound(movedStack->getCreature(), move), -1);
+		owner->moveSoundHander = CCS->soundh->playSound(battle_sound(stack->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
+	Point begPosition = CClickableHex::getXYUnitAnim(oldPos, stack->attackerOwned, stack, owner);
+	Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack->attackerOwned, stack, owner);
 
-		stepX = (endPosition.x - begPosition.x) / static_cast<double>(steps);
-		stepY = (endPosition.y - begPosition.y) / static_cast<double>(steps);
-	}
-	else
+	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)))
 	{
-		switch(mutPos)
-		{
-		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;
-		}
+		float distance = sqrt(distanceX * distanceX + distanceY * distanceY);
+
+		timeToMove *= distance / AnimationControls::getFlightDistance(stack->getCreature());
 	}
-	//step shifts calculated
 
 	return true;
 }
 
 void CMovementAnimation::nextFrame()
 {
+	progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove;
+
 	//moving instructions
-	posX += stepX;
-	myAnim()->pos.x = static_cast<Sint16>(posX);
-	posY += stepY;
-	myAnim()->pos.y = static_cast<Sint16>(posY);
-
-	// Increments step count and check if we are finished with current animation
-	++whichStep;
-	if(whichStep == steps)
+	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);
 
-	return true;
-}
+	myAnim->onAnimationReset += std::bind(&CMovementEndAnimation::endAnim, this);
 
-void CMovementEndAnimation::nextFrame()
-{
-	if(myAnim()->onLastFrameInGroup())
-	{
-		endAnim();
-	}
+	return true;
 }
 
 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,93 +526,79 @@ 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);
-	else
-		setupSecondPart();
-
-
-	return true;
-}
-
-void CReverseAnimation::nextFrame()
-{
-	if(partOfAnim == 1) //first part of animation
+	if(myAnim->framesInGroup(CCreatureAnim::TURN_L))
 	{
-		if(myAnim()->onLastFrameInGroup())
-		{
-			partOfAnim = 2;
-		}
+		myAnim->setType(CCreatureAnim::TURN_L);
+		myAnim->onAnimationReset += std::bind(&CReverseAnimation::setupSecondPart, this);
 	}
-	else if(partOfAnim == 2)
+	else
 	{
-		if(!secondPartSetup)
-		{
-			setupSecondPart();
-		}
-		if(myAnim()->onLastFrameInGroup())
-		{
-			endAnim();
-		}
+		setupSecondPart();
 	}
+	return true;
 }
 
 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::setupSecondPart()
+void CReverseAnimation::rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex)
 {
-	if(!stack)
-	{
-		endAnim();
-		return;
-	}
-
 	owner->creDir[stack->ID] = !owner->creDir[stack->ID];
 
-	myAnim()->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner);
+	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])
-				myAnim()->pos.x -= 44;
+				owner->creAnims[stack->ID]->pos.x -= 44;
 		}
 		else
 		{
 			if(owner->creDir[stack->ID])
-				myAnim()->pos.x += 44;
+				owner->creAnims[stack->ID]->pos.x += 44;
 		}
 	}
+}
+
+void CReverseAnimation::setupSecondPart()
+{
+	if(!stack)
+	{
+		endAnim();
+		return;
+	}
 
-	secondPartSetup = true;
+	rotateStack(owner, stack, hex);
 
-	if(myAnim()->framesInGroup(CCreatureAnim::TURN_R))
-		myAnim()->setType(CCreatureAnim::TURN_R);
+	if(myAnim->framesInGroup(CCreatureAnim::TURN_R))
+	{
+		myAnim->setType(CCreatureAnim::TURN_R);
+		myAnim->onAnimationReset += std::bind(&CReverseAnimation::endAnim, this);
+	}
 	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;
 }
 

+ 25 - 22
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<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
 };

+ 103 - 135
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<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:

+ 7 - 8
client/battle/CBattleInterface.h

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

+ 3 - 3
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);
 

+ 330 - 244
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,135 +122,116 @@ 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));
+
+		pixelData = std::move(data.first);
+		pixelDataSize = data.second;
+	}
+
+	CBinaryReader reader(new CMemoryStream(pixelData.get(), pixelDataSize));
 
-	//init anim data
-	int i,j, totalInBlock;
+	reader.readInt32(); // def type, unused
 
-	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;
+	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
-	{
-		++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
+	elapsedTime += timePassed;
+	currentFrame += timePassed * speed;
+	if (currentFrame >= float(framesInGroup(type)))
 	{
-		++curFrame;
-		if(curFrame>=frames)
-			curFrame = 0;
+		// just in case of extremely low fps
+		while (currentFrame >= float(framesInGroup(type)))
+			currentFrame -= framesInGroup(type);
+
+		if (once)
+			setType(CCreatureAnim::HOLDING);
+
+		endAnimation();
+		return true;
 	}
+	return false;
 }
 
-int CCreatureAnimation::getFrame() const
+void CCreatureAnimation::setBorderColor(SDL_Color palette)
 {
-	return curFrame;
+	border = palette;
 }
 
-int CCreatureAnimation::getAnimationFrame() const
+int CCreatureAnimation::getWidth() const
 {
-	return internalFrame;
+	return fullWidth;
 }
 
-bool CCreatureAnimation::onFirstFrameInGroup()
+int CCreatureAnimation::getHeight() const
 {
-	return internalFrame == 0;
+	return fullHeight;
 }
 
-bool CCreatureAnimation::onLastFrameInGroup()
+float CCreatureAnimation::getCurrentFrame() const
 {
-	if(internalFrame == frameGroups[type].size() - 1)
-		return true;
-	return false;
+	return currentFrame;
 }
 
 void CCreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
@@ -161,188 +240,195 @@ void CCreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
 	once = true;
 }
 
+inline int getBorderStrength(float time)
+{
+	float borderStrength = fabs(round(time) - time) * 2; // generate value in range 0-1
+
+	return borderStrength * 155 + 100; // scale to 0-255
+}
+
+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>
-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*/)
-{
-	//increasing frame number
-	int SIndex = curFrame;
-	if (IncrementFrame)
-		incrementFrame();
-
-#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;
+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));
 
-		if (TopMargin > 0)
-		{
-			ftcp += FullWidth * TopMargin;
-		}
-		ui32 *RLEntries = (ui32 *)(FDef + BaseOffsetor);
+	ui32 offset = dataOffsets.at(type).at(floor(currentFrame));
 
-		for (int i = 0; i < SpriteHeight; i++)
-		{
-			int BaseOffset = BaseOffsetor + read_le_u32(RLEntries + i);
-			int TotalRowLength; // length of read segment
+	CBinaryReader reader(new CMemoryStream(pixelData.get(), pixelDataSize));
 
-			if (LeftMargin > 0)
-			{
-				ftcp += LeftMargin;
-			}
-			
-			TotalRowLength = 0;
+	reader.getStream()->seek(offset);
 
-			// 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.
+	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();
 
-			int yB = ftcp / FullWidth + y;
+	const int rightMargin = fullWidth - spriteWidth - leftMargin;
+	//const int bottomMargin = fullHeight - spriteHeight - topMargin;
 
-			do
-			{
-				SegmentType = FDef[BaseOffset++];
-				SegmentLength = FDef[BaseOffset++];
+	const size_t baseOffset = reader.getStream()->tell();
 
-				const int remainder = ftcp % FullWidth;
-				int xB = (attacker ? remainder : FullWidth - remainder - 1) + x;
+	assert(defType2 == 1);
 
-				const ui8 aCountMod = (animCount & 0x20) ? ((animCount & 0x1e) >> 1) << 4 : (0x0f - ((animCount & 0x1e) >> 1)) << 4;
+	auto specialPalette = genSpecialPalette();
 
-				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;
-				}
+	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;
 
-				TotalRowLength+=SegmentLength+1;
-			} while(TotalRowLength < SpriteWidth);
-			if (RightMargin > 0)
+		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
 			{
-				ftcp += RightMargin;
+				for (size_t j=0; j<length; j++)
+					putPixelAt<bpp>(dest, destX + (rotate?(-j):(j)), destY, lineData[currentOffset + j], specialPalette, destRect);
+
+				currentOffset += length;
 			}
-		}
-		if (BottomMargin > 0)
-		{
-			ftcp += BottomMargin * FullWidth;
+			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;
 		}
 	}
-
-	return 0;
 }
 
-int CCreatureAnimation::nextFrame(SDL_Surface *dest, int x, int y, bool attacker, ui8 animCount, bool IncrementFrame, bool yellowBorder, bool blueBorder, SDL_Rect * destRect)
+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 ( 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);
+	}
+}
 
-			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;
-			}
-		}
+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);
+}

+ 103 - 45
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
 {
+public:
+	typedef boost::function<float(CCreatureAnimation *, size_t)> TSpeedController;
+
 private:
-	int totalEntries, DEFType, totalBlocks;
-	BMPPalette palette[256];
-	struct SEntry
-	{
-		int offset;
-		int group;
-	} ;
-	std::vector<SEntry> SEntries ;
-	std::string defName, curDir;
+	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 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])
-	
+	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>
-	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;
+	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:
-	int fullWidth, fullHeight; //read-only, please!
-	CCreatureAnimation(std::string name); //c-tor
-	~CCreatureAnimation(); //d-tor
+
+	// 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;
 };

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

+ 0 - 10
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);

+ 8 - 6
lib/BattleHex.cpp

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

+ 3 - 0
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);

+ 8 - 2
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;

+ 3 - 3
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)
 					{