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/modifier_hints.cpp
Source/controls/plrctrls.cpp Source/controls/plrctrls.cpp
Source/controls/touch.cpp Source/controls/touch.cpp
Source/engine/animationinfo.cpp
Source/qol/autopickup.cpp Source/qol/autopickup.cpp
Source/qol/common.cpp Source/qol/common.cpp
Source/qol/monhealthbar.cpp Source/qol/monhealthbar.cpp
@ -453,7 +454,7 @@ if(RUN_TESTS)
test/scrollrt_test.cpp test/scrollrt_test.cpp
test/stores_test.cpp test/stores_test.cpp
test/writehero_test.cpp test/writehero_test.cpp
test/animationinformation_test.cpp) test/animationinfo_test.cpp)
endif() endif()
add_executable(${BIN_TARGET} WIN32 MACOSX_BUNDLE ${devilutionx_SRCS}) 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 file->skip(4); // Unused
pPlayer->_pgfxnum = file->nextLE<int32_t>(); pPlayer->_pgfxnum = file->nextLE<int32_t>();
file->skip(4); // Skip pointer _pAnimData file->skip(4); // Skip pointer _pAnimData
pPlayer->_pAnimDelay = file->nextLE<int32_t>(); pPlayer->AnimInfo._pAnimDelay = file->nextLE<int32_t>();
pPlayer->_pAnimCnt = file->nextLE<int32_t>(); pPlayer->AnimInfo._pAnimCnt = file->nextLE<int32_t>();
pPlayer->_pAnimLen = file->nextLE<int32_t>(); pPlayer->AnimInfo._pAnimLen = file->nextLE<int32_t>();
pPlayer->_pAnimFrame = file->nextLE<int32_t>(); pPlayer->AnimInfo._pAnimFrame = file->nextLE<int32_t>();
pPlayer->_pAnimWidth = file->nextLE<int32_t>(); pPlayer->_pAnimWidth = file->nextLE<int32_t>();
// Skip _pAnimWidth2 // Skip _pAnimWidth2
file->skip(4); file->skip(4);
@ -1327,10 +1327,10 @@ static void SavePlayer(SaveHelper *file, int p)
file->skip(4); // Unused file->skip(4); // Unused
file->writeLE<int32_t>(pPlayer->_pgfxnum); file->writeLE<int32_t>(pPlayer->_pgfxnum);
file->skip(4); // Skip pointer _pAnimData file->skip(4); // Skip pointer _pAnimData
file->writeLE<int32_t>(pPlayer->_pAnimDelay); file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimDelay);
file->writeLE<int32_t>(pPlayer->_pAnimCnt); file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimCnt);
file->writeLE<int32_t>(pPlayer->_pAnimLen); file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimLen);
file->writeLE<int32_t>(pPlayer->_pAnimFrame); file->writeLE<int32_t>(pPlayer->AnimInfo._pAnimFrame);
file->writeLE<int32_t>(pPlayer->_pAnimWidth); file->writeLE<int32_t>(pPlayer->_pAnimWidth);
// write _pAnimWidth2 for vanilla compatibility // write _pAnimWidth2 for vanilla compatibility
file->writeLE<int32_t>(CalculateWidth2(pPlayer->_pAnimWidth)); 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); LoadPlrGFX(pnum, PFILE_DEATH);
plr[pnum]._pmode = PM_DEATH; plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth); NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1; plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = plr[pnum]._pAnimLen * 2; plr[pnum].actionFrame = plr[pnum].AnimInfo._pAnimLen * 2;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER; 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); LoadPlrGFX(pnum, PFILE_DEATH);
plr[pnum]._pmode = PM_DEATH; plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth); NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1; plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum]._pAnimLen; plr[pnum].actionFrame = 2 * plr[pnum].AnimInfo._pAnimLen;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER; 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); app_fatal("NewPlrAnim: illegal player %d", pnum);
} }
plr[pnum]._pAnimData = Peq; plr[pnum].AnimInfo._pAnimData = Peq;
plr[pnum]._pAnimLen = numFrames; plr[pnum].AnimInfo._pAnimLen = numFrames;
plr[pnum]._pAnimFrame = 1; plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum]._pAnimCnt = 0; plr[pnum].AnimInfo._pAnimCnt = 0;
plr[pnum]._pAnimDelay = Delay; plr[pnum].AnimInfo._pAnimDelay = Delay;
plr[pnum]._pAnimWidth = width; plr[pnum]._pAnimWidth = width;
plr[pnum]._pAnimGameTicksSinceSequenceStarted = 0; plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted = 0;
plr[pnum]._pAnimRelevantAnimationFramesForDistributing = 0; plr[pnum].AnimInfo._pAnimRelevantAnimationFramesForDistributing = 0;
plr[pnum]._pAnimGameTickModifier = 0.0f; plr[pnum].AnimInfo._pAnimGameTickModifier = 0.0f;
if (numSkippedFrames != 0 || params != AnimationDistributionParams::None) { if (numSkippedFrames != 0 || params != AnimationDistributionParams::None) {
// Animation Frames that will be adjusted for the skipped Frames/GameTicks // 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. // 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. // 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. // This also means Rendering should never hapen with _pAnimGameTicksSinceSequenceStarted < 0.
plr[pnum]._pAnimGameTicksSinceSequenceStarted = -1; plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted = -1;
} }
if (params == AnimationDistributionParams::SkipsDelayOfLastFrame) { 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 specifies the Animation fraction per GameTick, so we have to remove the delay from the variable
gameTickModifier /= gameTicksPerFrame; gameTickModifier /= gameTicksPerFrame;
plr[pnum]._pAnimRelevantAnimationFramesForDistributing = relevantAnimationFramesForDistributing; plr[pnum].AnimInfo._pAnimRelevantAnimationFramesForDistributing = relevantAnimationFramesForDistributing;
plr[pnum]._pAnimGameTickModifier = gameTickModifier; plr[pnum].AnimInfo._pAnimGameTickModifier = gameTickModifier;
} }
} }
@ -1135,13 +1135,13 @@ void InitPlayer(int pnum, bool FirstTime)
if (plr[pnum]._pHitPoints >> 6 > 0) { if (plr[pnum]._pHitPoints >> 6 > 0) {
plr[pnum]._pmode = PM_STAND; plr[pnum]._pmode = PM_STAND;
NewPlrAnim(pnum, plr[pnum]._pNAnim[DIR_S], plr[pnum]._pNFrames, 3, plr[pnum]._pNWidth); 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].AnimInfo._pAnimFrame = GenerateRnd(plr[pnum]._pNFrames - 1) + 1;
plr[pnum]._pAnimCnt = GenerateRnd(3); plr[pnum].AnimInfo._pAnimCnt = GenerateRnd(3);
} else { } else {
plr[pnum]._pmode = PM_DEATH; plr[pnum]._pmode = PM_DEATH;
NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth); NewPlrAnim(pnum, plr[pnum]._pDAnim[DIR_S], plr[pnum]._pDFrames, 1, plr[pnum]._pDWidth);
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen - 1; plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen - 1;
plr[pnum].actionFrame = 2 * plr[pnum]._pAnimLen; plr[pnum].actionFrame = 2 * plr[pnum].AnimInfo._pAnimLen;
} }
plr[pnum]._pdir = DIR_S; plr[pnum]._pdir = DIR_S;
@ -2275,21 +2275,21 @@ bool PM_DoWalk(int pnum, int variant)
//Play walking sound effect on certain animation frames //Play walking sound effect on certain animation frames
if (sgOptions.Audio.bWalkingSound) { if (sgOptions.Audio.bWalkingSound) {
if (plr[pnum]._pAnimFrame == 3 if (plr[pnum].AnimInfo._pAnimFrame == 3
|| (plr[pnum]._pWFrames == 8 && plr[pnum]._pAnimFrame == 7) || (plr[pnum]._pWFrames == 8 && plr[pnum].AnimInfo._pAnimFrame == 7)
|| (plr[pnum]._pWFrames != 8 && plr[pnum]._pAnimFrame == 4)) { || (plr[pnum]._pWFrames != 8 && plr[pnum].AnimInfo._pAnimFrame == 4)) {
PlaySfxLoc(PS_WALK1, plr[pnum].position.tile.x, plr[pnum].position.tile.y); 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 //"Jog" in town which works by doubling movement speed and skipping every other animation frame
if (currlevel == 0 && sgGameInitInfo.bRunInTown) { if (currlevel == 0 && sgGameInitInfo.bRunInTown) {
if (plr[pnum]._pAnimFrame % 2 == 0) { if (plr[pnum].AnimInfo._pAnimFrame % 2 == 0) {
plr[pnum]._pAnimFrame++; plr[pnum].AnimInfo._pAnimFrame++;
plr[pnum].actionFrame++; plr[pnum].actionFrame++;
} }
if (plr[pnum]._pAnimFrame >= plr[pnum]._pWFrames) { if (plr[pnum].AnimInfo._pAnimFrame >= plr[pnum]._pWFrames) {
plr[pnum]._pAnimFrame = 0; plr[pnum].AnimInfo._pAnimFrame = 0;
} }
} }
@ -2799,24 +2799,24 @@ bool PM_DoAttack(int pnum)
app_fatal("PM_DoAttack: illegal player %d", 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) { 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)) { 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)) { 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)) { 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); 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]; dx = plr[pnum].position.tile.x + offset_x[plr[pnum]._pdir];
dy = plr[pnum].position.tile.y + offset_y[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); StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
return true; return true;
@ -2910,20 +2910,20 @@ bool PM_DoRangeAttack(int pnum)
} }
if (!gbIsHellfire) { if (!gbIsHellfire) {
origFrame = plr[pnum]._pAnimFrame; origFrame = plr[pnum].AnimInfo._pAnimFrame;
if (plr[pnum]._pIFlags & ISPL_QUICKATTACK && origFrame == 1) { 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)) { if (plr[pnum]._pIFlags & ISPL_FASTATTACK && (origFrame == 1 || origFrame == 3)) {
plr[pnum]._pAnimFrame++; plr[pnum].AnimInfo._pAnimFrame++;
} }
} }
int arrows = 0; int arrows = 0;
if (plr[pnum]._pAnimFrame == plr[pnum]._pAFNum) { if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pAFNum) {
arrows = 1; 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; 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); StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
return true; return true;
@ -3025,11 +3025,11 @@ bool PM_DoBlock(int pnum)
app_fatal("PM_DoBlock: illegal player %d", pnum); app_fatal("PM_DoBlock: illegal player %d", pnum);
} }
if (plr[pnum]._pIFlags & ISPL_FASTBLOCK && plr[pnum]._pAnimFrame != 1) { if (plr[pnum]._pIFlags & ISPL_FASTBLOCK && plr[pnum].AnimInfo._pAnimFrame != 1) {
plr[pnum]._pAnimFrame = plr[pnum]._pBFrames; 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); StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
@ -3121,7 +3121,7 @@ bool PM_DoSpell(int pnum)
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
return true; return true;
} }
} else if (plr[pnum]._pAnimFrame == plr[pnum]._pSFrames) { } else if (plr[pnum].AnimInfo._pAnimFrame == plr[pnum]._pSFrames) {
StartStand(pnum, plr[pnum]._pdir); StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
return true; return true;
@ -3138,18 +3138,18 @@ bool PM_DoGotHit(int pnum)
app_fatal("PM_DoGotHit: illegal player %d", 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) { 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)) { 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)) { 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); StartStand(pnum, plr[pnum]._pdir);
ClearPlrPVars(pnum); ClearPlrPVars(pnum);
if (GenerateRnd(4) != 0) { if (GenerateRnd(4) != 0) {
@ -3179,8 +3179,8 @@ bool PM_DoDeath(int pnum)
} }
} }
plr[pnum]._pAnimDelay = 10000; plr[pnum].AnimInfo._pAnimDelay = 10000;
plr[pnum]._pAnimFrame = plr[pnum]._pAnimLen; plr[pnum].AnimInfo._pAnimFrame = plr[pnum].AnimInfo._pAnimLen;
dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER; dFlags[plr[pnum].position.tile.x][plr[pnum].position.tile.y] |= BFLAG_DEAD_PLAYER;
} }
@ -3437,7 +3437,7 @@ void CheckNewPath(int pnum)
return; 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) { if (plr[pnum].destAction == ACTION_ATTACK) {
d = GetDirection(plr[pnum].position.future, { plr[pnum].destParam1, plr[pnum].destParam2 }); d = GetDirection(plr[pnum].position.future, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartAttack(pnum, d); 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) { if (plr[pnum].destAction == ACTION_RATTACK) {
d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 }); d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartRangeAttack(pnum, d, 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) { if (plr[pnum].destAction == ACTION_SPELL) {
d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 }); d = GetDirection(plr[pnum].position.tile, { plr[pnum].destParam1, plr[pnum].destParam2 });
StartSpell(pnum, d, 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) void ProcessPlayerAnimation(int pnum)
{ {
plr[pnum]._pAnimCnt++; plr[pnum].AnimInfo._pAnimCnt++;
plr[pnum]._pAnimGameTicksSinceSequenceStarted++; plr[pnum].AnimInfo._pAnimGameTicksSinceSequenceStarted++;
if (plr[pnum]._pAnimCnt > plr[pnum]._pAnimDelay) { if (plr[pnum].AnimInfo._pAnimCnt > plr[pnum].AnimInfo._pAnimDelay) {
plr[pnum]._pAnimCnt = 0; plr[pnum].AnimInfo._pAnimCnt = 0;
plr[pnum]._pAnimFrame++; plr[pnum].AnimInfo._pAnimFrame++;
if (plr[pnum]._pAnimFrame > plr[pnum]._pAnimLen) { if (plr[pnum].AnimInfo._pAnimFrame > plr[pnum].AnimInfo._pAnimLen) {
plr[pnum]._pAnimFrame = 1; plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum]._pAnimGameTicksSinceSequenceStarted = 0; 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 // - if no frame-skipping is required and so we have exactly one Animationframe per GameTick
// or // 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) // - 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) if (relevantAnimationFramesForDistributing <= 0)
return pPlayer->_pAnimFrame; return pPlayer->AnimInfo._pAnimFrame;
if (pPlayer->_pAnimFrame > relevantAnimationFramesForDistributing) if (pPlayer->AnimInfo._pAnimFrame > relevantAnimationFramesForDistributing)
return pPlayer->_pAnimFrame; return pPlayer->AnimInfo._pAnimFrame;
assert(pPlayer->_pAnimGameTicksSinceSequenceStarted >= 0); assert(pPlayer->AnimInfo._pAnimGameTicksSinceSequenceStarted >= 0);
float progressToNextGameTick = gfProgressToNextGameTick; 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. // 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. // 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) { 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) // 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)) { if (absoluteAnimationFrame > (relevantAnimationFramesForDistributing + 1)) {
@ -3966,21 +3966,21 @@ void SyncPlrAnim(int pnum)
dir = plr[pnum]._pdir; dir = plr[pnum]._pdir;
switch (plr[pnum]._pmode) { switch (plr[pnum]._pmode) {
case PM_STAND: case PM_STAND:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break; break;
case PM_WALK: case PM_WALK:
case PM_WALK2: case PM_WALK2:
case PM_WALK3: case PM_WALK3:
plr[pnum]._pAnimData = plr[pnum]._pWAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pWAnim[dir];
break; break;
case PM_ATTACK: case PM_ATTACK:
plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pAAnim[dir];
break; break;
case PM_RATTACK: case PM_RATTACK:
plr[pnum]._pAnimData = plr[pnum]._pAAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pAAnim[dir];
break; break;
case PM_BLOCK: case PM_BLOCK:
plr[pnum]._pAnimData = plr[pnum]._pBAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pBAnim[dir];
break; break;
case PM_SPELL: case PM_SPELL:
if (pnum == myplr) if (pnum == myplr)
@ -3988,23 +3988,23 @@ void SyncPlrAnim(int pnum)
else else
sType = STYPE_FIRE; sType = STYPE_FIRE;
if (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) if (sType == STYPE_LIGHTNING)
plr[pnum]._pAnimData = plr[pnum]._pLAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pLAnim[dir];
if (sType == STYPE_MAGIC) if (sType == STYPE_MAGIC)
plr[pnum]._pAnimData = plr[pnum]._pTAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pTAnim[dir];
break; break;
case PM_GOTHIT: case PM_GOTHIT:
plr[pnum]._pAnimData = plr[pnum]._pHAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pHAnim[dir];
break; break;
case PM_NEWLVL: case PM_NEWLVL:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break; break;
case PM_DEATH: case PM_DEATH:
plr[pnum]._pAnimData = plr[pnum]._pDAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pDAnim[dir];
break; break;
case PM_QUIT: case PM_QUIT:
plr[pnum]._pAnimData = plr[pnum]._pNAnim[dir]; plr[pnum].AnimInfo._pAnimData = plr[pnum]._pNAnim[dir];
break; break;
default: default:
app_fatal("SyncPlrAnim"); app_fatal("SyncPlrAnim");

20
Source/player.h

@ -16,6 +16,7 @@
#include "path.h" #include "path.h"
#include "interfac.h" #include "interfac.h"
#include "utils/enum_traits.h" #include "utils/enum_traits.h"
#include "engine/animationinfo.h"
namespace devilution { namespace devilution {
@ -160,24 +161,11 @@ struct PlayerStruct {
ActorPosition position; ActorPosition position;
direction _pdir; // Direction faced by player (direction enum) 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) 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; AnimationInfo AnimInfo;
/* int _pAnimWidth;
* @brief Animation Frames that will be adjusted for the skipped Frames/GameTicks
*/
int _pAnimRelevantAnimationFramesForDistributing;
int _plid; int _plid;
int _pvid; int _pvid;
spell_id _pSpell; 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]; PlayerStruct *pPlayer = &plr[pnum];
BYTE *pCelBuff = pPlayer->_pAnimData; BYTE *pCelBuff = pPlayer->AnimInfo._pAnimData;
int nCel = GetFrameToUseForPlayerRendering(pPlayer); int nCel = GetFrameToUseForPlayerRendering(pPlayer);
int nWidth = pPlayer->_pAnimWidth; int nWidth = pPlayer->_pAnimWidth;

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

@ -1,7 +1,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "nthread.h" #include "nthread.h"
#include "player.h" #include "engine/animationinfo.h"
using namespace devilution; 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) void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams params, int numSkippedFrames, int distributeFramesBeforeFrame, const std::vector<TestData *> &vecTestData)
{ {
const int pnum = 0; const int pnum = 0;
PlayerStruct *pPlayer = &plr[pnum]; AnimationInfo animInfo;
NewPlrAnim(pnum, nullptr, numFrames, delay, 128, params, numSkippedFrames, distributeFramesBeforeFrame); animInfo.SetNewAnimation(nullptr, numFrames, delay, params, numSkippedFrames, distributeFramesBeforeFrame);
int currentGameTick = 0; int currentGameTick = 0;
for (TestData *x : vecTestData) { for (TestData *x : vecTestData) {
@ -62,10 +62,10 @@ void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams para
if (gameTickData != nullptr) { if (gameTickData != nullptr) {
currentGameTick += 1; currentGameTick += 1;
if (gameTickData->_FramesToSkip != 0) if (gameTickData->_FramesToSkip != 0)
pPlayer->_pAnimFrame += gameTickData->_FramesToSkip; pPlayer->AnimInfo._pAnimFrame += gameTickData->_FramesToSkip;
ProcessPlayerAnimation(pnum); ProcessPlayerAnimation(pnum);
EXPECT_EQ(pPlayer->_pAnimFrame, gameTickData->_ExpectedAnimationFrame); EXPECT_EQ(pPlayer->AnimInfo._pAnimFrame, gameTickData->_ExpectedAnimationFrame);
EXPECT_EQ(pPlayer->_pAnimCnt, gameTickData->_ExpectedAnimationCnt); EXPECT_EQ(pPlayer->AnimInfo._pAnimCnt, gameTickData->_ExpectedAnimationCnt);
} }
auto renderingData = dynamic_cast<RenderingData *>(x); auto renderingData = dynamic_cast<RenderingData *>(x);
@ -74,8 +74,8 @@ void RunAnimationTest(int numFrames, int delay, AnimationDistributionParams para
EXPECT_EQ(GetFrameToUseForPlayerRendering(pPlayer), renderingData->_ExpectedRenderingFrame) EXPECT_EQ(GetFrameToUseForPlayerRendering(pPlayer), renderingData->_ExpectedRenderingFrame)
<< std::fixed << std::setprecision(2) << std::fixed << std::setprecision(2)
<< "ProgressToNextGameTick: " << renderingData->_fProgressToNextGameTick << "ProgressToNextGameTick: " << renderingData->_fProgressToNextGameTick
<< " AnimFrame: " << pPlayer->_pAnimFrame << " AnimFrame: " << pPlayer->AnimInfo._pAnimFrame
<< " AnimCnt: " << pPlayer->_pAnimCnt << " AnimCnt: " << pPlayer->AnimInfo._pAnimCnt
<< " GameTick: " << currentGameTick; << " 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, 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 GameTickData(2, 0),
new RenderingData(0.0f, 1), new RenderingData(0.0f, 1),
new RenderingData(0.3f, 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, 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 GameTickData(2, 0),
new RenderingData(0.0f, 1), new RenderingData(0.0f, 1),
new RenderingData(0.3f, 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, 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, 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, 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) {" // 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, 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 RunBlockTest(int frames, int flags)
{ {
int pnum = 0; int pnum = 0;
plr[pnum]._pAnimFrame = 1; plr[pnum].AnimInfo._pAnimFrame = 1;
plr[pnum]._pHFrames = frames; plr[pnum]._pHFrames = frames;
plr[pnum].actionFrame = 1; plr[pnum].actionFrame = 1;
plr[pnum]._pIFlags = flags; plr[pnum]._pIFlags = flags;
@ -23,7 +23,7 @@ int RunBlockTest(int frames, int flags)
PM_DoGotHit(pnum); PM_DoGotHit(pnum);
if (plr[pnum]._pmode != PM_GOTHIT) if (plr[pnum]._pmode != PM_GOTHIT)
break; break;
plr[pnum]._pAnimFrame++; plr[pnum].AnimInfo._pAnimFrame++;
} }
return i; return i;

8
test/writehero_test.cpp

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

Loading…
Cancel
Save