Pārlūkot izejas kodu

Merge pull request #1515 from IvanSavenko/time_based_animations

Time based animations
Ivan Savenko 2 gadi atpakaļ
vecāks
revīzija
0ba74fea73

+ 28 - 22
client/CVideoHandler.cpp

@@ -21,6 +21,14 @@
 extern CGuiHandler GH; //global gui handler
 
 #ifndef DISABLE_VIDEO
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/imgutils.h>
+#include <libswscale/swscale.h>
+}
+
 //reads events and returns true on key down
 static bool keyDown()
 {
@@ -59,22 +67,20 @@ static si64 lodSeek(void * opaque, si64 pos, int whence)
 }
 
 CVideoPlayer::CVideoPlayer()
-{
-	stream = -1;
-	format = nullptr;
-	codecContext = nullptr;
-	codec = nullptr;
-	frame = nullptr;
-	sws = nullptr;
-	context = nullptr;
-	texture = nullptr;
-	dest = nullptr;
-	destRect = CSDL_Ext::genRect(0,0,0,0);
-	pos = CSDL_Ext::genRect(0,0,0,0);
-	refreshWait = 0;
-	refreshCount = 0;
-	doLoop = false;
-}
+	: stream(-1)
+	, format (nullptr)
+	, codecContext(nullptr)
+	, codec(nullptr)
+	, frame(nullptr)
+	, sws(nullptr)
+	, context(nullptr)
+	, texture(nullptr)
+	, dest(nullptr)
+	, destRect(0,0,0,0)
+	, pos(0,0,0,0)
+	, frameTime(0)
+	, doLoop(false)
+{}
 
 bool CVideoPlayer::open(std::string fname, bool scale)
 {
@@ -88,9 +94,8 @@ bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scal
 	close();
 
 	this->fname = fname;
-	refreshWait = 3;
-	refreshCount = -1;
 	doLoop = loop;
+	frameTime = 0;
 
 	ResourceID resource(std::string("Video/") + fname, EResType::VIDEO);
 
@@ -257,6 +262,7 @@ bool CVideoPlayer::nextFrame()
 			if (doLoop && !gotError)
 			{
 				// Rewind
+				frameTime = 0;
 				if (av_seek_frame(format, stream, 0, AVSEEK_FLAG_BYTE) < 0)
 					break;
 				gotError = true;
@@ -357,9 +363,11 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 	if (sws == nullptr)
 		return;
 
-	if (refreshCount <= 0)
+	double frameEndTime = (frame->pts + frame->pkt_duration) * av_q2d(format->streams[stream]->time_base);
+	frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.0;
+
+	if (frameTime >= frameEndTime )
 	{
-		refreshCount = refreshWait;
 		if (nextFrame())
 			show(x,y,dst,update);
 		else
@@ -377,8 +385,6 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 	{
 		redraw(x, y, dst, update);
 	}
-
-	refreshCount --;
 }
 
 void CVideoPlayer::close()

+ 7 - 36
client/CVideoHandler.h

@@ -56,39 +56,11 @@ public:
 
 #include "../lib/filesystem/CInputStream.h"
 
-extern "C" {
-#include <libavformat/avformat.h>
-#include <libavcodec/avcodec.h>
-#include <libavutil/imgutils.h>
-#include <libswscale/swscale.h>
-}
-
-//compatibility for libav 9.18 in ubuntu 14.04, 52.66.100 is ffmpeg 2.2.3
-#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 66, 100))
-inline AVFrame * av_frame_alloc()
-{
-	return avcodec_alloc_frame();
-}
-
-inline void av_frame_free(AVFrame ** frame)
-{
-	av_free(*frame);
-	*frame = nullptr;
-}
-#endif // VCMI_USE_OLD_AVUTIL
-
-//fix for travis-ci
-#if (LIBAVUTIL_VERSION_INT < AV_VERSION_INT(52, 0, 0))
-	#define AVPixelFormat PixelFormat
-	#define AV_PIX_FMT_NONE PIX_FMT_NONE
-	#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
-	#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
-	#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
-	#define AV_PIX_FMT_BGR32 PIX_FMT_BGR32
-	#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
-	#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
-	#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
-#endif
+struct AVFormatContext;
+struct AVCodecContext;
+struct AVCodec;
+struct AVFrame;
+struct AVIOContext;
 
 class CVideoPlayer : public IMainVideoPlayer
 {
@@ -108,8 +80,8 @@ class CVideoPlayer : public IMainVideoPlayer
 	Rect destRect;			// valid when dest is used
 	Rect pos;				// destination on screen
 
-	int refreshWait; // Wait several refresh before updating the image
-	int refreshCount;
+	/// video playback currnet progress, in seconds
+	double frameTime;
 	bool doLoop;				// loop through video
 
 	bool playVideo(int x, int y, bool stopOnKey);
@@ -141,4 +113,3 @@ public:
 };
 
 #endif
-

+ 4 - 5
client/battle/BattleAnimationClasses.cpp

@@ -364,7 +364,7 @@ bool MovementAnimation::init()
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
 	Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack);
 
-	timeToMove = AnimationControls::getMovementDuration(stack->getCreature());
+	progressPerSecond = AnimationControls::getMovementDistance(stack->getCreature());
 
 	begX = begPosition.x;
 	begY = begPosition.y;
@@ -375,8 +375,7 @@ bool MovementAnimation::init()
 	if (stack->hasBonus(Selector::type()(Bonus::FLYING)))
 	{
 		float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY));
-
-		timeToMove *= AnimationControls::getFlightDistance(stack->getCreature()) / distance;
+		progressPerSecond =  AnimationControls::getFlightDistance(stack->getCreature()) / distance;
 	}
 
 	return true;
@@ -384,7 +383,7 @@ bool MovementAnimation::init()
 
 void MovementAnimation::nextFrame()
 {
-	progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * timeToMove;
+	progress += float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000 * progressPerSecond;
 
 	//moving instructions
 	myAnim->pos.x = static_cast<Sint16>(begX + distanceX * progress );
@@ -432,7 +431,7 @@ MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stac
 	  curentMoveIndex(0),
 	  begX(0), begY(0),
 	  distanceX(0), distanceY(0),
-	  timeToMove(0.0),
+	  progressPerSecond(0.0),
 	  progress(0.0)
 {
 	logAnim->debug("Created MovementAnimation for %s", stack->getName());

+ 5 - 2
client/battle/BattleAnimationClasses.h

@@ -147,8 +147,11 @@ private:
 	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
+	/// progress gain per second
+	double progressPerSecond;
+
+	/// range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends
+	double progress;
 
 public:
 	bool init() override;

+ 4 - 4
client/battle/BattleInterface.cpp

@@ -520,16 +520,16 @@ void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinatio
 
 void BattleInterface::setAnimSpeed(int set)
 {
-	Settings speed = settings.write["battle"]["animationSpeed"];
-	speed->Float() = float(set) / 100;
+	Settings speed = settings.write["battle"]["speedFactor"];
+	speed->Float() = float(set);
 }
 
 int BattleInterface::getAnimSpeed() const
 {
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
-		return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float() *100));
+		return static_cast<int>(vstd::round(settings["session"]["spectate-battle-speed"].Float()));
 
-	return static_cast<int>(vstd::round(settings["battle"]["animationSpeed"].Float() *100));
+	return static_cast<int>(vstd::round(settings["battle"]["speedFactor"].Float()));
 }
 
 CPlayerInterface *BattleInterface::getCurrentPlayerInterface() const

+ 9 - 7
client/battle/BattleInterfaceClasses.cpp

@@ -221,8 +221,10 @@ void BattleHero::render(Canvas & canvas)
 	canvas.draw(flagFrame, flagPosition);
 	canvas.draw(heroFrame, heroPosition);
 
-	flagCurrentFrame += currentSpeed;
-	currentFrame += currentSpeed;
+	float timePassed = float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000.f;
+
+	flagCurrentFrame += currentSpeed * timePassed;
+	currentFrame += currentSpeed * timePassed;
 
 	if(flagCurrentFrame >= flagAnimation->size(0))
 		flagCurrentFrame -= flagAnimation->size(0);
@@ -241,8 +243,8 @@ void BattleHero::pause()
 
 void BattleHero::play()
 {
-	//FIXME: un-hardcode speed
-	currentSpeed = 0.25f;
+	//H3 speed: 10 fps ( 100 ms per frame)
+	currentSpeed = 10.f;
 }
 
 float BattleHero::getFrame() const
@@ -441,13 +443,13 @@ BattleOptionsWindow::BattleOptionsWindow(BattleInterface & owner):
 
 	std::shared_ptr<CToggleButton> toggle;
 	toggle = std::make_shared<CToggleButton>(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]);
-	animSpeeds->addToggle(40, toggle);
+	animSpeeds->addToggle(1, toggle);
 
 	toggle = std::make_shared<CToggleButton>(Point( 92, 225), "sysob10.def", CGI->generaltexth->zelp[423]);
-	animSpeeds->addToggle(63, toggle);
+	animSpeeds->addToggle(2, toggle);
 
 	toggle = std::make_shared<CToggleButton>(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]);
-	animSpeeds->addToggle(100, toggle);
+	animSpeeds->addToggle(3, toggle);
 
 	animSpeeds->setSelected(owner.getAnimSpeed());
 

+ 11 - 7
client/battle/BattleProjectileController.cpp

@@ -77,7 +77,11 @@ void ProjectileAnimatedMissile::show(Canvas & canvas)
 
 void ProjectileCatapult::show(Canvas & canvas)
 {
-	auto image = animation->getImage(frameNum, 0, true);
+	frameProgress += AnimationControls::getSpellEffectSpeed() * GH.mainFPSmng->getElapsedMilliseconds() / 1000;
+	int frameCounter = std::floor(frameProgress);
+	int frameIndex = (frameCounter + 1) % animation->size(0);
+
+	auto image = animation->getImage(frameIndex, 0, true);
 
 	if(image)
 	{
@@ -86,8 +90,6 @@ void ProjectileCatapult::show(Canvas & canvas)
 		Point pos(posX, posY);
 
 		canvas.draw(image, pos);
-
-		frameNum = (frameNum + 1) % animation->size(0);
 	}
 
 	float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
@@ -289,13 +291,13 @@ void BattleProjectileController::createCatapultProjectile(const CStack * shooter
 	auto catapultProjectile       = new ProjectileCatapult();
 
 	catapultProjectile->animation = getProjectileImage(shooter);
-	catapultProjectile->frameNum  = 0;
 	catapultProjectile->progress  = 0;
 	catapultProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
 	catapultProjectile->from      = from;
 	catapultProjectile->dest      = dest;
 	catapultProjectile->shooterID = shooter->ID;
 	catapultProjectile->playing   = false;
+	catapultProjectile->frameProgress = 0.f;
 
 	projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
 }
@@ -316,6 +318,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 		projectile.reset(rayProjectile);
 
 		rayProjectile->rayConfig = shooterInfo.animation.projectileRay;
+		rayProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getRayProjectileSpeed());
 	}
 	else if (stackUsesMissileProjectile(shooter))
 	{
@@ -323,11 +326,12 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 		projectile.reset(missileProjectile);
 
 		missileProjectile->animation = getProjectileImage(shooter);
-		missileProjectile->reverse  = !owner.stacksController->facingRight(shooter);
-		missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
+		missileProjectile->reverse   = !owner.stacksController->facingRight(shooter);
+		missileProjectile->frameNum  = computeProjectileFrameID(from, dest, shooter);
+		missileProjectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
 	}
 
-	projectile->speed     = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
+
 	projectile->from      = from;
 	projectile->dest      = dest;
 	projectile->shooterID = shooter->ID;

+ 1 - 1
client/battle/BattleProjectileController.h

@@ -61,7 +61,7 @@ struct ProjectileCatapult : ProjectileBase
 	void show(Canvas & canvas) override;
 
 	std::shared_ptr<CAnimation> animation;
-	int frameNum;  // frame to display from projectile animation
+	float frameProgress;
 };
 
 /// Projectile for mages/evil eye - render ray expanding from origin position to destination

+ 45 - 21
client/battle/CreatureAnimation.cpp

@@ -47,6 +47,15 @@ std::shared_ptr<CreatureAnimation> AnimationControls::getAnimation(const CCreatu
 	return std::make_shared<CreatureAnimation>(creature->animDefName, func);
 }
 
+float AnimationControls::getAnimationSpeedFactor()
+{
+	// according to testing, H3 ratios between slow/medium/fast might actually be 36/60/100 (x1.666)
+	// exact value is hard to tell due to large rounding errors
+	// however we will assume them to be 33/66/100 since these values are better for standard 60 fps displays:
+	// with these numbers, base frame display duration will be 100/66/33 ms - exactly 6/4/2 frames
+	return settings["battle"]["speedFactor"].Float();
+}
+
 float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType type)
 {
 	assert(creature->animation.walkAnimationTime != 0);
@@ -56,20 +65,23 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	// possible new fields for creature format:
 	//split "Attack time" into "Shoot Time" and "Cast Time"
 
-	// a lot of arbitrary multipliers, mostly to make animation speed closer to H3
-	const float baseSpeed = 0.1f;
-	const float speedMult = static_cast<float>(settings["battle"]["animationSpeed"].Float());
-	const float speed = baseSpeed / speedMult;
+	// base speed for all H3 animations on slow speed is 10 frames per second (or 100ms per frame)
+	const float baseSpeed = 10.f;
+	const float speed = baseSpeed * getAnimationSpeedFactor();
 
 	switch (type)
 	{
 	case ECreatureAnimType::MOVING:
-		return static_cast<float>(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type));
+		return speed / creature->animation.walkAnimationTime;
 
 	case ECreatureAnimType::MOUSEON:
 		return baseSpeed;
+
 	case ECreatureAnimType::HOLDING:
-		return static_cast<float>(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type));
+			if ( creature->animation.idleAnimationTime > 0.01)
+				return speed / creature->animation.idleAnimationTime;
+			else
+				return 0.f; // this animation is disabled for current creature
 
 	case ECreatureAnimType::SHOOT_UP:
 	case ECreatureAnimType::SHOOT_FRONT:
@@ -80,7 +92,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case ECreatureAnimType::CAST_DOWN:
 	case ECreatureAnimType::CAST_FRONT:
 	case ECreatureAnimType::CAST_UP:
-		return static_cast<float>(speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type));
+		return speed / creature->animation.attackAnimationTime;
 
 	// as strange as it looks like "attackAnimationTime" does not affects melee attacks
 	// necessary because length of these animations must be same for all creatures for synchronization
@@ -95,15 +107,15 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 	case ECreatureAnimType::GROUP_ATTACK_DOWN:
 	case ECreatureAnimType::GROUP_ATTACK_FRONT:
 	case ECreatureAnimType::GROUP_ATTACK_UP:
-		return speed * 3 / anim->framesInGroup(type);
+		return speed;
 
 	case ECreatureAnimType::TURN_L:
 	case ECreatureAnimType::TURN_R:
-		return speed / 3;
+		return speed;
 
 	case ECreatureAnimType::MOVE_START:
 	case ECreatureAnimType::MOVE_END:
-		return speed / 3;
+		return speed;
 
 	case ECreatureAnimType::DEAD:
 	case ECreatureAnimType::DEAD_RANGED:
@@ -116,37 +128,51 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
 
 float AnimationControls::getProjectileSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4000);
+	// H3 speed: 1250/2500/3750 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 1250);
+}
+
+float AnimationControls::getRayProjectileSpeed()
+{
+	// H3 speed: 4000/8000/12000 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 4000);
 }
 
 float AnimationControls::getCatapultSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 1000);
+	// H3 speed: 200/400/600 pixels per second
+	return static_cast<float>(getAnimationSpeedFactor() * 200);
 }
 
 float AnimationControls::getSpellEffectSpeed()
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
+	// H3 speed: 10/20/30 frames per second
+	return static_cast<float>(getAnimationSpeedFactor() * 10);
 }
 
-float AnimationControls::getMovementDuration(const CCreature * creature)
+float AnimationControls::getMovementDistance(const CCreature * creature)
 {
-	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime);
+	// H3 speed: 2/4/6 tiles per second
+	return static_cast<float>( 2.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime);
 }
 
 float AnimationControls::getFlightDistance(const CCreature * creature)
 {
-	return static_cast<float>(creature->animation.flightAnimationDistance * 200);
+	// Note: for whatever reason, H3 uses "Walk Animation Time" here, even though "Flight Animation Distance" also exists
+	// H3 speed: 250/500/750 pixels per second
+	return static_cast<float>( 250.0 * getAnimationSpeedFactor() / creature->animation.walkAnimationTime);
 }
 
 float AnimationControls::getFadeInDuration()
 {
-	return 1.0f / settings["battle"]["animationSpeed"].Float();
+	// H3 speed: 500/250/166 ms
+	return 0.5f / getAnimationSpeedFactor();
 }
 
 float AnimationControls::getObstaclesSpeed()
 {
-	return 10.0;// does not seems to be affected by animaiton speed settings
+	// H3 speed: 20 frames per second, irregardless of speed setting.
+	return 20.f;
 }
 
 ECreatureAnimType CreatureAnimation::getType() const
@@ -407,7 +433,5 @@ void CreatureAnimation::pause()
 void CreatureAnimation::play()
 {
 	//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
-    speed = 0;
-	if(speedController(this, type) != 0)
-		speed = 1 / speedController(this, type);
+	speed = speedController(this, type);
 }

+ 12 - 5
client/battle/CreatureAnimation.h

@@ -27,25 +27,32 @@ namespace AnimationControls
 	SDL_Color getGoldBorder();
 	SDL_Color getNoBorder();
 
+	/// returns animation speed factor according to game settings,
+	/// slow speed is considered to be "base speed" and will return 1.0
+	float getAnimationSpeedFactor();
+
 	/// creates animation object with preset speed control
 	std::shared_ptr<CreatureAnimation> getAnimation(const CCreature * creature);
 
 	/// returns animation speed of specific group, taking in mind game setting (in frames per second)
 	float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType groupID);
 
-	/// returns how far projectile should move per second
+	/// returns how far projectile should move per second, in pixels per second
 	float getProjectileSpeed();
 
-	/// returns speed of catapult projectile, in pixels per second (horizontal axis only)
+	/// returns how far projectile should move per second, in pixels per second
+	float getRayProjectileSpeed();
+
+	/// returns speed of catapult projectile, in pixels per second, on a straight line, without parabola correction
 	float getCatapultSpeed();
 
 	/// returns speed of any spell effects, including any special effects like morale (in frames per second)
 	float getSpellEffectSpeed();
 
-	/// returns duration of full movement animation, in seconds. Needed to move animation on screen
-	float getMovementDuration(const CCreature * creature);
+	/// returns speed of movement animation across the screen, in tiles per second
+	float getMovementDistance(const CCreature * creature);
 
-	/// Returns distance on which flying creatures should during one animation loop
+	/// returns speed of movement animation across the screen, in pixels per seconds
 	float getFlightDistance(const CCreature * creature);
 
 	/// Returns total time for full fade-in effect on newly summoned creatures, in seconds

+ 1 - 1
client/gui/CursorHandler.cpp

@@ -263,7 +263,7 @@ void CursorHandler::centerCursor()
 
 void CursorHandler::updateSpellcastCursor()
 {
-	static const float frameDisplayDuration = 0.1f;
+	static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
 
 	frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
 	size_t newFrame = frame;

+ 4 - 4
client/widgets/AdventureMapClasses.cpp

@@ -680,7 +680,7 @@ CInfoBar::VisibleDateInfo::VisibleDateInfo()
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	animation = std::make_shared<CShowableAnim>(1, 0, getNewDayName(), CShowableAnim::PLAY_ONCE);
+	animation = std::make_shared<CShowableAnim>(1, 0, getNewDayName(), CShowableAnim::PLAY_ONCE, 180);// H3 uses around 175-180 ms per frame
 
 	std::string labelText;
 	if(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK) == 1 && LOCPLINT->cb->getDate(Date::DAY) != 1) // monday of any week but first - show new week info
@@ -721,8 +721,8 @@ CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	background = std::make_shared<CPicture>("ADSTATNX");
 	banner = std::make_shared<CAnimImage>("CREST58", player.getNum(), 0, 20, 51);
-	sand = std::make_shared<CShowableAnim>(99, 51, "HOURSAND");
-	glass = std::make_shared<CShowableAnim>(99, 51, "HOURGLAS", CShowableAnim::PLAY_ONCE, 40);
+	sand = std::make_shared<CShowableAnim>(99, 51, "HOURSAND", 0, 100); // H3 uses around 100 ms per frame
+	glass = std::make_shared<CShowableAnim>(99, 51, "HOURGLAS", CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
 }
 
 CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
@@ -880,7 +880,7 @@ void CInfoBar::showDate()
 	playNewDaySound();
 	state = DATE;
 	visibleInfo = std::make_shared<VisibleDateInfo>();
-	setTimer(3000);
+	setTimer(3000); // confirmed to match H3
 	redraw();
 }
 

+ 11 - 9
client/widgets/Images.cpp

@@ -264,13 +264,13 @@ void CAnimImage::playerColored(PlayerColor currPlayer)
 			anim->getImage(0, group)->playerColored(player);
 }
 
-CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 Delay, size_t Group, uint8_t alpha):
+CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):
 	anim(std::make_shared<CAnimation>(name)),
 	group(Group),
 	frame(0),
 	first(0),
-	frameDelay(Delay),
-	value(0),
+	frameTimeTotal(frameTime),
+	frameTimePassed(0),
 	flags(Flags),
 	xOffset(0),
 	yOffset(0),
@@ -311,7 +311,7 @@ bool CShowableAnim::set(size_t Group, size_t from, size_t to)
 	group = Group;
 	frame = first = from;
 	last = max;
-	value = 0;
+	frameTimePassed = 0;
 	return true;
 }
 
@@ -328,13 +328,13 @@ bool CShowableAnim::set(size_t Group)
 		group = Group;
 		last = anim->size(Group);
 	}
-	frame = value = 0;
+	frame = 0;
+	frameTimePassed = 0;
 	return true;
 }
 
 void CShowableAnim::reset()
 {
-	value = 0;
 	frame = first;
 
 	if (callback)
@@ -358,9 +358,11 @@ void CShowableAnim::show(SDL_Surface * to)
 	if ((flags & PLAY_ONCE) && frame + 1 == last)
 		return;
 
-	if ( ++value == frameDelay )
+	frameTimePassed += GH.mainFPSmng->getElapsedMilliseconds();
+
+	if(frameTimePassed >= frameTimeTotal)
 	{
-		value = 0;
+		frameTimePassed -= frameTimeTotal;
 		if ( ++frame >= last)
 			reset();
 	}
@@ -395,7 +397,7 @@ void CShowableAnim::rotate(bool on, bool vertical)
 }
 
 CCreatureAnim::CCreatureAnim(int x, int y, std::string name, ui8 flags, ECreatureAnimType type):
-	CShowableAnim(x,y,name,flags,4,size_t(type))
+	CShowableAnim(x, y, name, flags, 100, size_t(type)) // H3 uses 100 ms per frame, irregardless of battle speed settings
 {
 	xOffset = 0;
 	yOffset = 0;

+ 6 - 4
client/widgets/Images.h

@@ -130,9 +130,11 @@ protected:
 
 	size_t first, last; //animation range
 
-	//TODO: replace with time delay(needed for battles)
-	ui32 frameDelay;//delay in frames of each image
-	ui32 value;//how many times current frame was showed
+	/// total time on scren for each frame in animation
+	ui32 frameTimeTotal;
+
+	/// how long was current frame visible on screen
+	ui32 frameTimePassed;
 
 	ui8 flags;//Flags from EFlags enum
 
@@ -151,7 +153,7 @@ public:
 	//Set per-surface alpha, 0 = transparent, 255 = opaque
 	void setAlpha(ui32 alphaValue);
 
-	CShowableAnim(int x, int y, std::string name, ui8 flags=0, ui32 Delay=4, size_t Group=0, uint8_t alpha = UINT8_MAX);
+	CShowableAnim(int x, int y, std::string name, ui8 flags, ui32 frameTime, size_t Group=0, uint8_t alpha = UINT8_MAX);
 	~CShowableAnim();
 
 	//set animation to group or part of group

+ 1 - 1
client/windows/CCastleInterface.cpp

@@ -47,7 +47,7 @@
 #include <SDL_events.h>
 
 CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town, const CStructure * Str)
-	: CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE),
+	: CShowableAnim(0, 0, Str->defName, CShowableAnim::BASE, BUILDING_FRAME_TIME),
 	  parent(Par),
 	  town(Town),
 	  str(Str),

+ 5 - 3
client/windows/CCastleInterface.h

@@ -45,9 +45,11 @@ public:
 	enum EBuildingCreationAnimationPhases : uint32_t
 	{
 		BUILDING_APPEAR_TIMEPOINT = 500, //500 msec building appears: 0->100% transparency
-		BUILDING_WHITE_BORDER_TIMEPOINT = 1000, //500 msec border glows from white to yellow
-		BUILDING_YELLOW_BORDER_TIMEPOINT = 1500, //500 msec border glows from yellow to normal
-		BUILD_ANIMATION_FINISHED_TIMEPOINT = 2500 //1000 msec delay, nothing happens
+		BUILDING_WHITE_BORDER_TIMEPOINT = 900, //400 msec border glows from white to yellow
+		BUILDING_YELLOW_BORDER_TIMEPOINT = 1100, //200 msec border glows from yellow to normal (dark orange)
+		BUILD_ANIMATION_FINISHED_TIMEPOINT = 2100, // 1000msec once border is back to yellow nothing happens (this stage is basically removed by HD Mod)
+
+		BUILDING_FRAME_TIME = 150 // confirmed H3 timing: 150 ms for each building animation frame
 	};
 
 	/// returns building associated with this structure

+ 4 - 4
config/battleEffects.json

@@ -85,28 +85,28 @@
 				"time" : 0.0
 			},
 			{
-				"time"  : 0.2, 
+				"time"  : 0.25, 
 				"red"   : [ 0.5, 0.0, 0.5, 0.4 ],
 				"green" : [ 0.0, 1.0, 0.0, 0.0 ],
 				"blue"  : [ 0.0, 0.0, 1.0, 0.0 ],
 				"alpha" : 1.0
 			},
 			{
-				"time"  : 0.4, 
+				"time"  : 0.5, 
 				"red"   : [ 0.6, 0.6, 0.6, 0.0 ],
 				"green" : [ 0.0, 0.5, 0.0, 0.0 ],
 				"blue"  : [ 0.0, 0.0, 0.5, 0.0 ],
 				"alpha" : 1.0
 			},
 			{
-				"time"  : 0.6, 
+				"time"  : 0.75, 
 				"red"   : [ 0.5, 0.0, 0.5, 0.4 ],
 				"green" : [ 0.0, 1.0, 0.0, 0.0 ],
 				"blue"  : [ 0.0, 0.0, 1.0, 0.0 ],
 				"alpha" : 1.0
 			},
 			{
-				"time" : 0.8,
+				"time" : 1.0,
 			},
 		],
 	}

+ 1 - 5
config/schemas/creature.json

@@ -172,7 +172,7 @@
 				"animationTime": {
 					"type":"object",
 					"additionalProperties" : false,
-					"required" : [ "attack", "flight", "walk", "idle" ],
+					"required" : [ "attack", "walk", "idle" ],
 					"description": "Length of several animations",
 					"properties":{
 						"attack": {
@@ -183,10 +183,6 @@
 							"type":"number",
 							"description": "idle"
 						},
-						"flight": {
-							"type":"number",
-							"description": "flight"
-						},
 						"walk": {
 							"type":"number",
 							"description": "walk"

+ 3 - 3
config/schemas/settings.json

@@ -252,11 +252,11 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "animationSpeed", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ],
+			"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize" ],
 			"properties" : {
-				"animationSpeed" : {
+				"speedFactor" : {
 					"type" : "number",
-					"default" : 0.63
+					"default" : 2
 				},
 				"mouseShadow" : {
 					"type":"boolean",

+ 1 - 2
lib/CCreatureHandler.cpp

@@ -812,7 +812,7 @@ void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser
 	JsonNode & animationTime = graphics["animationTime"];
 	animationTime["walk"].Float() = parser.readNumber();
 	animationTime["attack"].Float() = parser.readNumber();
-	animationTime["flight"].Float() = parser.readNumber();
+	parser.readNumber(); // unused value "Flight animation time" - H3 actually uses "Walk animation time" even for flying creatures
 	animationTime["idle"].Float() = 10.0;
 
 	JsonNode & missile = graphics["missile"];
@@ -851,7 +851,6 @@ void CCreatureHandler::loadJsonAnimation(CCreature * cre, const JsonNode & graph
 	cre->animation.walkAnimationTime = animationTime["walk"].Float();
 	cre->animation.idleAnimationTime = animationTime["idle"].Float();
 	cre->animation.attackAnimationTime = animationTime["attack"].Float();
-	cre->animation.flightAnimationDistance = animationTime["flight"].Float(); //?
 
 	const JsonNode & missile = graphics["missile"];
 	const JsonNode & offsets = missile["offset"];

+ 8 - 2
lib/CCreatureHandler.h

@@ -74,7 +74,7 @@ public:
 		};
 
 		double timeBetweenFidgets, idleAnimationTime,
-			   walkAnimationTime, attackAnimationTime, flightAnimationDistance;
+			   walkAnimationTime, attackAnimationTime;
 		int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX,
 		    upperRightMissleOffsetY, rightMissleOffsetY, lowerRightMissleOffsetY;
 
@@ -91,7 +91,13 @@ public:
 			h & idleAnimationTime;
 			h & walkAnimationTime;
 			h & attackAnimationTime;
-			h & flightAnimationDistance;
+
+			if (version < 814)
+			{
+				float unused = 0.f;
+				h & unused;
+			}
+
 			h & upperRightMissleOffsetX;
 			h & rightMissleOffsetX;
 			h & lowerRightMissleOffsetX;

+ 1 - 1
lib/serializer/CSerializer.h

@@ -14,7 +14,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-const ui32 SERIALIZATION_VERSION = 813;
+const ui32 SERIALIZATION_VERSION = 814;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 813;
 const std::string SAVEGAME_MAGIC = "VCMISVG";