diff --git a/Source/player.cpp b/Source/player.cpp index e2662e6d3..117bfe6f2 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -563,7 +563,8 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat plr[pnum]._pAnimGameTickModifier = 0.0f; if (numSkippedFrames != 0 || flags != AnimationDistributionFlags::None) { - int relevantAnimationFramesForDistributing = numFrames; // Animation Frames that will be adjusted for the skipped Frames/GameTicks + // Animation Frames that will be adjusted for the skipped Frames/GameTicks + int relevantAnimationFramesForDistributing = numFrames; if (distributeFramesBeforeFrame != 0) { // After an attack hits (_pAFNum or _pSFNum) it can be canceled or another attack can be queued and this means the animation is canceled. // In normal attacks frame skipping always happens before the attack actual hit. @@ -572,10 +573,15 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat relevantAnimationFramesForDistributing = distributeFramesBeforeFrame - 1; } - int gameTicksPerFrame = (Delay + 1); // How many GameTicks are needed to advance one Animation Frame - int relevantAnimationGameTicksForDistribution = relevantAnimationFramesForDistributing * gameTicksPerFrame; // GameTicks that will be adjusted for the skipped Frames/GameTicks + // How many GameTicks are needed to advance one Animation Frame + int gameTicksPerFrame = (Delay + 1); + + // GameTicks that will be adjusted for the skipped Frames/GameTicks + int relevantAnimationGameTicksForDistribution = relevantAnimationFramesForDistributing * gameTicksPerFrame; + + // How many GameTicks will the Animation be really shown (skipped Frames and GameTicks removed) + int relevantAnimationGameTicksWithSkipping = relevantAnimationGameTicksForDistribution - (numSkippedFrames * gameTicksPerFrame); - int relevantAnimationGameTicksWithSkipping = relevantAnimationGameTicksForDistribution - (numSkippedFrames * gameTicksPerFrame); // How many GameTicks will the Animation be really shown (skipped Frames and GameTicks removed) if ((flags & AnimationDistributionFlags::ProcessAnimationPending) == AnimationDistributionFlags::ProcessAnimationPending) { // If ProcessAnimation will be called after NewPlrAnim (in same GameTick as NewPlrAnim), we increment the Animation-Counter. // If no delay is specified, this will result in complete skipped frame (see ProcessPlayerAnimation). @@ -608,8 +614,11 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat relevantAnimationGameTicksWithSkipping -= Delay; } - float gameTickModifier = (float)relevantAnimationGameTicksForDistribution / (float)relevantAnimationGameTicksWithSkipping; // if we skipped Frames we need to expand the GameTicks to make one GameTick for this Animation "faster" - gameTickModifier /= gameTicksPerFrame; // gameTickModifier specifies the Animation fraction per GameTick, so we have to remove the delay from the variable + // if we skipped Frames we need to expand the GameTicks to make one GameTick for this Animation "faster" + float gameTickModifier = (float)relevantAnimationGameTicksForDistribution / (float)relevantAnimationGameTicksWithSkipping; + + // gameTickModifier specifies the Animation fraction per GameTick, so we have to remove the delay from the variable + gameTickModifier /= gameTicksPerFrame; plr[pnum]._pAnimRelevantAnimationFramesForDistributing = relevantAnimationFramesForDistributing; plr[pnum]._pAnimGameTickModifier = gameTickModifier; @@ -3794,11 +3803,18 @@ int GetFrameToUseForPlayerRendering(const PlayerStruct *pPlayer) assert(pPlayer->_pAnimGameTicksSinceSequenceStarted >= 0); float progressToNextGameTick = gfProgressToNextGameTick; - float totalGameTicksForCurrentAnimationSequence = progressToNextGameTick + (float)pPlayer->_pAnimGameTicksSinceSequenceStarted; // we don't use the processed game ticks alone but also the fragtion of the next game tick (if a rendering happens between game ticks). This helps to smooth the animations. - int absoluteAnimationFrame = 1 + (int)(totalGameTicksForCurrentAnimationSequence * pPlayer->_pAnimGameTickModifier); // 1 added for rounding reasons. float to int cast always truncate. - if (absoluteAnimationFrame > relevantAnimationFramesForDistributing) { // this can happen if we are at the last frame and the next game tick is due (nthread_GetProgressToNextGameTick returns 1.0f) - if (absoluteAnimationFrame > (relevantAnimationFramesForDistributing + 1)) // we should never have +2 frames even if next game tick is due + + // we don't use the processed game ticks alone but also the fragtion of the next game tick (if a rendering happens between game ticks). This helps to smooth the animations. + float totalGameTicksForCurrentAnimationSequence = progressToNextGameTick + (float)pPlayer->_pAnimGameTicksSinceSequenceStarted; + + // 1 added for rounding reasons. float to int cast always truncate. + int absoluteAnimationFrame = 1 + (int)(totalGameTicksForCurrentAnimationSequence * pPlayer->_pAnimGameTickModifier); + if (absoluteAnimationFrame > relevantAnimationFramesForDistributing) { + // this can happen if we are at the last frame and the next game tick is due (nthread_GetProgressToNextGameTick returns 1.0f) + if (absoluteAnimationFrame > (relevantAnimationFramesForDistributing + 1)) { + // we should never have +2 frames even if next game tick is due SDL_Log("GetFrameToUseForPlayerRendering: Calculated an invalid Animation Frame (Calculated %d MaxFrame %d)", absoluteAnimationFrame, relevantAnimationFramesForDistributing); + } return relevantAnimationFramesForDistributing; } if (absoluteAnimationFrame <= 0) { diff --git a/test/animationinformation_test.cpp b/test/animationinformation_test.cpp index 5ddfda5cc..53723795b 100644 --- a/test/animationinformation_test.cpp +++ b/test/animationinformation_test.cpp @@ -88,7 +88,8 @@ TEST(AnimationInformation, AttackSwordWarrior) // ProcessAnimationPending should { RunAnimationTest(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 0, 9, { - new GameTickData(2, 0), // ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before. + // ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), new RenderingData(0.0f, 1), new RenderingData(0.3f, 1), new RenderingData(0.6f, 1), @@ -124,7 +125,8 @@ TEST(AnimationInformation, AttackSwordWarrior) // ProcessAnimationPending should new RenderingData(0.6f, 8), new RenderingData(0.8f, 8), - new GameTickData(9, 0), // After this GameTick, the Animation Distribution Logic is disabled + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), new RenderingData(0.1f, 9), new GameTickData(10, 0), new RenderingData(0.4f, 10), @@ -148,7 +150,8 @@ TEST(AnimationInformation, AttackSwordWarriorWithFastestAttack) // Skipped frame { RunAnimationTest(16, 0, AnimationDistributionFlags::ProcessAnimationPending, 2, 9, { - new GameTickData(2, 0), // ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before. + // ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before. + new GameTickData(2, 0), new RenderingData(0.0f, 1), new RenderingData(0.3f, 1), new RenderingData(0.6f, 1), @@ -174,7 +177,8 @@ TEST(AnimationInformation, AttackSwordWarriorWithFastestAttack) // Skipped frame new RenderingData(0.6f, 8), new RenderingData(0.8f, 8), - new GameTickData(9, 0), // After this GameTick, the Animation Distribution Logic is disabled + // After this GameTick, the Animation Distribution Logic is disabled + new GameTickData(9, 0), new RenderingData(0.1f, 9), new GameTickData(10, 0), new RenderingData(0.4f, 10), @@ -367,7 +371,8 @@ TEST(AnimationInformation, Stand) // Distribution Logic shouldn't change anythin new GameTickData(10, 3), new RenderingData(0.6f, 10), - new GameTickData(1, 0), // Animation starts again + // Animation starts again + new GameTickData(1, 0), new RenderingData(0.1f, 1), new GameTickData(1, 1), new RenderingData(0.6f, 1),