Browse Source

Introduce AnimationInfo

Update AnimationInfo.DelayLen comment
pull/1753/head
obligaron 5 years ago committed by Anders Jenbo
parent
commit
0b5183f63e
  1. 3
      CMakeLists.txt
  2. 11
      Source/engine/animationinfo.cpp
  3. 51
      Source/engine/animationinfo.h
  4. 16
      Source/loadsave.cpp
  5. 4
      Source/msg.cpp
  6. 4
      Source/multi.cpp
  7. 154
      Source/player.cpp
  8. 20
      Source/player.h
  9. 2
      Source/scrollrt.cpp
  10. 32
      test/animationinfo_test.cpp
  11. 4
      test/player_test.cpp
  12. 8
      test/writehero_test.cpp

3
CMakeLists.txt

@ -343,6 +343,7 @@ set(devilutionx_SRCS
Source/controls/modifier_hints.cpp
Source/controls/plrctrls.cpp
Source/controls/touch.cpp
Source/engine/animationinfo.cpp
Source/qol/autopickup.cpp
Source/qol/common.cpp
Source/qol/monhealthbar.cpp
@ -453,7 +454,7 @@ if(RUN_TESTS)
test/scrollrt_test.cpp
test/stores_test.cpp
test/writehero_test.cpp
test/animationinformation_test.cpp)
test/animationinfo_test.cpp)
endif()
add_executable(${BIN_TARGET} WIN32 MACOSX_BUNDLE ${devilutionx_SRCS})

11
Source/engine/animationinfo.cpp

@ -0,0 +1,11 @@
/**
* @file animationinfo.cpp
*
* Contains the core animation information and related logic
*/
#include "animationinfo.h"
namespace devilution {
} // namespace devilution

51
Source/engine/animationinfo.h

@ -0,0 +1,51 @@
/**
* @file animationinfo.h
*
* Contains the core animation information and related logic
*/
#pragma once
#include <stdint.h>
namespace devilution {
/*
* @brief Contains the core animation information and related logic
*/
class AnimationInfo {
public:
/*
* @brief Pointer to Animation Data
*/
uint8_t *_pAnimData;
/*
* @brief Additional delay of each animation in the current animation
*/
int _pAnimDelay;
/*
* @brief Increases by one each game tick, counting how close we are to _pAnimDelay
*/
int _pAnimCnt;
/*
* @brief Number of frames in current animation
*/
int _pAnimLen;
/*
* @brief Current frame of animation
*/
int _pAnimFrame;
/*
* @brief Specifies how many animations-fractions are displayed between two gameticks. this can be > 0, if animations are skipped or < 0 if the same animation is shown in multiple times (delay specified).
*/
float _pAnimGameTickModifier;
/*
* @brief Number of GameTicks after the current animation sequence started
*/
int _pAnimGameTicksSinceSequenceStarted;
/*
* @brief Animation Frames that will be adjusted for the skipped Frames/GameTicks
*/
int _pAnimRelevantAnimationFramesForDistributing;
};
} // namespace devilution

16
Source/loadsave.cpp

@ -343,10 +343,10 @@ static void LoadPlayer(LoadHelper *file, int p)
file->skip(4); // Unused
pPlayer->_pgfxnum = file->nextLE<int32_t>();
file->skip(4); // Skip pointer _pAnimData
pPlayer->_pAnimDelay = file->nextLE<int32_t>();
pPlayer->_pAnimCnt = file->nextLE<int32_t>();
pPlayer->_pAnimLen = file->nextLE<int32_t>();
pPlayer->_pAnimFrame = file->nextLE<int32_t>();
pPlayer->AnimInfo._pAnimDelay = file->nextLE<int32_t>();
pPlayer->AnimInfo._pAnimCnt = file->nextLE<int32_t>();
pPlayer->AnimInfo._pAnimLen = file->nextLE<int32_t>();
pPlayer->AnimInfo._pAnimFrame = file->nextLE<int32_t>();
pPlayer->_pAnimWidth = file->nextLE<int32_t>();
// Skip _pAnimWidth2
file->skip(4);
@ -1327,10 +1327,10 @@ static void SavePlayer(SaveHelper *file, int p)
file->skip(4); // Unused
file->writeLE<int32_t>(pPlayer->_pgfxnum);
file->skip(4); // Skip pointer _pAnimData
file->writeLE<int32_t>(pPlayer->_pAnimDelay);
file->writeLE<int32_t>(pPlayer->_pAnimCnt);
file->writeLE<int32_t>(pPlayer->_pAnimLen);
file->writeLE<int32_t>(pPlayer->_pAnimFrame);
file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimDelay);
file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimCnt);
file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimLen);
file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimFrame);
file->writeLE<int32_t>(pPlayer->_pAnimWidth);
// write _pAnimWidth2 for vanilla compatibility
file->writeLE<int32_t>(CalculateWidth2(pPlayer->_pAnimWidth));

4
Source/msg.cpp

@ -2235,8 +2235,8 @@ static DWORD On_PLAYER_JOINLEVEL(TCmd *pCmd, int pnum)
LoadPlrGFX(pnum, PFILE_DEATH);
plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1;
plr[pnum].actionFrame = plr[pnum]._pAnimLen * 2;
plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = plr[pnum].AnimInfo._pAnimLen * 2;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER;
}

4
Source/multi.cpp

@ -880,8 +880,8 @@ void recv_plrinfo(int pnum, TCmdPlrInfoHdr *p, bool recv)
LoadPlrGFX(pnum, PFILE_DEATH);
plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum]._pAnimLen;
plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum].AnimInfo._pAnimLen;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER;
}
}

154
Source/player.cpp

@ -544,15 +544,15 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat
app_fatal("NewPlrAnim: illegal player %d", pnum);
}
plr[pnum]._pAnimData = Peq;
plr[pnum]._pAnimLen = numFrames;
plr[pnum]._pAnimFrame = 1;
plr[pnum]._pAnimCnt = 0;
plr[pnum]._pAnimDelay = Delay;
plr[pnum].AnimInfo._pAnimData = Peq;
plr[pnum].AnimInfo._pAnimLen = numFrames;
plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum].AnimInfo._pAnimCnt = 0;
plr[pnum].AnimInfo._pAnimDelay = Delay;
plr[pnum]._pAnimWidth = width;
plr[pnum]._pAnimGameTicksSinceSequenceStarted = 0;
plr[pnum]._pAnimRelevantAnimationFramesForDistributing = 0;
plr[pnum]._pAnimGameTickModifier = 0.0f;
plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted = 0;
plr[pnum].AnimInfo._pAnimRelevantAnimationFramesForDistributing = 0;
plr[pnum].AnimInfo._pAnimGameTickModifier = 0.0f;
if (numSkippedFrames != 0 || params != AnimationDistributionParams::None) {
// Animation Frames that will be adjusted for the skipped Frames/GameTicks
@ -583,7 +583,7 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat
// The Animation Distribution Logic needs to account how many GameTicks passed since the Animation started.
// Because ProcessAnimation will increase this later (in same GameTick as NewPlrAnim), we correct this upfront.
// This also means Rendering should never hapen with _pAnimGameTicksSinceSequenceStarted < 0.
plr[pnum]._pAnimGameTicksSinceSequenceStarted = -1;
plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted = -1;
}
if (params == AnimationDistributionParams::SkipsDelayOfLastFrame) {
@ -612,8 +612,8 @@ void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, Animat
// 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;
plr[pnum].AnimInfo._pAnimRelevantAnimationFramesForDistributing = relevantAnimationFramesForDistributing;
plr[pnum].AnimInfo._pAnimGameTickModifier = gameTickModifier;
}
}
@ -1135,13 +1135,13 @@ void InitPlayer(int pnum, bool FirstTime)
if (plr[pnum]._pHitPoints >> 6 > 0) {
plr[pnum]._pmode = PM_STAND;
NewPlrAnim(pnum, plr[pnum]._pNAnim[DIR_S], plr[pnum]._pNFrames, 3, plr[pnum]._pNWidth);
plr[pnum]._pAnimFrame = GenerateRnd(plr[pnum]._pNFrames - 1) + 1;
plr[pnum]._pAnimCnt = GenerateRnd(3);
plr[pnum].AnimInfo._pAnimFrame = GenerateRnd(plr[pnum]._pNFrames - 1) + 1;
plr[pnum].AnimInfo._pAnimCnt = GenerateRnd(3);
} else {
plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum]._pAnimLen;
plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum].AnimInfo._pAnimLen;
}
plr[pnum]._pdir = DIR_S;
@ -2275,21 +2275,21 @@ bool PM_DoWalk(int pnum, int variant)
//Play walking sound effect on certain animation frames
if (sgOptions.Audio.bWalkingSound) {
if (plr[pnum]._pAnimFrame == 3
|| (plr[pnum]._pWFrames == 8 && plr[pnum]._pAnimFrame == 7)
|| (plr[pnum]._pWFrames != 8 && plr[pnum]._pAnimFrame == 4)) {
if (plr[pnum].AnimInfo._pAnimFrame == 3
|| (plr[pnum]._pWFrames == 8 && plr[pnum].AnimInfo._pAnimFrame == 7)
|| (plr[pnum]._pWFrames != 8 && plr[pnum].AnimInfo._pAnimFrame == 4)) {
PlaySfxLoc(PS_WALK1, plr[pnum].position.tile.x, plr[pnum].position.tile.y);
}
}
//"Jog" in town which works by doubling movement speed and skipping every other animation frame
if (currlevel == 0 && sgGameInitInfo.bRunInTown) {
if (plr[pnum]._pAnimFrame % 2 == 0) {
plr[pnum]._pAnimFrame++;
if (plr[pnum].AnimInfo._pAnimFrame % 2 == 0) {
plr[pnum].AnimInfo._pAnimFrame++;
plr[pnum].actionFrame++;
}
if (plr[pnum]._pAnimFrame >= plr[pnum]._pWFrames) {
plr[pnum]._pAnimFrame = 0;
if (plr[pnum].AnimInfo._pAnimFrame >= plr[pnum]._pWFrames) {
plr[pnum].AnimInfo._pAnimFrame = 0;
}
}
@ -2799,24 +2799,24 @@ bool PM_DoAttack(int pnum)
app_fatal("PM_DoAttack: illegal player %d", pnum);
}
frame = plr[pnum]._pAnimFrame;
frame = plr[pnum].AnimInfo._pAnimFrame;
if (plr[pnum]._pIFlags & ISPL_QUICKATTACK && frame == 1) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTATTACK && (frame == 1 || frame == 3)) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTERATTACK && (frame == 1 || frame == 3 || frame == 5)) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTESTATTACK && (frame == 1 || frame == 4)) {
plr[pnum]._pAnimFrame += 2;
plr[pnum].AnimInfo._pAnimFrame += 2;
}
if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum - 1) {
if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFNum - 1) {
PlaySfxLoc(PS_SWING, plr[pnum].position.tile.x, plr[pnum].position.tile.y);
}
if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum) {
if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFNum) {
dx = plr[pnum].position.tile.x + offset_x[plr[pnum]._pdir];
dy = plr[pnum].position.tile.y + offset_y[plr[pnum]._pdir];
@ -2893,7 +2893,7 @@ bool PM_DoAttack(int pnum)
}
}
if (plr[pnum]._pAnimFrame == plr[pnum]._pAFrames) {
if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFrames) {
StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum);
return true;
@ -2910,20 +2910,20 @@ bool PM_DoRangeAttack(int pnum)
}
if (!gbIsHellfire) {
origFrame = plr[pnum]._pAnimFrame;
origFrame = plr[pnum].AnimInfo._pAnimFrame;
if (plr[pnum]._pIFlags & ISPL_QUICKATTACK && origFrame == 1) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTATTACK && (origFrame == 1 || origFrame == 3)) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
}
int arrows = 0;
if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum) {
if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFNum) {
arrows = 1;
}
if ((plr[pnum]._pIFlags & ISPL_MULT_ARROWS) != 0 && plr[pnum]._pAnimFrame == plr[pnum]._pAFNum + 2) {
if ((plr[pnum]._pIFlags & ISPL_MULT_ARROWS) != 0 && plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFNum + 2) {
arrows = 2;
}
@ -2976,7 +2976,7 @@ bool PM_DoRangeAttack(int pnum)
}
}
if (plr[pnum]._pAnimFrame >= plr[pnum]._pAFrames) {
if (plr[pnum].AnimInfo._pAnimFrame >= plr[pnum]._pAFrames) {
StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum);
return true;
@ -3025,11 +3025,11 @@ bool PM_DoBlock(int pnum)
app_fatal("PM_DoBlock: illegal player %d", pnum);
}
if (plr[pnum]._pIFlags & ISPL_FASTBLOCK && plr[pnum]._pAnimFrame != 1) {
plr[pnum]._pAnimFrame = plr[pnum]._pBFrames;
if (plr[pnum]._pIFlags & ISPL_FASTBLOCK && plr[pnum].AnimInfo._pAnimFrame != 1) {
plr[pnum].AnimInfo._pAnimFrame = plr[pnum]._pBFrames;
}
if (plr[pnum]._pAnimFrame >= plr[pnum]._pBFrames) {
if (plr[pnum].AnimInfo._pAnimFrame >= plr[pnum]._pBFrames) {
StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum);
@ -3121,7 +3121,7 @@ bool PM_DoSpell(int pnum)
ClearPlrPVars(pnum);
return true;
}
} else if (plr[pnum]._pAnimFrame == plr[pnum]._pSFrames) {
} else if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pSFrames) {
StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum);
return true;
@ -3138,18 +3138,18 @@ bool PM_DoGotHit(int pnum)
app_fatal("PM_DoGotHit: illegal player %d", pnum);
}
frame = plr[pnum]._pAnimFrame;
frame = plr[pnum].AnimInfo._pAnimFrame;
if (plr[pnum]._pIFlags & ISPL_FASTRECOVER && frame == 3) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTERRECOVER && (frame == 3 || frame == 5)) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pIFlags & ISPL_FASTESTRECOVER && (frame == 1 || frame == 3 || frame == 5)) {
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
if (plr[pnum]._pAnimFrame >= plr[pnum]._pHFrames) {
if (plr[pnum].AnimInfo._pAnimFrame >= plr[pnum]._pHFrames) {
StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum);
if (GenerateRnd(4) != 0) {
@ -3179,8 +3179,8 @@ bool PM_DoDeath(int pnum)
}
}
plr[pnum]._pAnimDelay = 10000;
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen;
plr[pnum].AnimInfo._pAnimDelay = 10000;
plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER;
}
@ -3437,7 +3437,7 @@ void CheckNewPath(int pnum)
return;
}
if (plr[pnum]._pmode == PM_ATTACK && plr[pnum]._pAnimFrame > plr[myplr]._pAFNum) {
if (plr[pnum]._pmode == PM_ATTACK && plr[pnum].AnimInfo._pAnimFrame > plr[myplr]._pAFNum) {
if (plr[pnum].destAction == ACTION_ATTACK) {
d = GetDirection(plr[pnum].position.future, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartAttack(pnum, d);
@ -3476,7 +3476,7 @@ void CheckNewPath(int pnum)
}
}
if (plr[pnum]._pmode == PM_RATTACK && plr[pnum]._pAnimFrame > plr[myplr]._pAFNum) {
if (plr[pnum]._pmode == PM_RATTACK && plr[pnum].AnimInfo._pAnimFrame > plr[myplr]._pAFNum) {
if (plr[pnum].destAction == ACTION_RATTACK) {
d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartRangeAttack(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
@ -3494,7 +3494,7 @@ void CheckNewPath(int pnum)
}
}
if (plr[pnum]._pmode == PM_SPELL && plr[pnum]._pAnimFrame > plr[pnum]._pSFNum) {
if (plr[pnum]._pmode == PM_SPELL && plr[pnum].AnimInfo._pAnimFrame > plr[pnum]._pSFNum) {
if (plr[pnum].destAction == ACTION_SPELL) {
d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartSpell(pnum, d, plr[pnum].destParam1, plr[pnum].destParam2);
@ -3714,14 +3714,14 @@ void ProcessPlayers()
void ProcessPlayerAnimation(int pnum)
{
plr[pnum]._pAnimCnt++;
plr[pnum]._pAnimGameTicksSinceSequenceStarted++;
if (plr[pnum]._pAnimCnt > plr[pnum]._pAnimDelay) {
plr[pnum]._pAnimCnt = 0;
plr[pnum]._pAnimFrame++;
if (plr[pnum]._pAnimFrame > plr[pnum]._pAnimLen) {
plr[pnum]._pAnimFrame = 1;
plr[pnum]._pAnimGameTicksSinceSequenceStarted = 0;
plr[pnum].AnimInfo._pAnimCnt++;
plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted++;
if (plr[pnum].AnimInfo._pAnimCnt > plr[pnum].AnimInfo._pAnimDelay) {
plr[pnum].AnimInfo._pAnimCnt = 0;
plr[pnum].AnimInfo._pAnimFrame++;
if (plr[pnum].AnimInfo._pAnimFrame > plr[pnum].AnimInfo._pAnimLen) {
plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted = 0;
}
}
}
@ -3732,22 +3732,22 @@ int GetFrameToUseForPlayerRendering(const PlayerStruct *pPlayer)
// - if no frame-skipping is required and so we have exactly one Animationframe per GameTick
// or
// - if we load from a savegame where the new variables are not stored (we don't want to break savegame compatiblity because of smoother rendering of one animation)
int relevantAnimationFramesForDistributing = pPlayer->_pAnimRelevantAnimationFramesForDistributing;
int relevantAnimationFramesForDistributing = pPlayer->AnimInfo._pAnimRelevantAnimationFramesForDistributing;
if (relevantAnimationFramesForDistributing <= 0)
return pPlayer->_pAnimFrame;
return pPlayer->AnimInfo._pAnimFrame;
if (pPlayer->_pAnimFrame > relevantAnimationFramesForDistributing)
return pPlayer->_pAnimFrame;
if (pPlayer->AnimInfo._pAnimFrame > relevantAnimationFramesForDistributing)
return pPlayer->AnimInfo._pAnimFrame;
assert(pPlayer->_pAnimGameTicksSinceSequenceStarted >= 0);
assert(pPlayer->AnimInfo._pAnimGameTicksSinceSequenceStarted >= 0);
float progressToNextGameTick = gfProgressToNextGameTick;
// 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;
float totalGameTicksForCurrentAnimationSequence = progressToNextGameTick + (float)pPlayer->AnimInfo._pAnimGameTicksSinceSequenceStarted;
// 1 added for rounding reasons. float to int cast always truncate.
int absoluteAnimationFrame = 1 + (int)(totalGameTicksForCurrentAnimationSequence * pPlayer->_pAnimGameTickModifier);
int absoluteAnimationFrame = 1 + (int)(totalGameTicksForCurrentAnimationSequence * pPlayer->AnimInfo._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)) {
@ -3966,21 +3966,21 @@ void SyncPlrAnim(int pnum)
dir = plr[pnum]._pdir;
switch (plr[pnum]._pmode) {
case PM_STAND:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break;
case PM_WALK:
case PM_WALK2:
case PM_WALK3:
plr[pnum]._pAnimData = plr[pnum]._pWAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pWAnim[dir];
break;
case PM_ATTACK:
plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pAAnim[dir];
break;
case PM_RATTACK:
plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pAAnim[dir];
break;
case PM_BLOCK:
plr[pnum]._pAnimData = plr[pnum]._pBAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pBAnim[dir];
break;
case PM_SPELL:
if (pnum == myplr)
@ -3988,23 +3988,23 @@ void SyncPlrAnim(int pnum)
else
sType = STYPE_FIRE;
if (sType == STYPE_FIRE)
plr[pnum]._pAnimData = plr[pnum]._pFAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pFAnim[dir];
if (sType == STYPE_LIGHTNING)
plr[pnum]._pAnimData = plr[pnum]._pLAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pLAnim[dir];
if (sType == STYPE_MAGIC)
plr[pnum]._pAnimData = plr[pnum]._pTAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pTAnim[dir];
break;
case PM_GOTHIT:
plr[pnum]._pAnimData = plr[pnum]._pHAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pHAnim[dir];
break;
case PM_NEWLVL:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break;
case PM_DEATH:
plr[pnum]._pAnimData = plr[pnum]._pDAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pDAnim[dir];
break;
case PM_QUIT:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir];
plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break;
default:
app_fatal("SyncPlrAnim");

20
Source/player.h

@ -16,6 +16,7 @@
#include "path.h"
#include "interfac.h"
#include "utils/enum_traits.h"
#include "engine/animationinfo.h"
namespace devilution {
@ -160,24 +161,11 @@ struct PlayerStruct {
ActorPosition position;
direction _pdir; // Direction faced by player (direction enum)
int _pgfxnum; // Bitmask indicating what variant of the sprite the player is using. Lower byte define weapon (anim_weapon_id) and higher values define armour (starting with anim_armor_id)
uint8_t *_pAnimData;
int _pAnimDelay; // Tick length of each frame in the current animation
int _pAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
int _pAnimLen; // Number of frames in current animation
int _pAnimFrame; // Current frame of animation.
int _pAnimWidth;
/*
* @brief Specifies how many animations-fractions are displayed between two gameticks. this can be > 0, if animations are skipped or < 0 if the same animation is shown in multiple times (delay specified).
*/
float _pAnimGameTickModifier;
/*
* @brief Number of GameTicks after the current animation sequence started
* @brief Contains Information for current Animation
*/
int _pAnimGameTicksSinceSequenceStarted;
/*
* @brief Animation Frames that will be adjusted for the skipped Frames/GameTicks
*/
int _pAnimRelevantAnimationFramesForDistributing;
AnimationInfo AnimInfo;
int _pAnimWidth;
int _plid;
int _pvid;
spell_id _pSpell;

2
Source/scrollrt.cpp

@ -416,7 +416,7 @@ static void DrawPlayer(const CelOutputBuffer &out, int pnum, int x, int y, int p
PlayerStruct *pPlayer = &plr[pnum];
BYTE *pCelBuff = pPlayer->_pAnimData;
BYTE *pCelBuff = pPlayer->AnimInfo._pAnimData;
int nCel = GetFrameToUseForPlayerRendering(pPlayer);
int nWidth = pPlayer->_pAnimWidth;

32
test/animationinformation_test.cpp → test/animationinfo_test.cpp

@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include "nthread.h"
#include "player.h"
#include "engine/animationinfo.h"
using namespace devilution;
@ -53,8 +53,8 @@ struct RenderingData : TestData {
void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams params, int numSkippedFrames, int distributeFramesBeforeFrame, const std::vector<TestData *> &vecTestData)
{
const int pnum = 0;
PlayerStruct *pPlayer = &plr[pnum];
NewPlrAnim(pnum, nullptr, numFrames, delay, 128, params, numSkippedFrames, distributeFramesBeforeFrame);
AnimationInfo animInfo;
animInfo.SetNewAnimation(nullptr, numFrames, delay, params, numSkippedFrames, distributeFramesBeforeFrame);
int currentGameTick = 0;
for (TestData *x : vecTestData) {
@ -62,10 +62,10 @@ void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams para
if (gameTickData != nullptr) {
currentGameTick += 1;
if (gameTickData->_FramesToSkip != 0)
pPlayer->_pAnimFrame += gameTickData->_FramesToSkip;
pPlayer->AnimInfo._pAnimFrame += gameTickData->_FramesToSkip;
ProcessPlayerAnimation(pnum);
EXPECT_EQ(pPlayer->_pAnimFrame, gameTickData->_ExpectedAnimationFrame);
EXPECT_EQ(pPlayer->_pAnimCnt, gameTickData->_ExpectedAnimationCnt);
EXPECT_EQ(pPlayer->AnimInfo._pAnimFrame, gameTickData->_ExpectedAnimationFrame);
EXPECT_EQ(pPlayer->AnimInfo._pAnimCnt, gameTickData->_ExpectedAnimationCnt);
}
auto renderingData = dynamic_cast<RenderingData *>(x);
@ -74,8 +74,8 @@ void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams para
EXPECT_EQ(GetFrameToUseForPlayerRendering(pPlayer), renderingData->_ExpectedRenderingFrame)
<< std::fixed << std::setprecision(2)
<< "ProgressToNextGameTick: " << renderingData->_fProgressToNextGameTick
<< " AnimFrame: " << pPlayer->_pAnimFrame
<< " AnimCnt: " << pPlayer->_pAnimCnt
<< " AnimFrame: " << pPlayer->AnimInfo._pAnimFrame
<< " AnimCnt: " << pPlayer->AnimInfo._pAnimCnt
<< " GameTick: " << currentGameTick;
}
}
@ -84,11 +84,11 @@ void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams para
}
}
TEST(AnimationInformation, AttackSwordWarrior) // ProcessAnimationPending should be considered by distribution logic
TEST(AnimationInfo, AttackSwordWarrior) // ProcessAnimationPending should be considered by distribution logic
{
RunAnimationTest(16, 0, AnimationDistributionParams::ProcessAnimationPending, 0, 9,
{
// ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before.
// ProcessAnimation 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),
@ -146,11 +146,11 @@ TEST(AnimationInformation, AttackSwordWarrior) // ProcessAnimationPending should
});
}
TEST(AnimationInformation, AttackSwordWarriorWithFastestAttack) // Skipped frames and ProcessAnimationPending should be considered by distribution logic
TEST(AnimationInfo, AttackSwordWarriorWithFastestAttack) // Skipped frames and ProcessAnimationPending should be considered by distribution logic
{
RunAnimationTest(16, 0, AnimationDistributionParams::ProcessAnimationPending, 2, 9,
{
// ProcessPlayer directly after StartAttack (in same GameTick). So we don't see any rendering before.
// ProcessAnimation 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),
@ -198,7 +198,7 @@ TEST(AnimationInformation, AttackSwordWarriorWithFastestAttack) // Skipped frame
});
}
TEST(AnimationInformation, BlockingWarriorNormal) // Ignored delay for last Frame should be considered by distribution logic
TEST(AnimationInfo, BlockingWarriorNormal) // Ignored delay for last Frame should be considered by distribution logic
{
RunAnimationTest(2, 2, AnimationDistributionParams::SkipsDelayOfLastFrame, 0, 0,
{
@ -225,7 +225,7 @@ TEST(AnimationInformation, BlockingWarriorNormal) // Ignored delay for last Fram
});
}
TEST(AnimationInformation, BlockingSorcererWithFastBlock) // Skipped frames and ignored delay for last Frame should be considered by distribution logic
TEST(AnimationInfo, BlockingSorcererWithFastBlock) // Skipped frames and ignored delay for last Frame should be considered by distribution logic
{
RunAnimationTest(6, 2, AnimationDistributionParams::SkipsDelayOfLastFrame, 4, 0,
{
@ -252,7 +252,7 @@ TEST(AnimationInformation, BlockingSorcererWithFastBlock) // Skipped frames and
});
}
TEST(AnimationInformation, HitRecoverySorcererZenMode) // Skipped frames and ignored delay for last Frame should be considered by distribution logic
TEST(AnimationInfo, HitRecoverySorcererZenMode) // Skipped frames and ignored delay for last Frame should be considered by distribution logic
{
RunAnimationTest(8, 0, AnimationDistributionParams::None, 4, 0,
{
@ -278,7 +278,7 @@ TEST(AnimationInformation, HitRecoverySorcererZenMode) // Skipped frames and ign
// Animation stopped cause PM_DoGotHit would stop the Animation "if (plr[pnum]._pAnimFrame >= plr[pnum]._pHFrames) {"
});
}
TEST(AnimationInformation, Stand) // Distribution Logic shouldn't change anything here
TEST(AnimationInfo, Stand) // Distribution Logic shouldn't change anything here
{
RunAnimationTest(10, 3, AnimationDistributionParams::None, 0, 0,
{

4
test/player_test.cpp

@ -11,7 +11,7 @@ extern bool PM_DoGotHit(int pnum);
int RunBlockTest(int frames, int flags)
{
int pnum = 0;
plr[pnum]._pAnimFrame = 1;
plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum]._pHFrames = frames;
plr[pnum].actionFrame = 1;
plr[pnum]._pIFlags = flags;
@ -23,7 +23,7 @@ int RunBlockTest(int frames, int flags)
PM_DoGotHit(pnum);
if (plr[pnum]._pmode != PM_GOTHIT)
break;
plr[pnum]._pAnimFrame++;
plr[pnum].AnimInfo._pAnimFrame++;
}
return i;

8
test/writehero_test.cpp

@ -292,10 +292,10 @@ static void AssertPlayer(PlayerStruct *pPlayer)
ASSERT_EQ(pPlayer->_pmode, 0);
ASSERT_EQ(Count8(pPlayer->walkpath, MAX_PATH_LENGTH), 25);
ASSERT_EQ(pPlayer->_pgfxnum, 36);
ASSERT_EQ(pPlayer->_pAnimDelay, 3);
ASSERT_EQ(pPlayer->_pAnimCnt, 1);
ASSERT_EQ(pPlayer->_pAnimLen, 20);
ASSERT_EQ(pPlayer->_pAnimFrame, 1);
ASSERT_EQ(pPlayer->AnimInfo._pAnimDelay, 3);
ASSERT_EQ(pPlayer->AnimInfo._pAnimCnt, 1);
ASSERT_EQ(pPlayer->AnimInfo._pAnimLen, 20);
ASSERT_EQ(pPlayer->AnimInfo._pAnimFrame, 1);
ASSERT_EQ(pPlayer->_pAnimWidth, 96);
ASSERT_EQ(pPlayer->_pSpell, -1);
ASSERT_EQ(pPlayer->_pSplType, 4);

Loading…
Cancel
Save