From d34efaeb8145d8360fd145ab622594b54ae57995 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 27 Jan 2023 23:16:02 +0200 Subject: [PATCH] All battle effects now use time-based timings --- client/battle/BattleAnimationClasses.cpp | 9 ++-- client/battle/BattleAnimationClasses.h | 7 ++- client/battle/BattleInterface.cpp | 2 +- client/battle/BattleInterfaceClasses.cpp | 6 +-- client/battle/BattleProjectileController.cpp | 18 ++++--- client/battle/BattleProjectileController.h | 2 +- client/battle/CreatureAnimation.cpp | 54 +++++++++++++------- client/battle/CreatureAnimation.h | 13 +++-- config/battleEffects.json | 8 +-- lib/CCreatureHandler.cpp | 2 +- lib/CCreatureHandler.h | 4 +- 11 files changed, 75 insertions(+), 50 deletions(-) diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 111a4e3a6..fac752acc 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/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(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(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()); diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index f89d4435c..89309ffe9 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/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; diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index 14ade3839..e87745884 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -521,7 +521,7 @@ void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinatio void BattleInterface::setAnimSpeed(int set) { Settings speed = settings.write["battle"]["animationSpeed"]; - speed->Float() = float(set) / 100; + speed->Float() = float(set); } int BattleInterface::getAnimSpeed() const diff --git a/client/battle/BattleInterfaceClasses.cpp b/client/battle/BattleInterfaceClasses.cpp index 5c5031ef7..280385372 100644 --- a/client/battle/BattleInterfaceClasses.cpp +++ b/client/battle/BattleInterfaceClasses.cpp @@ -438,13 +438,13 @@ BattleOptionsWindow::BattleOptionsWindow(BattleInterface & owner): std::shared_ptr toggle; toggle = std::make_shared(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]); - animSpeeds->addToggle(40, toggle); + animSpeeds->addToggle(1, toggle); toggle = std::make_shared(Point( 92, 225), "sysob10.def", CGI->generaltexth->zelp[423]); - animSpeeds->addToggle(63, toggle); + animSpeeds->addToggle(2, toggle); toggle = std::make_shared(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]); - animSpeeds->addToggle(100, toggle); + animSpeeds->addToggle(3, toggle); animSpeeds->setSelected(owner.getAnimSpeed()); diff --git a/client/battle/BattleProjectileController.cpp b/client/battle/BattleProjectileController.cpp index 429d11fc1..b9260d37e 100644 --- a/client/battle/BattleProjectileController.cpp +++ b/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; @@ -294,13 +296,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(catapultProjectile)); } @@ -321,6 +323,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)) { @@ -328,11 +331,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; diff --git a/client/battle/BattleProjectileController.h b/client/battle/BattleProjectileController.h index 102a99f09..477072eec 100644 --- a/client/battle/BattleProjectileController.h +++ b/client/battle/BattleProjectileController.h @@ -61,7 +61,7 @@ struct ProjectileCatapult : ProjectileBase void show(Canvas & canvas) override; std::shared_ptr animation; - int frameNum; // frame to display from projectile animation + float frameProgress; }; /// Projectile for mages/evil eye - render ray expanding from origin position to destination diff --git a/client/battle/CreatureAnimation.cpp b/client/battle/CreatureAnimation.cpp index 0dda6089a..436cc162d 100644 --- a/client/battle/CreatureAnimation.cpp +++ b/client/battle/CreatureAnimation.cpp @@ -56,20 +56,24 @@ 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; + // base speed for all H3 animations is 10/20/30 frames per second (100/50/33 ms per frame) + const float baseSpeed = 10.f; const float speedMult = static_cast(settings["battle"]["animationSpeed"].Float()); - const float speed = baseSpeed / speedMult; + const float speed = baseSpeed * speedMult; switch (type) { case ECreatureAnimType::MOVING: - return static_cast(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type)); + return speed / creature->animation.walkAnimationTime; case ECreatureAnimType::MOUSEON: return baseSpeed; + case ECreatureAnimType::HOLDING: - return static_cast(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type)); + if ( creature->animation.idleAnimationTime > 0.01) + return speed / creature->animation.idleAnimationTime; + else + return 0.f; case ECreatureAnimType::SHOOT_UP: case ECreatureAnimType::SHOOT_FRONT: @@ -80,7 +84,7 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c case ECreatureAnimType::CAST_DOWN: case ECreatureAnimType::CAST_FRONT: case ECreatureAnimType::CAST_UP: - return static_cast(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 +99,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 +120,51 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c float AnimationControls::getProjectileSpeed() { + // H3 speed: 1250/2500/3750 pixels per second + return static_cast(settings["battle"]["animationSpeed"].Float() * 1250); +} + +float AnimationControls::getRayProjectileSpeed() +{ + // H3 speed: 4000/8000/12000 pixels per second return static_cast(settings["battle"]["animationSpeed"].Float() * 4000); } float AnimationControls::getCatapultSpeed() { - return static_cast(settings["battle"]["animationSpeed"].Float() * 1000); + // H3 speed: 200/400/600 pixels per second + return static_cast(settings["battle"]["animationSpeed"].Float() * 200); } float AnimationControls::getSpellEffectSpeed() { - return static_cast(settings["battle"]["animationSpeed"].Float() * 30); + // H3 speed: 10/20/30 frames per second + return static_cast(settings["battle"]["animationSpeed"].Float() * 10); } -float AnimationControls::getMovementDuration(const CCreature * creature) +float AnimationControls::getMovementDistance(const CCreature * creature) { - return static_cast(settings["battle"]["animationSpeed"].Float() * 4 / creature->animation.walkAnimationTime); + // H3 speed: 2/4/6 tiles per second + return static_cast( 2.0 * settings["battle"]["animationSpeed"].Float() / creature->animation.walkAnimationTime); } float AnimationControls::getFlightDistance(const CCreature * creature) { - return static_cast(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( 250.0 * settings["battle"]["animationSpeed"].Float() / creature->animation.walkAnimationTime); } float AnimationControls::getFadeInDuration() { - return 1.0f / settings["battle"]["animationSpeed"].Float(); + // H3 speed: 500/250/166 ms + return 0.5f / settings["battle"]["animationSpeed"].Float(); } 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 +425,5 @@ void CreatureAnimation::pause() void CreatureAnimation::play() { //logAnim->trace("Play %s group %d at %d:%d", name, static_cast(getType()), pos.x, pos.y); - speed = 0; - if(speedController(this, type) != 0) - speed = 1 / speedController(this, type); + speed = speedController(this, type); } diff --git a/client/battle/CreatureAnimation.h b/client/battle/CreatureAnimation.h index cdead9c79..04f68afac 100644 --- a/client/battle/CreatureAnimation.h +++ b/client/battle/CreatureAnimation.h @@ -31,19 +31,22 @@ namespace AnimationControls /// 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 diff --git a/config/battleEffects.json b/config/battleEffects.json index 089d8dbef..7f08e608e 100644 --- a/config/battleEffects.json +++ b/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, }, ], } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index f5e256301..8c72c5b10 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -851,7 +851,7 @@ 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(); //? + cre->animation.missileVelocityFactor = animationTime["missile"].Float(); const JsonNode & missile = graphics["missile"]; const JsonNode & offsets = missile["offset"]; diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 0958c6eea..acf27a08b 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -73,7 +73,7 @@ public: }; double timeBetweenFidgets, idleAnimationTime, - walkAnimationTime, attackAnimationTime, flightAnimationDistance; + walkAnimationTime, attackAnimationTime, missileVelocityFactor; int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX, upperRightMissleOffsetY, rightMissleOffsetY, lowerRightMissleOffsetY; @@ -90,7 +90,7 @@ public: h & idleAnimationTime; h & walkAnimationTime; h & attackAnimationTime; - h & flightAnimationDistance; + h & missileVelocityFactor; h & upperRightMissleOffsetX; h & rightMissleOffsetX; h & lowerRightMissleOffsetX;